百度uid-generator https://github.com/baidu/uid-generator

UidGenerator是百度开源的Java语言实现,基于Snowflake算法的唯一ID生成器。而且,它非常适合虚拟环境,比如:Docker。另外,它通过消费未来时间克服了雪花算法的并发限制。UidGenerator提前生成ID并缓存在RingBuffer中。 压测结果显示,单个实例的QPS能超过6000,000。

依赖环境:

  • JDK8+

  • MySQL(用于分配WorkerId)

snowflake

由下图可知,雪花算法的几个核心组成部分:

  • 1位sign标识位;

  • 41位时间戳;

  • 10位workId(数据中心+工作机器,可以其他组成方式);

  • 12位自增序列;

但是百度对这些组成部分稍微调整了一下:

由上图可知,UidGenerator的时间部分只有28位,这就意味着UidGenerator默认只能承受8.5年(2^28-1/86400/365)。当然,根据你业务的需求,UidGenerator可以适当调整delta seconds、worker node id和sequence占用位数。

接下来分析百度UidGenerator的实现。需要说明的是UidGenerator有两种方式提供:和DefaultUidGenerator和CachedUidGenerator。我们先分析比较容易理解的DefaultUidGenerator。

DefaultUidGenerator

delta seconds

这个值是指当前时间与epoch时间的时间差,且单位为秒。epoch时间就是指集成UidGenerator生成分布式ID服务第一次上线的时间,可配置,也一定要根据你的上线时间进行配置,因为默认的epoch时间可是2016-09-20,不配置的话,会浪费好几年的可用时间。

worker id

接下来说一下UidGenerator是如何给worker id赋值的,搭建UidGenerator的话,需要创建一个表:

UidGenerator会在集成用它生成分布式ID的实例启动的时候,往这个表中插入一行数据,得到的id值就是准备赋给workerId的值。由于workerId默认22位,那么,集成UidGenerator生成分布式ID的所有实例重启次数是不允许超过4194303次(即2^22-1),否则会抛出异常。

这段逻辑的核心代码来自DisposableWorkerIdAssigner.java中,当然,你也可以实现WorkerIdAssigner.java接口,自定义生成workerId。

sequence

核心代码如下,几个实现的关键点:

  • synchronized保证线程安全;

  • 如果时间有任何的回拨,那么直接抛出异常;

  • 如果当前时间和上一次是同一秒时间,那么sequence自增。如果同一秒内自增值超过2^13-1,那么就会自旋等待下一秒(getNextSecond);

  • 如果是新的一秒,那么sequence重新从0开始;

/*** Get UID** @return UID* @throws UidGenerateException in the case: Clock moved backwards; Exceeds the max timestamp*/protected synchronized long nextId() {long currentSecond = getCurrentSecond();// Clock moved backwards, refuse to generate uidif (currentSecond < lastSecond) {long refusedSeconds = lastSecond - currentSecond;throw new UidGenerateException("Clock moved backwards. Refusing for %d seconds", refusedSeconds);}// At the same second, increase sequenceif (currentSecond == lastSecond) {sequence = (sequence + 1) & bitsAllocator.getMaxSequence();// Exceed the max sequence, we wait the next second to generate uidif (sequence == 0) {currentSecond = getNextSecond(lastSecond);}// At the different second, sequence restart from zero} else {sequence = 0L;}lastSecond = currentSecond;// Allocate bits for UIDreturn bitsAllocator.allocate(currentSecond - epochSeconds, workerId, sequence);}

总结

通过DefaultUidGenerator的实现可知,它对时钟回拨的处理比较简单粗暴。另外如果使用UidGenerator的DefaultUidGenerator方式生成分布式ID,一定要根据你的业务的情况和特点,调整各个字段占用的位数:

<property name="timeBits" value="28"/><property name="workerBits" value="22"/><property name="seqBits" value="13"/><property name="epochStr" value="2016-09-20"/>

CachedUidGenerator

CachedUidGenerator是UidGenerator的重要改进实现。它的核心利用了RingBuffer,如下图所示,它本质上是一个数组,数组中每个项被称为slot。UidGenerator设计了两个RingBuffer,一个保存唯一ID,一个保存flag。RingBuffer的尺寸是2^n,n必须是正整数:

RingBuffer Of Flag

其中,保存flag这个RingBuffer的每个slot的值都是0或者1,0是CANPUTFLAG的标志位,1是CANTAKEFLAG的标识位。每个slot的状态要么是CANPUT,要么是CANTAKE。以某个slot的值为例,初始值为0,即CANPUT。接下来会初始化填满这个RingBuffer,这时候这个slot的值就是1,即CANTAKE。等获取分布式ID时取到这个slot的值后,这个slot的值又变为0,以此类推。

RingBuffer Of UID

保存唯一ID的RingBuffer有两个指针,Tail指针和Cursor指针。

  1. Tail指针表示最后一个生成的唯一ID。如果这个指针追上了Cursor指针,意味着RingBuffer已经满了。这时候,不允许再继续生成ID了。用户可以通过属性rejectedPutBufferHandler指定处理这种情况的策略。

  2. Cursor指针表示最后一个已经给消费的唯一ID。如果Cursor指针追上了Tail指针,意味着RingBuffer已经空了。这时候,不允许再继续获取ID了。用户可以通过属性rejectedTakeBufferHandler指定处理这种异常情况的策略。

另外,如果你想增强RingBuffer提升它的吞吐能力,那么需要配置一个更大的boostPower值:

<!-- RingBuffer size扩容参数, 可提高UID生成能力.即每秒产生ID数上限能力 --><!-- 默认:3,原bufferSize=2^13, 扩容后bufferSize = 2^13 << 3 = 65536 --><property name="boostPower" value="3"/>

CachedUidGenerator的理论讲完后,接下来就是它具体是如何实现的了,我们首先看它的申明,它是继承了DefaultUidGenerator,所以,它事实上就是对DefaultUidGenerator的增强:

public class CachedUidGenerator extends DefaultUidGenerator implements DisposableBean {... ...}

worker id

CachedUidGenerator的workerId实现继承自它的父类DefaultUidGenerator,即实例启动时往表WORKER_NODE插入数据后得到的自增ID值。

接下来深入解读CachedUidGenerator的核心操作,即对RingBuffer的操作,包括初始化、取分布式唯一ID、填充分布式唯一ID等。

初始化

CachedUidGenerator在初始化时除了给workerId赋值,还会初始化RingBuffer。这个过程主要工作有:

  1. 根据boostPower的值确定RingBuffer的size;

  2. 构造RingBuffer,默认paddingFactor为50。这个值的意思是当RingBuffer中剩余可用ID数量少于50%的时候,就会触发一个异步线程往RingBuffer中填充新的唯一ID(调用BufferPaddingExecutor中的paddingBuffer()方法,这个线程中会有一个标志位running控制并发问题),直到填满为止;

  3. 判断是否配置了属性scheduleInterval,这是另外一种RingBuffer填充机制, 在Schedule线程中, 周期性检查填充。默认:不配置, 即不使用Schedule线程. 如需使用, 请指定Schedule线程时间间隔, 单位:秒;

  4. 初始化Put操作拒绝策略,对应属性rejectedPutBufferHandler。即当RingBuffer已满, 无法继续填充时的操作策略。默认无需指定, 将丢弃Put操作, 仅日志记录. 如有特殊需求, 请实现RejectedPutBufferHandler接口(支持Lambda表达式);

  5. 初始化Take操作拒绝策略,对应属性rejectedTakeBufferHandler。即当环已空, 无法继续获取时的操作策略。默认无需指定, 将记录日志, 并抛出UidGenerateException异常. 如有特殊需求, 请实现RejectedTakeBufferHandler接口;

  6. 初始化填满RingBuffer中所有slot(即塞满唯一ID,这一步和第2步骤一样都是调用BufferPaddingExecutor中的paddingBuffer()方法);

  7. 开启buffer补丁线程(前提是配置了属性scheduleInterval),原理就是利用ScheduledExecutorService的scheduleWithFixedDelay()方法。

说明:第二步的异步线程实现非常重要,也是UidGenerator解决时钟回拨的关键:在满足填充新的唯一ID条件时,通过时间值递增得到新的时间值(lastSecond.incrementAndGet()),而不是System.currentTimeMillis()这种方式,而lastSecond是AtomicLong类型,所以能保证线程安全问题。

取值

RingBuffer初始化有值后,接下来的取值就简单了。不过,由于分布式ID都保存在RingBuffer中,取值过程中就会有一些逻辑判断:

  1. 如果剩余可用ID百分比低于paddingFactor参数指定值,就会异步生成若干个ID集合,直到将RingBuffer填满。

  2. 如果获取值的位置追上了tail指针,就会执行Task操作的拒绝策略。

  3. 获取slot中的分布式ID。

  4. 将这个slot的标志位只为CANPUTFLAG。

总结

通过上面对UidGenerator的分析可知,CachedUidGenerator方式主要通过采取如下一些措施和方案规避了时钟回拨问题和增强唯一性:

  • 自增列:UidGenerator的workerId在实例每次重启时初始化,且就是数据库的自增ID,从而完美的实现每个实例获取到的workerId不会有任何冲突。

  • RingBuffer:UidGenerator不再在每次取ID时都实时计算分布式ID,而是利用RingBuffer数据结构预先生成若干个分布式ID并保存。

  • 时间递增:传统的雪花算法实现都是通过System.currentTimeMillis()来获取时间并与上一次时间进行比较,这样的实现严重依赖服务器的时间。而UidGenerator的时间类型是AtomicLong,且通过incrementAndGet()方法获取下一次的时间,从而脱离了对服务器时间的依赖,也就不会有时钟回拨的问题(这种做法也有一个小问题,即分布式ID中的时间信息可能并不是这个ID真正产生的时间点,例如:获取的某分布式ID的值为3200169789968523265,它的反解析结果为{"timestamp":"2019-05-02 23:26:39","workerId":"21","sequence":"1"},但是这个ID可能并不是在"2019-05-02 23:26:39"这个时间产生的)。

参考: 百度开源的分布式唯一ID生成器UidGenerator,解决了时钟回拨问题

百度uid-generator源码

架构系列---百度分布式唯一ID生成器UidGenerator解析相关推荐

  1. 百度开源的分布式唯一ID生成器UidGenerator,解决了时钟回拨问题

    转载自   百度开源的分布式唯一ID生成器UidGenerator,解决了时钟回拨问题 UidGenerator是百度开源的Java语言实现,基于Snowflake算法的唯一ID生成器.而且,它非常适 ...

  2. 分布式全局唯一id实现-2 springCloud-MyBatis-Plus集成百度分布式全局id(uid-generator)

    前言:MyBatis-Plus 集成百度的uid-generator ,实现业务实体在insert 实体时,可以自动获取全局id,完成数据保存: 1 uid-generator 全局id 生成的方式了 ...

  3. 时钟回拨问题咋解决?百度开源的唯一ID生成器UidGenerator

    时钟回拨问题咋解决?百度开源的唯一ID生成器UidGenerator 参考文章: (1)时钟回拨问题咋解决?百度开源的唯一ID生成器UidGenerator (2)https://www.cnblog ...

  4. php id 生产器,Laravel 分布式唯一 ID 生成器运用_PHP开发框架教程

    在运用程序中,常常须要全局唯一的ID作为数据库主键.怎样生成全局唯一ID? 起首,须要肯定全局唯一ID是整型照样字符串?假如是字符串,那末现有的UUID就完整满足需求,不须要分外的事情.瑕玷是字符串作 ...

  5. 分布式全局唯一id实现-2.1 springCloud-MyBatis-Plus集成百度分布式全局id(uid-generator)--优化版

    前言:在上一篇 springCloud-MyBatis-Plus集成百度分布式全局id, id的生成全部交予程序实现,虽然可以通过集群的方式来提高id 生成服务的高可用性,但是依然需要考虑极端情况,在 ...

  6. 分布式唯一ID生成器

    在应用程序中,经常需要全局唯一的ID作为数据库主键.如何生成全局唯一ID? 首先,需要确定全局唯一ID是整型还是字符串?如果是字符串,那么现有的UUID就完全满足需求,不需要额外的工作.缺点是字符串作 ...

  7. springboot持续集成百度唯一id生成器(UidGenerator)

    springboot集成UidGenerator 集成UidGenerator 第一步:拉去开源项目,打成jar包 第二步:导包 第三步:创建数据库表结构 第四步:配置数据库信息以及mapper扫描 ...

  8. 融云发送图片消息_IM消息ID技术专题(五):开源分布式ID生成器UidGenerator的技术实现...

    1.引言 很多人一想到IM应用开发,第一印象就是"长连接"."socket"."保活"."协议"这些关键词,没错,这些确 ...

  9. IM消息ID技术专题(五):开源分布式ID生成器UidGenerator的技术实现

    1.引言 很多人一想到IM应用开发,第一印象就是"长连接"."socket"."保活"."协议"这些关键词,没错,这些确 ...

  10. 开源分布式ID生成器UidGenerator的技术实现

    1.引言 很多人一想到IM应用开发,第一印象就是"长连接"."socket"."保活"."协议"这些关键词,没错,这些确 ...

最新文章

  1. CodeForces - 632E Thief in a Shop 完全背包
  2. 关于VS打包程序无法弹出主界面的问题
  3. 前端学习(3195):虚拟dom的创建方式1的js
  4. (pytorch-深度学习系列)正向传播与反向传播-学习笔记
  5. json为全局变量 vue_Vue-cli开发笔记二----------接口调用、配置全局变量
  6. barbuttonitem 文字换行_ios开发 常用代码整理
  7. 数据结构之内部排序三
  8. Python之数据分析(Numpy中读取与保存数据文件、将数据文件制成K线图)
  9. 2020受欢迎的20个JavaScript 库
  10. maven坐标Dependencies和Exclusions详解
  11. Packets 1037A(二进制数)
  12. 08_Flask ORM 利用SQLAlchemy进行数据库增删改查
  13. OpenRefine中的正则表达式
  14. 软考信息安全工程师知识总结
  15. 词频统计 matlab,5年cvpr论文词频分析,今年最火词——深度学习
  16. 什么是数据工程师,数据工程师主要是做什么的?
  17. Xilinx 7A 开发流程——工程模式 ARTY XC7A35T
  18. 运用java爬虫和python做词云图
  19. 使用element ui + vue时出现vue.runtime.esm.js:1737 TypeError: Cannot read property ‘$options‘ of undefined
  20. 基于MATLAB的人体行为检测与识别

热门文章

  1. 5G NR LDPC码(3)—— DL-SCH和UL-SCH的LDPC处理(含代码)
  2. 03-Kubernetes中的Deployment
  3. 0基础看-最大似然函数,原理,基本概念,例子
  4. 免费使用QQ邮箱“邮件短信通知”服务
  5. 1078 字符串压缩与解压
  6. 怎么判断私网地址_判断本机IP地址是公网地址还是私网地址
  7. 默认关闭idea2020的注释doc的rendered view模式
  8. navigator、history对象
  9. python:24点游戏
  10. java fmail发送邮件_【已实测通过】JavaMail常用的几种邮件发送方式