![1dc6d106f5c2e8af51f5e1ef4be5e7ef.gif](https://i-blog.csdnimg.cn/blog_migrate/3e841757c5ab663c7f15b5d0f2f97e08.gif)
◀背景▶
对于一套分布式部署的 IM 系统,要求每条消息的 ID 要保证在集群中全局唯一且按生成时间有序排列。如何快速高效的生成消息数据的唯一 ID ,是影响系统吞吐量的关键因素。那么,融云是如何做到生成全局唯一消息 ID 的呢?
首先需要明确下 ID 生成的核心需求:
1. 全局唯一
2. 有序
◀设计▶
融云消息数据的唯一 ID 长度采用 80 Bit 。每 5 个 Bit ,进行一次 32 进制编码,转换为一个字符,字符取值范围是,( 2 ~ 9 ) 和 ( A ~ B ),其中,已经去掉容易造成肉眼混淆的,数字 0 和 1 ,及字母 O 和 I 。这样,80 Bit 可以转换为 16 个字符,再加上 3 个分隔符( - ),将 16 个字符分为 4 组,最终得到一个 19 字符的唯一 ID 。 这样设计,即可以保证生成的 ID 是有序的,也能方便阅读。
![de3b4d1f2034500821bfe2e2b042896d.png](https://i-blog.csdnimg.cn/blog_migrate/ae3ae2c11825894d85c2f4b06d5673cb.jpeg)
如上图所示,80 Bit 被分为 4 段:
1. 第一段 42 Bit ,用于存放时间戳,最长可表示到 2109 年,足够开发者当前使用了。时间戳数据放在高位,可以保证生成的唯一 ID 是按时间有序的,这个是消息 ID 必须要满足的条件。
2. 第二段 12 Bit ,用于存放自旋转 ID 。我们知道,时间戳的精度是到毫秒的,对于一套亿级 IM 系统来说,同一毫秒内产生多条消息太正常不过了,这个自旋 ID 就是在给落到同一毫秒内的消息进行自增编号。12 Bit 则意味着,同一毫秒内,单台主机中最多可以标识 4096( 2 的 12 次方)条消息。
3. 第三段 4 Bit ,用于标识会话类型。4 Bit ,最多可以标识 16 中会话,足够涵盖单聊、群聊、系统消息、聊天室、客服及公众号等常用会话类型。
4. 第四段 22 Bit ,会话 ID 。如群聊中的群 ID ,聊天室中的聊天室 ID 等。与第三段会话类型组合在一起,可以唯一标识一个会话。其他的一些 ID 生成算法,会预留两段,分别用来标识数据中心编号和主机编号(如 SnowFlake 算法),我们并没有这样做,而是将这两段用来标识会话。这样,ID 生成可以直接融入到业务服务中,且不必关心服务所在的主机,做到无状态扩缩容。
◀实现过程▶
消息 ID 共占 80 Bit ,计算时我们分为两部分,高 64 Bit (记为 highBits )和低 16 Bit (记为 lowBits )。
1. 获取当前系统的时间戳,并赋值给消息 ID 的高 64 Bit ;
![81ffb2b730c8d67a4bc368b335c702ce.png](https://i-blog.csdnimg.cn/blog_migrate/de71e4b0d4aac186bf3595df35e05e5f.jpeg)
2. 获取一个自旋 ID , highBits 左移 12 位,并将自旋 ID 拼接到低 12 位中;
![3df4bc6032864e375a4d243fe3717811.png](https://i-blog.csdnimg.cn/blog_migrate/3402dcf4b8161d276bf76ec75d602d78.jpeg)
其中,自旋 ID 是一个从 0 到 4095 范围内,顺序递增的数,生成规则如下:
![b7942232243b538c11b377174d0e3203.png](https://i-blog.csdnimg.cn/blog_migrate/03ce152e35d4182013fbbcb372d1c33a.jpeg)
3. 上步的 highBits 左移 4 位,将会话类型拼接到低 4 位;
![438755e1f12a6449bf826ca1a64c604f.png](https://i-blog.csdnimg.cn/blog_migrate/847e5d622b15e9872d14bb7862d97681.jpeg)
4. 取会话 ID 哈希值的低 22 位,记为 sessionIdInt ;
![8f4eb13ff27b4c398eca2b02e5911d18.png](https://i-blog.csdnimg.cn/blog_migrate/3ace9a0199b07f4defb76a8c0be6b900.jpeg)
5. highBits 左移 6 位,并将 sessionIdInt 的高 6 位拼接到 highBits 的低 6 位中;
![c4d0bfac6c16b5a5ea26efc67abca370.png](https://i-blog.csdnimg.cn/blog_migrate/8e3d4ed0ba983fd40a23a7b0378d3071.jpeg)
6. 取会话 ID 的低 16 位作为 lowBits ;
![770cf26b8b4e33a80fc032a32fe3a274.png](https://i-blog.csdnimg.cn/blog_migrate/1d009c09b01c3aa8a72f9783eb536b8d.jpeg)
7. highBits 与 lowBits 拼接,得到 80 Bit 的消息 ID 。对其进行 32 进制编码,即可得到唯一消息 ID 。编码规则如下:从左至右,每 5 个 Bit 转换为一个整数,以这个整数作为下标,即可在下表中找到对应的字符。
![56bea59e8d8eb87894288b6e65a990fa.png](https://i-blog.csdnimg.cn/blog_migrate/b84c182928f53edc5421be172c49f2d5.jpeg)
总结:
这种 ID 生成的方式,需要注意保证自旋 ID 的生成是线程安全的。避免在并发情况下,生成出同样的 ID 。另外,此 ID 生成算法,强烈依赖系统时间,如果系统时间被改小,也可能造成 ID 生成重复。