UidGenerator是百度开源的Java语言实现,基于Snowflake算法的惟一ID生成器。并且,它很是适合虚拟环境,好比:Docker。另外,它经过消费将来时间克服了雪花算法的并发限制。UidGenerator提早生成ID并缓存在RingBuffer中。 压测结果显示,单个实例的QPS能超过6000,000。依赖环境:node
雪花算法几个核心部分: 算法
百度分布式id生成器作了修改: 数据库
时间部分是28位,意味着默认只能承受8.5年(2^28-1/86400/365)。根据不一样业务需求,能够适当调整delta seconds,worker node id和sequence占用位数。 UidGenerator提供两种方式:DefaultUidGenerator 和 CachedUidGenerator 。数组
delta seconds缓存
指的是当前时间和epoch时间的时间差,单位位秒。epoch时间指的是UidGenerator生成分布式ID服务第一次上线的时间,可配置,也必定要根据你的上线时间进行配置,由于默认的epoch时间但是2016-09-20,不配置的话,会浪费好几年的可用时间。安全
worker id服务器
看下worker id是如何赋值的,先建立一个表:数据结构
DROP TABLE IF EXISTS WORKER_NODE; CREATE TABLE WORKER_NODE( ID BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY , 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 DATETIME NOT NULL COMMENT 'modified time', CREATED DATEIMTE NOT NULL COMMENT 'created time' ) COMMENT='DB WorkerID Assigner for UID Generator',ENGINE = INNODB;
分布式ID的实例启动的时候,往这个表中插入一行数据,获得的id值就是准备赋给workerId的值。因为workerId默认22位,那么,集成UidGenerator生成分布式ID的全部实例重启次数是不容许超过4194303次(即2^22-1),不然会抛出异常。 固然也能够自定义生成workerid的方式。并发
**sequence **异步
关键点实现:
protected synchronized long nextId() { long currentSecond = getCurrentSecond(); if (currentSecond < lastSecond) { long refusedSeconds = lastSecond - currentSecond; throw new UidGenerateException("Clock moved backwards. Refusing for %d seconds", refusedSeconds); } if (currentSecond == lastSecond) { sequence = (sequence + 1) & bitsAllocator.getMaxSequence(); if (sequence == 0) { currentSecond = getNextSecond(lastSecond); } } else { sequence = 0L; } lastSecond = currentSecond; return bitsAllocator.allocate(currentSecond - epochSeconds, workerId, sequence); }
总结
时钟回拨的处理比较简单粗暴。另外若是使用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是UidGenerator的重要改进实现。它的核心利用了RingBuffer,以下图所示,它本质上是一个数组,数组中每一个项被称为slot。UidGenerator设计了两个RingBuffer,一个保存惟一ID,一个保存flag。RingBuffer的尺寸是2^n,n必须是正整数:
RingBuffer Of Flag
保存flag这个RingBuffer的每一个slot的值都是0或者1,0是CAN_PUT_FLAG的标志位,1是CAN_TAKE_FLAG的标识位。每一个slot的状态要么是CAN_PUT,要么是CAN_TAKE。以某个slot的值为例,初始值为0,即CAN_PUT。接下来会初始化填满这个RingBuffer,这时候这个slot的值就是1,即CAN_TAKE。等获取分布式ID时取到这个slot的值后,这个slot的值又变为0,以此类推。
RingBuffer Of UID
保存惟一ID的RingBuffer有两个指针,Tail指针和Cursor指针。
Tail指针表示最后一个生成的惟一ID。若是这个指针追上了Cursor指针,意味着RingBuffer已经满了。这时候,不容许再继续生成ID了。用户能够经过属性rejectedPutBufferHandler指定处理这种状况的策略。
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。这个过程主要工做有:
说明:第二步的异步线程实现很是重要,也是UidGenerator解决时钟回拨的关键:在知足填充新的惟一ID条件时,经过时间值递增获得新的时间值(lastSecond.incrementAndGet()),而不是System.currentTimeMillis()这种方式,而lastSecond是AtomicLong类型,因此能保证线程安全问题。
取值
RingBuffer初始化有值后,接下来的取值就简单了。不过,因为分布式ID都保存在RingBuffer中,取值过程当中就会有一些逻辑判断:
经过上面对UidGenerator的分析可知,CachedUidGenerator方式主要经过采起以下一些措施和方案规避了时钟回拨问题和加强惟一性: