雪花算法是解决分布式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
。