记一次“雪花算法”造成的生产事故的排查记录( 三 )


来源:本文主要内容如下:

记一次“雪花算法”造成的生产事故的排查记录

文章插图
 
前言
最近生产环境遇到一个问题:
现象:创建工单、订单等地方 , 全都创建数据失败 。
初步排查:报错信息为duplicate key , 意思是保存数据的时候 , 报主键 id 重复 , 而这些 id 都是由雪花算法生成的 , 按道理来说 , 雪花算法生成的 ID 是唯一 ID , 不应该出现重复的 ID 。
大家可以先猜猜是什么原因 。
有的同学可能对雪花算法不熟悉 , 这里做个简单的说明 。(熟悉的同学可以跳到第二个段落)
一、雪花算法
snowflake(雪花算法):Twitter 开源的分布式 id 生成算法 , 64 位的 long 型的 id , 分为 4 部分:
记一次“雪花算法”造成的生产事故的排查记录

文章插图
 
snowflake 算法
 
  • 1 bit:不用 , 统一为 0
  • 41 bits:毫秒时间戳 , 可以表示 69 年的时间 。
  • 10 bits:5 bits 代表机房 id , 5 个 bits 代表机器 id 。最多代表 32 个机房 , 每个机房最多代表 32 台机器 。
  • 12 bits:同一毫秒内的 id , 最多 4096 个不同 id , 自增模式
 
优点:
 
  • 毫秒数在高位 , 自增序列在低位 , 整个ID都是趋势递增的 。
  • 不依赖数据库等第三方系统 , 以服务的方式部署 , 稳定性更高 , 生成ID的性能也是非常高的 。
  • 可以根据自身业务特性分配bit位 , 非常灵活 。
 
缺点:
 
  • 强依赖机器时钟 , 如果机器上时钟回拨(可以搜索 2017 年闰秒 7:59:60找到相关问题) , 会导致发号重复或者服务会处于不可用状态 。
 
闰秒就是通过给“世界标准时间”加(或减)1秒 , 让它更接近“太阳时” 。例如 , 两者相差超过0.9秒时 , 就在23点59分59秒与00点00分00秒之间 , 插入一个原本不存在的“23点59分60秒” , 来将时间调慢一秒钟 。
看了上面的关于雪花算法的简短介绍 , 想必大家能猜出个一二了 。
雪花算法和时间是强关联的 , 其中有 41 位是当前时间的时间戳 , 那么会不会和时间有关?
二、排查 2.1 雪花算法有什么问题?
既然是雪花算法的问题 , 那我们就来看下雪花算法出了什么问题:
(1)What:雪花算法生成了重复的 ID , 这些 ID 是什么样的?
(2)Why:雪花算法为什么生成了重复的 key
第一个问题 , 我们可以通过报错信息发现 , 这个重复的 ID 是 -1 , 这个就很奇怪了 。一般雪花算法生成的唯一 ID 如下所示 , 我分别用二进制和十进制来表示:
十进制表示:2097167233578045440 二进制表示:0001 1101 0001 1010 1010 0010 0111 1100 1101 1000 0000 0010 0001 0000 0000 0000
找到项目中使用雪花算法的工具类 , 生成 ID 的时候有个判断逻辑:
 
当当前时间小于上次的生成时间就会返回 -1 , 所以问题就出在这个逻辑上面 。(有的雪花算法是直接抛异常)
if (timestamp < this.lastTimestamp) { return -1; } 
记一次“雪花算法”造成的生产事故的排查记录

文章插图
 
由于每次 timestamp 都是小于 lastTimeStamp , 所以每次都返回了 -1 , 这也解释了为什么生成了重复的 key 。
2.2 时钟回拨或跳跃
那么问题就聚焦在为什么当前时间还会小于上次的生成时间 。
下面有种场景可能发生这种情况:
首先假定当前的北京时间是 9:00:00 。另外上次生成 ID 的时候 , 服务器获取的时间 lastTimestamp=10:00:00 , 而现在服务器获取的当前时间 timestamp=09:00:00 , 这就相当于服务器之前是获取了一个未来时间 , 现在突然跳跃到当前时间 。


推荐阅读