分布式id(3)— snowflake(雪花算法)的问题

雪花算法是解决分布式id的一个高效的方案,大部分互联网公司都在使用雪花算法,当然还有公司自己实现其他的方案。该算法生成的是一个64位的ID,故在Java下正好可以通过8字节的long类型存放。所生成的ID结构如下所示:
在这里插入图片描述
但雪花算法依然存在id重复的问题:

1、时间回拨产生的id重复
由于雪花算法严重依赖时间,所以当发生服务器时钟回拨的问题是会导致可能产生重复的id。当然几乎没有公司会修改服务器时间,修改以后会导致各种问题,公司宁愿新加一台服务器也不愿意修改服务器时间,但是不排除特殊情况。

如何解决时钟回拨的问题?

  • 当时间回拨较小时,等待时钟同步到最后一次主键生成的时间后再继续工作。

  • 当时间回拨较大时,可以对序列化的初始值设置步长,每次触发时钟回拨事件,则其初始步长就加1w,可以在下面代码的第85行来实现,将sequence的初始值设置为10000。

2、workId产生的id重复问题
同一机器同一毫秒级,我们能生成4096个不同序列,即不同Id,但是如果我们使用的是微服务架构,那不同机器人是否会可能生成相同Id呢?

提到工作机器Id的作用,就是用于解决分布式Id重复的问题,这个workerId是通过构造方法传入的,如果我们用10位来存储这个值,那就是最多支持1024个节点

那么关键问题就回归到如何去把我们的服务器和workerId对应起来?如果不是容器化部署,部署是固定的机器,我们用机器的唯一名来做key,那我们可以对这些机器名和workerId建立一个对应关系,如果存在就用之前的workerId,不存在就往上累加比如我们用计算机名做key:
在这里插入图片描述
这样机器如果不断累加,最多支持1024台服务器

但是如果是容器化部署,需要支持动态增加节点,并且每次部署的机器不一定一样时,就会有问题,如果发现不同,就往上累加,经过多次发版,就可能会超过1023,这个时候生成雪花Id时,工作机器id左移12位后,当进行或运算时,时间戳的位置就会被影响,比如workerId=1024,我们拿之前的举例第1000ms,那它和第1001ms、workerId=0配置,可能生成重复的Id,如下图所示:
在这里插入图片描述

        private static void Init()
        {
            if (worker == null)
            {
                //初始化为1
                long workerId = 1;
                //得到服务器机器名称
                string hostName = System.Net.Dns.GetHostName();
                if (RedisHelper.Exists(hostName))
                {
                    // 如果redis中存在改服务器名称,则直接取得workerId
                    workerId =long.Parse(RedisHelper.Get(hostName));
                }
                else
                {
                    //如果redis不存在,则用hashcode对32取模
                    var code = hostName.GetHashCode();
                    var Id = code % 32;
                    //如果取模以后的Id,大于15,则从0~15中随机一个数字,也就是把16~31中转换到0~15,并存入redis
                    //原因是,我司只给了4个bit存储workerId,所以只能支持0~15
                    if (Id>15||Id<0)
                    {
                        Id = new Random().Next(0, 15);
                    }
                    workerId = (long)Id;
                    RedisHelper.Set(hostName, workerId);
                }
                //把workerId传入构造方法
                worker = new IdWorker(workerId);
            }
        }

上述代码有2个问题:

  • hashcode对32取模,本身就可能会重复,比如460141958和3164804对32取模都是4,那生成的workerId就重复了
  • 如果hashcode>15,随机取一个,那每次都有1/16的概率重复

优化方案为:

  • 在redis中存储一个当前workerId的最大值
  • 每次生成workerId时,从redis中获取到当前workerId最大值,并+1作为当前workerId,并存入redis
  • 如果workerId为1023,自增为1024,则重置0,作为当前workerId,并存入redis

述方案是否一定没问题呢?其实是有的,如果自增1新分配的workerId还没释放掉,这个时候就会冲突了

比如我们第一个pod(workerId=0)一直没有重启过,但是第二个pod一直在重启,达到1024时回到0,则同时会有两个pod的workerId为0, 这两台pod上程序生成的Id就有可能重复。

我们算极端情况下,workerIdBits=10,即1024个节点的情况下,可以支持到两次发版中间第一个pod一直不重启,其余5个pod一直重启的极端情况下,也能支持204次。但是只要发一次版本,所有pod都会到最近redis中记录的最大workerId,像我们一周一个版本的情况,不会存在这个问题。

分布式id参考方案:

SnowFlake
今天的主角雪花算法,它是Twitter开源的由64位整数组成分布式ID,性能较高,并且在单机上递增。 具体参考:

https://github.com/twitter-archive/snowflake

UidGenerator
UidGenerator是百度开源的分布式ID生成器,其基于雪花算法实现。 具体参考:

https://github.com/baidu/uid-generator/blob/master/README.zh_cn.md

Leaf
Leaf是美团开源的分布式ID生成器,能保证全局唯一,趋势递增,但需要依赖关系数据库、Zookeeper等中间件。 具体参考:

https://tech.meituan.com/MT_Leaf.html

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值