V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
flycloud
V2EX  ›  程序员

分享一个 id 自增生成器,依赖于 redis,求大佬帮忙挑挑毛病

  •  
  •   flycloud · 74 天前 · 3996 次点击
    这是一个创建于 74 天前的主题,其中的信息可能已经有所发展或是发生改变。

    看了雪花算法,服务需要配置标识位(数据中心 ID 、机器 ID ),多一项配置就多一份出错的风险,同时在不依赖其他基础服务的情况下也不太好解决时间回调的问题。然后自己实现了一个简化版的,依赖于 redis 。大佬们帮忙看看有啥问题没有,感谢。

    func GlobalIncrId() (int64, error) {
    	script := redis.NewScript(`
    		local key = KEYS[1]
    		local stamp = ARGV[1]
    		
    		local newValue = redis.call("incr", key)
    		if newValue then
    			if newValue > tonumber(stamp) then
    				return newValue
    			else
    				local flag = redis.call("set", key, stamp)
    				if flag then
    					return stamp
    				end
    			end
    		end
    	
    		return nil
    	`)
    	stamp := (time.Now().Unix() - 50 * 365 * 86400) << 22
    	ret, err := script.Run(RedisIns, []string{sredis.KEY_GLOBAL_INCR_ID}, stamp).Int64()
    	return ret, err
    }
    

    自增 id 带上 stamp 信息, 是为了防止 redis 的 key 丢失, 或者值被清除。stamp 的计算回拨了 50 年, 因为 32bit 的时间戳到 2038 年就溢出了。只要机器的系统时间回调以及 key 失效这两件事不同时发生, 就能保持自增性。

    低 22 位用于 redis incr 自增,高位是时间信息,即 1s 内最多支持 4194304 个 id,超出了也没关系,只是会提前占用高位的时间。

    用 22 位来自增是因为某些业务可能会将 id 用在 zadd 中,zadd 的 score 范围是:-(2^53) 至 +(2^53), 即时间信息可以占用 31 位, 保证 score 不会溢出。

    32 条回复    2021-09-18 18:30:45 +08:00
    GM
        1
    GM   74 天前   ❤️ 20
    看到那么多发明 id 生成器的,我来分享一个我喜欢用的吧:

    select uuid_short() as id;


    优点:
    纯数字单调递增 id
    性能足够好,每秒几万 ID 毫无压力
    分布式,多台服务器生成的 id 不会重复
    依赖少,只要你用 MySql 就够了。
    不怕 id 生成系统失效,只要数据库还活着就能用。如果数据库都挂了,id 生成器活着也没用。
    zhaokun
        2
    zhaokun   74 天前   ❤️ 1
    依赖的服务要稳住
    雪花可以拿机器 ip 作为机器 id,不需要配置
    flycloud
        3
    flycloud   74 天前
    @GM 我们业务没有用 mysql,🤣
    flycloud
        4
    flycloud   74 天前
    @zhaokun 有道理
    Ariver
        5
    Ariver   74 天前 via iPhone
    redis 集群的话考虑了吗
    flycloud
        6
    flycloud   74 天前
    @Ariver 集群或者单实例都支持呀,反正只用到了一个 key 。主要是可以规避时间回调问题。
    lysS
        7
    lysS   74 天前
    @flycloud 这跟 mysql 有啥关系?
    不考虑持久化,直接 uint64 自增;根本不会出现碰撞的可能
    flycloud
        8
    flycloud   74 天前
    @lysS 没懂你的意思。

    我们业务目前没有使用 mysql,不想因为一个 id 生成新增加一个组件。

    “不考虑持久化,直接 uint64 自增” 这个分布式环境你咋个自增
    bthulu
        9
    bthulu   74 天前
    @zhaokun ip 重复了咋办,我司机器是分布式部署的,直接部署在门店里,门店用的都是家用路由器,ip 都是 192.168.x.xxx ,很容易重复的
    enan01
        10
    enan01   74 天前 via iPhone
    redis 如果使用主从,从节点同步延迟,也会导致 ID 重复吧
    XiLingHost
        11
    XiLingHost   74 天前
    @bthulu 那就用 mac
    skies457
        12
    skies457   74 天前
    return ++i;
    draymonder
        13
    draymonder   74 天前
    1. 用脚本,会拖慢 redis 性能,主从之间复制也可能会出问题
    2. 另外,请求 redis 失败了呢,多一个依赖,就会降低整体系统稳定性
    billly
        14
    billly   73 天前
    redis 出问题的几率肯定比我加两个配置出问题的几率大
    Kinnice
        15
    Kinnice   73 天前
    @bthulu ip+mac+hostname+deviceid
    flycloud
        16
    flycloud   73 天前
    @draymonder #13

    第一个问题确实存在。
    因为本来就会大量用到 redis,所以并没有增加依赖。
    flycloud
        17
    flycloud   73 天前
    @enan01 #10 没有这个问题,因为只会在主节点写 redis
    enan01
        18
    enan01   73 天前
    @flycloud 主节点挂掉,切换到从节点,从节点异步复制,没办法保证数据强一致,如果从节点 key 存在,但是 value 没有完成复制,从节点继续 incr,会存在重复的可能
    workingonescape
        19
    workingonescape   73 天前
    @GM 你这个有链接么,想学习一下
    GM
        20
    GM   73 天前
    @workingonescape

    没什么要学的,就是 mysql 自带的,直接 select uuid_short() 就 完事了。
    JakeZou
        21
    JakeZou   73 天前
    @GM 确实不错
    seakingii
        22
    seakingii   73 天前
    最大的问题是用 REDIS
    xingzhi
        23
    xingzhi   73 天前
    @GM 这方法确实不错

    本质上感觉跟雪花算法差不多,机器 ID+时间戳+自增序列
    区别在于雪花算法用了 ZK 来管理机器 ID 了
    wangritian
        24
    wangritian   73 天前
    数据中心 ID 配置不麻烦吧,机器 ID 可以用 redis incr 搞定啊,生成就不要依赖网络了,也可以用 k8s-statefulset 的 hostname 最后数字作为机器 ID
    Rocketer
        25
    Rocketer   73 天前
    uuid 的问题在于多台机器之间无法实现递增关系,雪花算法主要就是为了解决这个问题的,后生成的 id 一定大于之前生成的 id 。

    如果对雪花的性能不满意,可以考虑做个 id 微服务,闲余时提前生好,存在内存里,需要时取第一个发下去就行了,这样不会重复,也保证了递增关系。关键是算法可以很简单(比如就是数字递增),从而保证这个微服务有极强的承载能力。
    hanxiV2EX
        26
    hanxiV2EX   73 天前 via Android
    https://github.com/hanxi/skynet-demo/commit/b4ef9dd1b8e835318da8f17bec885bb4b58d5b73

    我也实现了一个雪花算法,分布式不依赖数据库。用本地定时存文件的方式解决时间回调问题。
    hanxiV2EX
        27
    hanxiV2EX   73 天前 via Android
    既然用 redis 了,还不如直接用 lua 实现集成在 redis 里,用 EVALSHA 获取 id
    fpure
        28
    fpure   73 天前
    @GM 学到了👍
    JamesChen
        29
    JamesChen   73 天前
    路过提一嘴,“if newValue then”和“if flag then”永远都是 True 。lua 不是 js,别写混了
    GTim
        30
    GTim   73 天前
    @GM
    flycloud
        31
    flycloud   73 天前
    @JamesChen #29 谢谢,看了文档,确实啊。如果 redis.call 命令有错误会直接抛出异常了,如果执行成功,返回值一定是 True
    egfegdfr
        32
    egfegdfr   72 天前
    mybatis-plus 自带的 ID_WORKER 及挺好用的,不需要太多配置,直接注解就行,生产的是 有序但是不连续的 id,还可防止被人恶意爆破的问题
    关于   ·   帮助文档   ·   API   ·   FAQ   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   3395 人在线   最高记录 5497   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 05:32 · PVG 13:32 · LAX 21:32 · JFK 00:32
    ♥ Do have faith in what you're doing.