[{"data":1,"prerenderedAt":528},["ShallowReactive",2],{"post-\u002Fposts\u002Fgolang-redis-seq":3,"surround-\u002Fposts\u002Fgolang-redis-seq":523},{"id":4,"title":5,"body":6,"cover":509,"date":510,"description":511,"draft":512,"extension":513,"meta":514,"minutes":45,"navigation":403,"path":515,"seo":516,"stem":517,"tags":518,"updated":509,"__hash__":522},"posts\u002Fposts\u002Fgolang-redis-seq.md","Golang基于Redis生成唯一序列",{"type":7,"value":8,"toc":502},"minimark",[9,13,16,19,145,148,151,156,196,200,203,242,246,381,385,471,475,495,498],[10,11,12],"p",{},"在项目中对应数据库的唯一id，我们通常会选择使用数据库自增id，uuid，雪花算法等，每种模式的id都有各自的优缺点，这里将通过Redis来实现一种自定义分布式的id生成模式。",[10,14,15],{},"当然这种实现方式也有一定的缺点，那就是过于依赖redis，一旦出现redis不可用，会导致id生成失败，从而影响整个业务的不可用，所以在设计的时候需要考虑，如果redis不可用如何自动启用其他唯一id生成模式。",[10,17,18],{},"首先看redis实现部分：",[20,21,26],"pre",{"className":22,"code":23,"language":24,"meta":25,"style":25},"language-go shiki shiki-themes tokyo-night github-light","\u002F\u002F 获取第一个Key\nlocal id_key = KEYS[1]\n\u002F\u002F 获取形参的第一个数，这里是初始化值\nlocal initial = ARGV[1]\n\u002F\u002F 获取形参的第二数据，这里每次增长的量\nlocal step = ARGV[2]\n\u002F\u002F 检查key当前是否存在值\nlocal current = redis.call('get', id_key)\n\u002F\u002F 如果不存在则返回初始值，并在redis中设置初始值\nif current == false then\n        redis.call('set', id_key, initial)\n        return initial\nend\n--redis.log(redis.LOG_NOTICE,' current:'..current..':')\n\u002F\u002F 如果存在值则在当前值上面加入每次的增量，设置到redis后并返回\nlocal result =  tonumber(current)+ tonumber(step)\n--redis.log(redis.LOG_NOTICE,' result:'..result..':')\nredis.call('set', id_key, result)\nreturn tostring(result)\n","go","",[27,28,29,37,43,49,55,61,67,73,79,85,91,97,103,109,115,121,127,133,139],"code",{"__ignoreMap":25},[30,31,34],"span",{"class":32,"line":33},"line",1,[30,35,36],{},"\u002F\u002F 获取第一个Key\n",[30,38,40],{"class":32,"line":39},2,[30,41,42],{},"local id_key = KEYS[1]\n",[30,44,46],{"class":32,"line":45},3,[30,47,48],{},"\u002F\u002F 获取形参的第一个数，这里是初始化值\n",[30,50,52],{"class":32,"line":51},4,[30,53,54],{},"local initial = ARGV[1]\n",[30,56,58],{"class":32,"line":57},5,[30,59,60],{},"\u002F\u002F 获取形参的第二数据，这里每次增长的量\n",[30,62,64],{"class":32,"line":63},6,[30,65,66],{},"local step = ARGV[2]\n",[30,68,70],{"class":32,"line":69},7,[30,71,72],{},"\u002F\u002F 检查key当前是否存在值\n",[30,74,76],{"class":32,"line":75},8,[30,77,78],{},"local current = redis.call('get', id_key)\n",[30,80,82],{"class":32,"line":81},9,[30,83,84],{},"\u002F\u002F 如果不存在则返回初始值，并在redis中设置初始值\n",[30,86,88],{"class":32,"line":87},10,[30,89,90],{},"if current == false then\n",[30,92,94],{"class":32,"line":93},11,[30,95,96],{},"        redis.call('set', id_key, initial)\n",[30,98,100],{"class":32,"line":99},12,[30,101,102],{},"        return initial\n",[30,104,106],{"class":32,"line":105},13,[30,107,108],{},"end\n",[30,110,112],{"class":32,"line":111},14,[30,113,114],{},"--redis.log(redis.LOG_NOTICE,' current:'..current..':')\n",[30,116,118],{"class":32,"line":117},15,[30,119,120],{},"\u002F\u002F 如果存在值则在当前值上面加入每次的增量，设置到redis后并返回\n",[30,122,124],{"class":32,"line":123},16,[30,125,126],{},"local result =  tonumber(current)+ tonumber(step)\n",[30,128,130],{"class":32,"line":129},17,[30,131,132],{},"--redis.log(redis.LOG_NOTICE,' result:'..result..':')\n",[30,134,136],{"class":32,"line":135},18,[30,137,138],{},"redis.call('set', id_key, result)\n",[30,140,142],{"class":32,"line":141},19,[30,143,144],{},"return tostring(result)\n",[10,146,147],{},"实现完lua脚本后，我们知道一共需要传递三个参数，有个是业务key，用来标识每个表的，第二参数是id的初始值，比如从1开始或者从1000开始，就是通过第二个参数来设置的，最后一个是每次的增长量：比如我们需要每次在原来的基础+1，那么就传递1，如果需要+10，对应的传递10即可。",[10,149,150],{},"脚本有了之后我们开始在golang中实现具体的功能。",[152,153,155],"h2",{"id":154},"_1定义配置对象","1.定义配置对象",[20,157,159],{"className":22,"code":158,"language":24,"meta":25,"style":25},"type Config struct {\n    Prefix  string `json:\"prefix\"`  \u002F\u002F 编号前缀\n    Initial int64  `json:\"initial\"` \u002F\u002F 初始值\n    Step    int    `json:\"step\"`    \u002F\u002F 每次递增数量\n    Remark  string `json:\"remark\"`  \u002F\u002F 备注\n    Key     string `json:\"key\"`     \u002F\u002F key名称\n}\n",[27,160,161,166,171,176,181,186,191],{"__ignoreMap":25},[30,162,163],{"class":32,"line":33},[30,164,165],{},"type Config struct {\n",[30,167,168],{"class":32,"line":39},[30,169,170],{},"    Prefix  string `json:\"prefix\"`  \u002F\u002F 编号前缀\n",[30,172,173],{"class":32,"line":45},[30,174,175],{},"    Initial int64  `json:\"initial\"` \u002F\u002F 初始值\n",[30,177,178],{"class":32,"line":51},[30,179,180],{},"    Step    int    `json:\"step\"`    \u002F\u002F 每次递增数量\n",[30,182,183],{"class":32,"line":57},[30,184,185],{},"    Remark  string `json:\"remark\"`  \u002F\u002F 备注\n",[30,187,188],{"class":32,"line":63},[30,189,190],{},"    Key     string `json:\"key\"`     \u002F\u002F key名称\n",[30,192,193],{"class":32,"line":69},[30,194,195],{},"}\n",[152,197,199],{"id":198},"_2定义接口","2.定义接口",[10,201,202],{},"这里需要定义一个接口来满足不同的id需求。",[20,204,206],{"className":22,"code":205,"language":24,"meta":25,"style":25},"\u002F\u002F Sequence 序列接口\ntype Sequence interface {\n    \u002F\u002F GetConfig 获取序列配置\n    GetConfig() Config\n    \u002F\u002F NextCode 获取下一个序列化\n    NextCode() string\n}\n",[27,207,208,213,218,223,228,233,238],{"__ignoreMap":25},[30,209,210],{"class":32,"line":33},[30,211,212],{},"\u002F\u002F Sequence 序列接口\n",[30,214,215],{"class":32,"line":39},[30,216,217],{},"type Sequence interface {\n",[30,219,220],{"class":32,"line":45},[30,221,222],{},"    \u002F\u002F GetConfig 获取序列配置\n",[30,224,225],{"class":32,"line":51},[30,226,227],{},"    GetConfig() Config\n",[30,229,230],{"class":32,"line":57},[30,231,232],{},"    \u002F\u002F NextCode 获取下一个序列化\n",[30,234,235],{"class":32,"line":63},[30,236,237],{},"    NextCode() string\n",[30,239,240],{"class":32,"line":69},[30,241,195],{},[152,243,245],{"id":244},"_3id生成","3.ID生成",[20,247,249],{"className":22,"code":248,"language":24,"meta":25,"style":25},"\u002F\u002F 执行lua脚本获取id\nfunc execute(conf Config) string {\n    luaId := redis.NewScript(`\n        local id_key = KEYS[1]\n        local initial = ARGV[1]\n        local step = ARGV[2]\n        local current = redis.call('get', id_key)\n        if current == false then\n            redis.call('set', id_key, initial)\n            return initial\n        end\n        --redis.log(redis.LOG_NOTICE,' current:'..current..':')\n        local result =  tonumber(current)+ tonumber(step)\n        --redis.log(redis.LOG_NOTICE,' result:'..result..':')\n        redis.call('set', id_key, result)\n        return tostring(result)\n    `)\n    var ctx = context.Background()\n    n, err := luaId.Run(ctx, cache.RedisClient, []string{conf.Key}, conf.Initial, conf.Step).Int64()\n    if err != nil {\n        log.Log.Errorf(\"生成序列id错误:%s\", err.Error())\n        return \"\"\n    }\n    return fmt.Sprintf(\"%s%d\", conf.Prefix, n)\n}\n",[27,250,251,256,261,266,271,276,281,286,291,296,301,306,311,316,321,326,331,336,341,346,352,358,364,370,376],{"__ignoreMap":25},[30,252,253],{"class":32,"line":33},[30,254,255],{},"\u002F\u002F 执行lua脚本获取id\n",[30,257,258],{"class":32,"line":39},[30,259,260],{},"func execute(conf Config) string {\n",[30,262,263],{"class":32,"line":45},[30,264,265],{},"    luaId := redis.NewScript(`\n",[30,267,268],{"class":32,"line":51},[30,269,270],{},"        local id_key = KEYS[1]\n",[30,272,273],{"class":32,"line":57},[30,274,275],{},"        local initial = ARGV[1]\n",[30,277,278],{"class":32,"line":63},[30,279,280],{},"        local step = ARGV[2]\n",[30,282,283],{"class":32,"line":69},[30,284,285],{},"        local current = redis.call('get', id_key)\n",[30,287,288],{"class":32,"line":75},[30,289,290],{},"        if current == false then\n",[30,292,293],{"class":32,"line":81},[30,294,295],{},"            redis.call('set', id_key, initial)\n",[30,297,298],{"class":32,"line":87},[30,299,300],{},"            return initial\n",[30,302,303],{"class":32,"line":93},[30,304,305],{},"        end\n",[30,307,308],{"class":32,"line":99},[30,309,310],{},"        --redis.log(redis.LOG_NOTICE,' current:'..current..':')\n",[30,312,313],{"class":32,"line":105},[30,314,315],{},"        local result =  tonumber(current)+ tonumber(step)\n",[30,317,318],{"class":32,"line":111},[30,319,320],{},"        --redis.log(redis.LOG_NOTICE,' result:'..result..':')\n",[30,322,323],{"class":32,"line":117},[30,324,325],{},"        redis.call('set', id_key, result)\n",[30,327,328],{"class":32,"line":123},[30,329,330],{},"        return tostring(result)\n",[30,332,333],{"class":32,"line":129},[30,334,335],{},"    `)\n",[30,337,338],{"class":32,"line":135},[30,339,340],{},"    var ctx = context.Background()\n",[30,342,343],{"class":32,"line":141},[30,344,345],{},"    n, err := luaId.Run(ctx, cache.RedisClient, []string{conf.Key}, conf.Initial, conf.Step).Int64()\n",[30,347,349],{"class":32,"line":348},20,[30,350,351],{},"    if err != nil {\n",[30,353,355],{"class":32,"line":354},21,[30,356,357],{},"        log.Log.Errorf(\"生成序列id错误:%s\", err.Error())\n",[30,359,361],{"class":32,"line":360},22,[30,362,363],{},"        return \"\"\n",[30,365,367],{"class":32,"line":366},23,[30,368,369],{},"    }\n",[30,371,373],{"class":32,"line":372},24,[30,374,375],{},"    return fmt.Sprintf(\"%s%d\", conf.Prefix, n)\n",[30,377,379],{"class":32,"line":378},25,[30,380,195],{},[152,382,384],{"id":383},"_4自定义配置","4.自定义配置",[20,386,388],{"className":22,"code":387,"language":24,"meta":25,"style":25},"type GoodsSeq struct {\n}\n\nfunc (u GoodsSeq) GetConfig() Config {\n    return Config{\n        Prefix:  \"SP\",\n        Initial: 300,\n        Step:    1,\n        Remark:  \"商品配置ID生成器\",\n        Key:     \"goods-id-seq\",\n    }\n}\n\nfunc (u GoodsSeq) NextCode() string {\n    conf := u.GetConfig()\n    return execute(conf)\n}\n",[27,389,390,395,399,405,410,415,420,425,430,435,440,444,448,452,457,462,467],{"__ignoreMap":25},[30,391,392],{"class":32,"line":33},[30,393,394],{},"type GoodsSeq struct {\n",[30,396,397],{"class":32,"line":39},[30,398,195],{},[30,400,401],{"class":32,"line":45},[30,402,404],{"emptyLinePlaceholder":403},true,"\n",[30,406,407],{"class":32,"line":51},[30,408,409],{},"func (u GoodsSeq) GetConfig() Config {\n",[30,411,412],{"class":32,"line":57},[30,413,414],{},"    return Config{\n",[30,416,417],{"class":32,"line":63},[30,418,419],{},"        Prefix:  \"SP\",\n",[30,421,422],{"class":32,"line":69},[30,423,424],{},"        Initial: 300,\n",[30,426,427],{"class":32,"line":75},[30,428,429],{},"        Step:    1,\n",[30,431,432],{"class":32,"line":81},[30,433,434],{},"        Remark:  \"商品配置ID生成器\",\n",[30,436,437],{"class":32,"line":87},[30,438,439],{},"        Key:     \"goods-id-seq\",\n",[30,441,442],{"class":32,"line":93},[30,443,369],{},[30,445,446],{"class":32,"line":99},[30,447,195],{},[30,449,450],{"class":32,"line":105},[30,451,404],{"emptyLinePlaceholder":403},[30,453,454],{"class":32,"line":111},[30,455,456],{},"func (u GoodsSeq) NextCode() string {\n",[30,458,459],{"class":32,"line":117},[30,460,461],{},"    conf := u.GetConfig()\n",[30,463,464],{"class":32,"line":123},[30,465,466],{},"    return execute(conf)\n",[30,468,469],{"class":32,"line":129},[30,470,195],{},[152,472,474],{"id":473},"_5调用案例","5.调用案例",[20,476,478],{"className":22,"code":477,"language":24,"meta":25,"style":25},"\u002F\u002F 生成商品ID\nid := sequence.GoodsSeq{}.NextCode()\n\u002F\u002F 输出值：SP300\n",[27,479,480,485,490],{"__ignoreMap":25},[30,481,482],{"class":32,"line":33},[30,483,484],{},"\u002F\u002F 生成商品ID\n",[30,486,487],{"class":32,"line":39},[30,488,489],{},"id := sequence.GoodsSeq{}.NextCode()\n",[30,491,492],{"class":32,"line":45},[30,493,494],{},"\u002F\u002F 输出值：SP300\n",[10,496,497],{},"到这里，一个分布式自定义的id生成器已经实现，给出的样例中，针对redis不可用的情况并没有做处理，在生产环境中需要考虑。",[499,500,501],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}",{"title":25,"searchDepth":45,"depth":45,"links":503},[504,505,506,507,508],{"id":154,"depth":39,"text":155},{"id":198,"depth":39,"text":199},{"id":244,"depth":39,"text":245},{"id":383,"depth":39,"text":384},{"id":473,"depth":39,"text":474},null,"2024-03-15","当前基于Golang 1.22.2 版本实现",false,"md",{},"\u002Fposts\u002Fgolang-redis-seq",{"title":5,"description":511},"posts\u002Fgolang-redis-seq",[519,520,521],"golang","redis","分布式序列号","L-Gnq8uBoo8MIwWxTWwoW67pjS2OD_83cuIHj4EHNvE",[524,509],{"title":525,"path":526,"stem":527,"children":-1},"Golang exec.Cmd在Kill后不能关闭进程","\u002Fposts\u002Fgolang-exec-kill","posts\u002Fgolang-exec-kill",1786707084689]