Golang基于Redis生成唯一序列

Golang基于Redis生成唯一序列

Golang 1.22.2

在项目中对应数据库的唯一id,我们通常会选择使用数据库自增id,uuid,雪花算法等,每种模式的id都有各自的优缺点,这里将通过Redis来实现一种自定义分布式的id生成模式。

当然这种实现方式也有一定的缺点,那就是过于依赖redis,一旦出现redis不可用,会导致id生成失败,从而影响整个业务的不可用,所以在设计的时候需要考虑,如果redis不可用如何自动启用其他唯一id生成模式。

首先看redis实现部分:

// 获取第一个Key
local id_key = KEYS[1]
// 获取形参的第一个数,这里是初始化值
local initial = ARGV[1]
// 获取形参的第二数据,这里每次增长的量
local step = ARGV[2]
// 检查key当前是否存在值
local current = redis.call('get', id_key)
// 如果不存在则返回初始值,并在redis中设置初始值
if current == false then
        redis.call('set', id_key, initial)
        return initial
end
--redis.log(redis.LOG_NOTICE,' current:'..current..':')
// 如果存在值则在当前值上面加入每次的增量,设置到redis后并返回
local result =  tonumber(current)+ tonumber(step)
--redis.log(redis.LOG_NOTICE,' result:'..result..':')
redis.call('set', id_key, result)
return tostring(result)

实现完lua脚本后,我们知道一共需要传递三个参数,有个是业务key,用来标识每个表的,第二参数是id的初始值,比如从1开始或者从1000开始,就是通过第二个参数来设置的,最后一个是每次的增长量:比如我们需要每次在原来的基础+1,那么就传递1,如果需要+10,对应的传递10即可。

脚本有了之后我们开始在golang中实现具体的功能。

1.定义配置对象

type Config struct {
    Prefix  string `json:"prefix"`  // 编号前缀
    Initial int64  `json:"initial"` // 初始值
    Step    int    `json:"step"`    // 每次递增数量
    Remark  string `json:"remark"`  // 备注
    Key     string `json:"key"`     // key名称
}

2.定义接口

这里需要定义一个接口来满足不同的id需求。

// Sequence 序列接口
type Sequence interface {
    // GetConfig 获取序列配置
    GetConfig() Config
    // NextCode 获取下一个序列化
    NextCode() string
}

3.ID生成

// 执行lua脚本获取id
func execute(conf Config) string {
    luaId := redis.NewScript(`
        local id_key = KEYS[1]
        local initial = ARGV[1]
        local step = ARGV[2]
        local current = redis.call('get', id_key)
        if current == false then
            redis.call('set', id_key, initial)
            return initial
        end
        --redis.log(redis.LOG_NOTICE,' current:'..current..':')
        local result =  tonumber(current)+ tonumber(step)
        --redis.log(redis.LOG_NOTICE,' result:'..result..':')
        redis.call('set', id_key, result)
        return tostring(result)
    `)
    var ctx = context.Background()
    n, err := luaId.Run(ctx, cache.RedisClient, []string{conf.Key}, conf.Initial, conf.Step).Int64()
    if err != nil {
        log.Log.Errorf("生成序列id错误:%s", err.Error())
        return ""
    }
    return fmt.Sprintf("%s%d", conf.Prefix, n)
}

4.自定义配置

type GoodsSeq struct {
}

func (u GoodsSeq) GetConfig() Config {
    return Config{
        Prefix:  "SP",
        Initial: 300,
        Step:    1,
        Remark:  "商品配置ID生成器",
        Key:     "goods-id-seq",
    }
}

func (u GoodsSeq) NextCode() string {
    conf := u.GetConfig()
    return execute(conf)
}

5.调用案例

// 生成商品ID
id := sequence.GoodsSeq{}.NextCode()
// 输出值:SP300

到这里,一个分布式自定义的id生成器已经实现,给出的样例中,针对redis不可用的情况并没有做处理,在生产环境中需要考虑。