你好,我是悟空。

最近出产环境遇到一个问题:

现象:创立工单、订单等地方,全都创立数据失败。

开始排查:报错信息为duplicate key,意思是保存数据的时分,报主键 id 重复,而这些 id 都是由雪花算法生成的,按道理来说,雪花算法是生成分布式仅有 ID,不应该生成重复的 ID。

咱们能够先猜猜是什么原因。

有的同学可能对雪花算法不熟悉,这儿做个简单的阐明。

一、雪花算法

snowflake(雪花算法):Twitter 开源的分布式 id 生成算法,64 位的 long 型的 id,分为 4 部分:

记一次雪花算法遇到的 生产事故!

  • 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),会导致发号重复或许服务会处于不可用状况。

看了上面的关于雪花算法的简短介绍,想必咱们能猜出个一二了。

雪花算法和时刻是强关联的,其中有 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,这就相当于服务器之前是获取了一个未来时刻,现在突然跳动到当时时刻。

而这种场景咱们称之为时钟回拨时钟跳动

时钟回拨:服务器时钟可能会因为各种原因产生不准,而网络中会供给 NTP 服务来做时刻校准,因而在做校准的时分,服务器时钟就会产生时钟的跳动或许回拨问题。

2.3 时钟同步

那么服务器为什么会产生时钟回拨或跳动呢?

咱们猜测是不是服务器上的时钟不同步后,又自动进行同步了,前后时刻不一致。

首要咱们的每台服务器上都安装了 ntpdate 软件,作为 NTP 客户端,会每隔 10 分钟NTP 时刻服务器同步一次时刻。

如下图所示,服务器 1 和 服务器 2 部署了应用服务,每隔 10 分钟向时刻服务器同步一次时刻,来保证服务器 1 和服务器 2 的时刻和时刻服务器的时刻一致。

记一次雪花算法遇到的 生产事故!

每隔 10 分钟同步的设置:

*/10 * * * * /usr/sbin/ntpdate <ip>

另外时刻服务器会向 NTP Pool同步时刻,NTP Pool 正在为国际各地成百上千万的体系供给服务。 它是绝大多数干流Linux发行版和许多网络设备的默许“时刻服务器”。(参考ntppool.org)

那问题便是 NTP 同步出了问题??

2.4 时钟不同步

咱们到服务器上查看了下时刻,的确和时钟服务器不同步,早了几分钟。

当咱们履行 NTP 同步的指令后,时钟又同步了,也便是说时刻回拨了。

ntpdate  <时钟服务器 IP>

在产生事故之前,咱们重启过服务器 1。咱们估测服务器重启后,服务器因网络问题没有正常同步。而在下一次守时同步操作到来之前的这个时刻段,咱们的后端服务现已呈现了因 ID 重复导致的大量反常问题。

这个 NTP 时钟回拨的偶发现象并不常见,但时钟回拨的确会带了许多问题,比方润秒 问题也会带来 1s 时刻的回拨。

闰秒便是经过给“国际标准时刻”加(或减)1秒,让它更挨近“太阳时”。例如,两者相差超越0.9秒时,就在23点59分59秒与00点00分00秒之间,插入一个原本不存在的“23点59分60秒”,来将时刻调慢一秒钟。

为了防备这种状况的产生,网上也有一些开源解决计划。

三、解决计划

(1)方法一:运用美团 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-generator

四、总结

本篇经过一次偶发的出产事故,引出了雪花算法的原理、雪花算法的缺乏、对应的开源解决计划。

雪花算法强依靠服务器的时钟,假如时钟产生了回拨,就会形成许多问题。

咱们的体系尽管做了 NTP 时钟同步,但也不是 100% 可靠,并且润秒这种场景也是呈现过许多次。鉴于此,美团和百度也有对应的解决计划。

最后,咱们的出产环境也是第一次遇到因 NTP 导致的时钟回拨,并且体系中用到雪花算法的地方并不多,所以目前并没有采取以上的替换计划。

https://github.com/Jackson0714/PassJava-Platform/blob/master/passjava-common/src/main/java/com/jackson0714/passjava/common/utils/SnowflakeUtilV2.java

参考资料:

time.geekbang.org/dailylesson…

blog.csdn.net/liangcsdn11…

www.jianshu.com/p/291110ca6…