来源:http://www.52im.net
哈喽,各位新来的小伙伴们,大家好!由于公众号做了改版,为了保证公众号的资源能准时推送到你手里,大家记得将咱们的公众号 加星标置顶 ,在此真诚的表示感谢~正文如下:
# 引言
很多人一想到IM应用开发,第一印象就是“长连接”、“socket”、“保活”、“协议”这些关键词,没错,这些确实是IM开发中肯定会涉及的技术范畴。
但,当你真正开始编写第一行代码时,最现实的问题实际上是“聊天消息ID该怎么生成?”这个看似微不足道的小事情。说它看似微不足道,是因为在IM里它太平常了,处处可见它的身影。不过,虽然看似微不足道,但实际却很重要,因为它的生成算法和生成策略的优劣在某种意义上来说,决定了你的IM应用层某些功能实现的难易度。
本文专门介绍百度开源的分布式消息ID生成器UidGenerator的算法逻辑、实现思路、重点源码解读等,或许能带给你更多的启发。
# 基本介绍
全局ID(常见的比如:IM聊天系统中的消息ID、电商系统中的订单号、外卖应用中的订单号等)服务是分布式服务中的基础服务,需要保持全局唯一、高效、高可靠性。有些时候还可能要求保持单调,但也并非一定要严格递增或者递减。
全局ID也可以通过数据库的自增主键来获取,但是如果要求QPS很高显然是不现实的。
UidGenerator(备用地址)工程是百度开源的基于Snowflake算法的唯一ID生成器(百度对Snowflake算法进行了改进),引入了高性能队列高性能队列disruptor中RingBuffer思想,进一步提升了效率。
UidGenerator是Java语言实现的,它以组件形式工作在应用项目中,支持自定义workerId位数和初始化策略,,从而适用于docker等虚拟化环境下实例自动重启、漂移等场景。
在技术实现上,UidGenerator有以下关键特性:
1)UidGenerator通过借用未来时间来解决sequence天然存在的并发限制; 2)采用RingBuffer来缓存已生成的UID, 并行化UID的生产和消费; 3)同时对CacheLine补齐,避免了由RingBuffer带来的硬件级「伪共享」问题。 基于以上技术特性,UidGenerator的单机压力测试数据显示,其QPS可高达600万。依赖的环境:
1)Java8及以上版本(代码中使用了函数式编程语句等新特性) 2)MySQL(内置WorkerID分配器, 启动阶段通过DB进行分配; 如自定义实现, 则DB非必选依赖)。以下是UidGenerator工程的相关资源:
1)完整源码地址:https://github.com/baidu/uid-generator 2)备用源码地址:https://github.com/52im/uid-generator 3)源码在线阅读:http://docs.52im.net/extend/docs/src/uid-generator/(推荐)# 什么是Snowflake算法? 1 SnowFlake算法原理 SnowFlake 算法,是 Twitter 开源的分布式 ID 生成算法。其核心思想就是:使用一个 64 bit 的 long 型的数字作为全局唯一 ID。 这 64 个 bit 中,其中 1 个 bit 是不用的,然后用其中的 41 bit 作为毫秒数,用 10 bit 作为工作机器 ID,12 bit 作为序列号。 SnowFlake的ID构成:
![a8f13c4a8e1ffd2779c7955f8b70e97e.png](https://i-blog.csdnimg.cn/blog_migrate/44219f65e40f5cdfc31d15515b2dfa54.png)
![7b5a5e52e28448b5bd81da3df01aebf1.png](https://i-blog.csdnimg.cn/blog_migrate/d66346f72010e7faaf918a860234cca0.jpeg)
1)第一个部分,是 1 个 bit:0,这个是无意义的; 2)第二个部分,是 41 个 bit:表示的是时间戳; 3)第三个部分,是 5 个 bit:表示的是机房 ID,10001; 4)第四个部分,是 5 个 bit:表示的是机器 ID,1 1001; 5)第五个部分,是 12 个 bit:表示的序号,就是某个机房某台机器上这一毫秒内同时生成的 ID 的序号,0000 00000000。① 1 bit:是不用的,为啥呢? 因为二进制里第一个 bit 为如果是 1,那么都是负数,但是我们生成的 ID 都是正数,所以第一个 bit 统一都是 0。 ② 41 bit:表示的是时间戳,单位是毫秒。 41 bit 可以表示的数字多达 2^41 - 1,也就是可以标识 2 ^ 41 - 1 个毫秒值,换算成年就是表示 69 年的时间。 ③ 10 bit:记录工作机器 ID,代表的是这个服务最多可以部署在 2^10 台机器上,也就是 1024 台机器。 但是 10 bit 里 5 个 bit 代表机房 id,5 个 bit 代表机器 ID。意思就是最多代表 2 ^ 5 个机房(32 个机房),每个机房里可以代表 2 ^ 5 个机器(32 台机器)。 ④12 bit:这个是用来记录同一个毫秒内产生的不同 ID。 12 bit 可以代表的最大正整数是 2 ^ 12 - 1 = 4096,也就是说可以用这个 12 bit 代表的数字来区分同一个毫秒内的 4096 个不同的 ID。理论上snowflake方案的QPS约为409.6w/s,这种分配方式可以保证在任何一个IDC的任何一台机器在任意毫秒内生成的ID都是不同的。 简单来说,你的某个服务假设要生成一个全局唯一 ID,那么就可以发送一个请求给部署了 SnowFlake 算法的系统,由这个 SnowFlake 算法系统来生成唯一 ID。
1)这个 SnowFlake 算法系统首先肯定是知道自己所在的机房和机器的,比如机房 ID = 17,机器 ID = 12;
2)接着 SnowFlake 算法系统接收到这个请求之后,首先就会用二进制位运算的方式生成一个 64 bit 的 long 型 ID,64 个 bit 中的第一个 bit 是无意义的;
3)接着 41 个 bit,就可以用当前时间戳(单位到毫秒),然后接着 5 个 bit 设置上这个机房 id,还有 5 个 bit 设置上机器 ID;
4)最后再判断一下,当前这台机房的这台机器上这一毫秒内,这是第几个请求,给这次生成 ID 的请求累加一个序号,作为最后的 12 个 bit。
1)毫秒数在高位,自增序列在低位,整个ID都是趋势递增的; 2)不依赖数据库等第三方系统,以服务的方式部署,稳定性更高,生成ID的性能也是非常高的; 3)可以根据自身业务特性分配bit位,非常灵活。► 缺点:
强依赖机器时钟,如果机器上时钟回拨,会导致发号重复或者服务会处于不可用状态。# UidGenerator改进后的SnowFlake算法 通过上节,我们知道了原版SnowFlake算法的基本构成。 具体是,原版SnowFlake算法核心组成:
![a8f13c4a8e1ffd2779c7955f8b70e97e.png](https://i-blog.csdnimg.cn/blog_migrate/44219f65e40f5cdfc31d15515b2dfa54.png)
1)1位sign标识位; 2)41位时间戳; 3)10位workId(即5位数据中心id+5位工作机器id); 4)12位自增序列。而UidGenerator改进后的SnowFlake算法核心组成如下图:
![9fbc87cd5a05d9f08d9b7d5fc0a8c312.png](https://i-blog.csdnimg.cn/blog_migrate/690b74d44f301484c477275b05667980.png)
1)sign(1bit):固定1bit符号标识,即生成的UID为正数。
2)delta seconds (28 bits):当前时间,相对于时间基点"2016-05-20"的增量值,单位:秒,最多可支持约8.7年(注意:(a)这里的单位是秒,而不是毫秒!(b)注意这里的用词,是“最多”可支持8.7年,为什么是“最多”,后面会讲)。
3)worker id (22 bits):机器id,最多可支持约420w次机器启动。内置实现为在启动时由数据库分配,默认分配策略为用后即弃,后续可提供复用策略。
4)sequence (13 bits):每秒下的并发序列,13 bits可支持每秒8192个并发(注意下这个地方,默认支持qps最大为8192个)。
DROP DATABASE IF EXISTS `xxxx`;
CREATE DATABASE `xxxx` ;
use `xxxx`;
DROP TABLE IF EXISTS WORKER_NODE;
CREATE TABLE WORKER_NODE
(
ID BIGINT NOT NULL AUTO_INCREMENT COMMENT 'auto increment id',
HOST_NAME VARCHAR(64) NOT NULL COMMENT 'host name',
PORT VARCHAR(64) NOT NULL COMMENT 'port',
TYPE INT NOT NULL COMMENT 'node type: ACTUAL or CONTAINER',
LAUNCH_DATE DATE NOT NULL COMMENT 'launch date',
MODIFIED TIMESTAMP NOT NULL COMMENT 'modified time',
CREATED TIMESTAMP NOT NULL COMMENT 'created time',
PRIMARY KEY(ID)
)
COMMENT='DB WorkerID Assigner for UID Generator', ENGINE = INNODB;
DefaultUidGenerator会在集成用它生成分布式ID的实例启动的时候,往这个表中插入一行数据,得到的id值就是准备赋给workerId的值。由于workerId默认22位,那么,集成DefaultUidGenerator生成分布式ID的所有实例重启次数是不允许超过4194303次(即2^22-1),否则会抛出异常。
3)sequence(13bits):
核心代码如下,几个实现的关键点:
a. synchronized保证线程安全; b. 如果时间有任何的回拨,那么直接抛出异常; c. 如果当前时间和上一次是同一秒时间,那么sequence自增。如果同一秒内自增值超过2^13-1,那么就会自旋等待下一秒(getNextSecond); d. 如果是新的一秒,那么sequence重新从0开始。
![ef1d318003b54f36536b1b9bdafa47a9.png](https://i-blog.csdnimg.cn/blog_migrate/eebf7750f06aa210c1b0c2e465c93779.png)
CachedUidGenerator
CachedUidGenerator是DefaultUidGenerator的重要改进实现。它的核心利用了RingBuffer,它本质上是一个数组,数组中每个项被称为slot。
CachedUidGenerator设计了两个RingBuffer,一个保存唯一ID,一个保存flag。RingBuffer的尺寸是2^n,n必须是正整数。
以下是CachedUidGenerator中的RingBuffer原理示意图:
![3e3455642c90c42bd0ab3ef2a07bd3b7.png](https://i-blog.csdnimg.cn/blog_migrate/83ef59594e19c818ab87af0f03526f48.png)
1)自增列:CachedUidGenerator的workerId在实例每次重启时初始化,且就是数据库的自增ID,从而完美的实现每个实例获取到的workerId不会有任何冲突;
2)RingBuffer:CachedUidGenerator不再在每次取ID时都实时计算分布式ID,而是利用RingBuffer数据结构预先生成若干个分布式ID并保存;
3)时间递增:传统的SnowFlake算法实现都是通过System.currentTimeMillis()来获取时间并与上一次时间进行比较,这样的实现严重依赖服务器的时间。而CachedUidGenerator的时间类型是AtomicLong,且通过incrementAndGet()方法获取下一次的时间,从而脱离了对服务器时间的依赖,也就不会有时钟回拨的问题(这种做法也有一个小问题,即分布式ID中的时间信息可能并不是这个ID真正产生的时间点,例如:获取的某分布式ID的值为3200169789968523265,它的反解析结果为{"timestamp":"2019-05-02 23:26:39","workerId":"21","sequence":"1"},但是这个ID可能并不是在"2019-05-02 23:26:39"这个时间产生的)。
![a35991dc20b9f536c77a480a81699a21.png](https://i-blog.csdnimg.cn/blog_migrate/7abee1b743284049ad06f272bdf7297b.png)
![cf6b5bd3d9d091445faaea6e4ee6549d.png](https://i-blog.csdnimg.cn/blog_migrate/b39650bab9ba5a093e98b7c601a6817c.png)
![3d3376a59a93c1cb511ad2acc02819cb.png](https://i-blog.csdnimg.cn/blog_migrate/8ff49df7cb4aa7cffb0bc8aaf7fc668d.png)
[1] 改进版Snowflake全局ID生成器-uid-generator [2] UidGenerator [3] 百度开源分布式id生成器uid-generator源码剖
热门推荐:
- 卧槽!竟然有小姐姐盗用他人设计作品找工作,还成功入职某大厂~
- 免翻、免翻!懂的进~
- Zuul网关到底有何牛逼之处?竟然这么多人在用~
![523b52a03f330f7ac3bf7d446337c21e.png](https://i-blog.csdnimg.cn/blog_migrate/149ff3ad566eac4839cb05799d914031.png)
![28e74d3a6fadd0e0bbd06d4a285a8660.png](https://i-blog.csdnimg.cn/blog_migrate/7c27364b998c4c1944bb31b4a7f2d18f.jpeg)
![db57ca17138d2a1595e4345ebf9f4d45.gif](https://i-blog.csdnimg.cn/blog_migrate/01048b2e26eb77875f8b8927d69cdd93.gif)