大家好,我是悟空。本文主要内容如下:前言最近生产环境遇到一个问题:现象:创建工??单、订单等,都创建数据失败。初步排查:报错信息为duplicatekey,意思是保存数据时,主键id重复,这些id是雪花算法生成的。从逻辑上讲,雪花算法生成的ID是唯一的ID,不应该是重复的ID。你可以先猜猜是什么原因。可能有同学对雪花算法不熟悉,这里简单介绍一下。(熟悉的同学可以跳到第二段)1.雪花算法snowflake(雪花算法):推特开源的分布式id生成算法,64位长型id,分为4部分:雪花算法1位:无,统一为041位:毫秒时间戳,可以表示69年的时间。10位:5位代表机房id,5位代表机器id。最多可以代表32个机房,每个机房最多可以代表32台机器。12位:同一毫秒内的id,最多4096个不同的id,自增方式的优点:毫秒数在高位,自增序列在低位,整个ID呈上升趋势。不依赖数据库等第三方系统,以服务形式部署,稳定性更高,生成ID的性能更高。比特可以根据自己的业务特点进行分配,非常灵活。缺点:对机器时钟依赖性强,如果拨回机器时钟(可以搜索2017年闰秒7:59:60找到相关issue),会导致重复发号或业务中断不可用。闰秒是通过在“世界标准时间”上加(或减)1秒使其更接近“太阳时”。例如,当两者相差超过0.9秒时,在23:59:59和00:00:00之间插入一个不存在的“23:59:60”,使时间减慢一秒钟。看完上面对雪花算法的简单介绍,大家一定能猜出一二。雪花算法跟时间强相关,其中41位是当前时间的时间戳,那么跟时间有关系吗?2.疑难解答2.1雪花算法有什么问题?既然是Snowflake算法的问题,那我们看看Snowflake算法有什么问题:(1)What:Snowflake算法产生了重复的ID。这些ID是什么样的?(2)Why:雪花算法为什么会产生重复的key第一个问题,我们从报错信息中可以发现,重复的ID是-1,很奇怪。一般雪花算法生成的唯一ID如下,我分别用二进制和十进制表示:十进制表示:2097167233578045440二进制表示:00011101000110101010001001111100110110000000001000010000s算法0中的SnowItemfoundtheSnowItemfound00flakesalgorithm0使用00Tool类,在生成ID时有一个判断逻辑:当当前时间小于上次生成时间时,返回-1,所以问题出在这个逻辑上。(有些雪花算法直接抛出异常)if(timestamp此外,时间服务器会将时间同步到NTPPool,NTPPool正在为全球数百万个系统提供服务。它是大多数主要Linux发行版和许多网络设备的默认“时间服务器”。(参考ntppool.org)问题是NTP同步有问题??2.4时钟不同步。我们检查了服务器上的时间,确实与时钟服务器不同步,早了几分钟。当我们执行NTP同步命令时,时钟又被同步了一次,也就是时间拨回了。同步命令如下:ntpdate出事前,我们重启了服务器1,我们推测是服务器重启后,由于网络问题导致服务器没有正常同步。在下一次定时同步操作之前的时间段,我们的后台服务已经出现了大量重复ID导致的异常问题。NTP时钟回调的零星现象并不常见,但是时钟回调确实带来了很多问题,比如跑秒问题也会造成1s时间回调。为了防止这种情况发生,网上也有一些开源的解决方案。3、解决方案(一)方法一:使用美团Leaf基于雪花算法的解决方案。(2)方法二:使用百度UidGenerator,基于雪花算法。(3)方法三:使用Redis生成自增分布式ID。缺点是ID容易被猜到,存在安全隐患。3.1美团的Leaf方案美团开源项目Leaf的方案:使用依赖ZooKeeper的数据存储。如果时钟回调时间超过最大容忍毫秒阈值,程序会报错;如果在可容忍范围内,Leaf会等待时钟同步到上次生成主键的时间,然后再继续工作。重点是等待时钟同步!3.2百度UidGenerator方案百度UidGenerator方案并不是每次获取ID都实时计算分布式ID,而是利用RingBuffer数据结构通过缓存预生成一批唯一ID列表,下次获取时通过incrementAndGet()方法,从而摆脱对服务器时间的依赖,也不会有时钟回调的问题。重点是预先生成一批ID!Github地址:https://github.com/baidu/uid-generator4.总结本文通过一次偶然的生产事故,介绍了雪花算法的原理,雪花算法的不足,以及相应的开源解决方案。雪花算法在很大程度上依赖于服务器的时钟。如果时钟拨回,会导致很多问题。我们的系统虽然实现了NTP时钟同步,但并不是100%可靠,跑秒的场景出现过多次。对此,美团和百度也有相应的解决方案。最后,我们的生产环境也是第一次遇到NTP引起的时钟回调,而且系统中使用雪花算法的地方不多,所以目前没有采用以上的替代方案。Snowflake算法的代码已经上传到Gitlab:https://github.com/Jackson0714/PassJava-Platform/blob/master/passjava-common/src/main/java/com/jackson0714/passjava/common/utils/SnowflakeUtilV2.java参考资料:https://time.geekbang.org/dailylesson/detail/100075739https://blog.csdn.net/liangcsdn111/article/details/126103041https://www.jianshu.com/p/291110ca60fc