UidGenerator
是Java实现的,提供了两种生成器: DefaultUidGenerator、CachedUidGenerator。
如对UID生成性能有要求, 请使用CachedUidGenerator,支持缓存生成的id。
DefaultUidGenerator的原理是基于Snowflake算法,它使用了时间戳、机器ID和序列号来生成唯一的ID。其中,
时间戳用于保证ID的唯一性和有序性,机器ID用于区分不同的机器,序列号用于解决同一毫秒内并发生成ID的问题。单个实例的QPS能超过6000000。需要的环境:JDK8+,Mysql(用于分配WorkerId)。
优点:1.克服了雪花算法的并发限制,通过借用未来时间来解决squenece天然存在的并发限制。
2. 高性能:支持每秒生成数百万个ID,满足高并发场景的需求,单个实例的QPS能超过6000000。
3. 高可用性:支持多节点部署,即使某个节点宕机也不会影响整个系统的正常运行。
4. 易于使用:提供了简单易用的API,可以快速集成到现有系统中。
5. 可定制化:支持自定义机器ID和序列号的生成方式,可以根据实际需求进行定制。
缺点:1.趋势自增。
2.依赖Mysql做workerId分发,使用Mysql自增Id做workId,用后即弃。
3.UidGenerator的时间部分只有28位,意味着UidGenerator默认只能承受8.5年(2^28-1/86400/365)。
在项目使用的数据库里,执行WORKER_NODE表脚本
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: CONTAINER(1), ACTUAL(2), FAKE(3)',
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;
maven的pom文件里引入依赖
<dependencies>
<dependency>
<groupId>com.github.wujun234</groupId>
<artifactId>uid-generator-spring-boot-starter</artifactId>
<version>1.0.3.RELEASE</version>
</dependency>
</dependencies>
配置文件application.yml里引入自定义配置
# UidGenerator
# 初始时间, 默认:"2019-02-20"
uid:
epochStr: 2020-05-08
# 时间位, 默认:30
timeBits: 41
# 机器位, 默认:16
workerBits: 10
# 序列号, 默认:7
seqBits: 12
# 是否容忍时钟回拨, 默认:true
enableBackward: true
# RingBuffer size扩容参数, 可提高UID生成的吞吐量, 默认:3
CachedUidGenerator:
boostPower: 3
# 指定何时向RingBuffer中填充UID, 取值为百分比(0, 100), 默认为50
paddingFactor: 50
IdGenerator.java工具类
@Component
public class IdGenerator {
@Autowired
private CachedUidGenerator cachedUidGenerator;
/**
* 获取uid
*
* @return
*/
public long nextId() {
return cachedUidGenerator.getUID();
}
/**
* 格式化传入的uid,方便查看其实际含义
*
* @param uid
* @return
*/
public String parse(long uid) {
return cachedUidGenerator.parseUID(uid);
}
}
源码分析
DefaultUidGenerator
DefaultUidGenerator的产生id的方法与基本上就是常见的snowflake算法实现,仅有一些不同,如以秒为为单位而不是毫秒。
(1)delta seconds(28bits):这个值是指当前时间与epoch时间的时间差,且单位为秒。epoch时间就是集成DefaultUidGenerator生成分布式ID服务第一次上线的时间,可配置,也一定要根据实际上线时间进行配置,因为默认的epoch时间是2016-09-20,不配置的话,会浪费几年的可用时间。
(2) worker id(22bits):DefaultUidGenerator会在集成用它生成分布式ID的实例启动的时候,往表work_node中插入一行数据,得到的id值就是准备赋值给workerId的值。由于workerId默认22位,那么,集成DefaultUidGenerator生成分布式ID的所有实例重启次数是不允许4194303次(即2^22-1),否则会抛出异常。
(3)sequence(23bits):几个实现的关键点。
a. synchronized保证线程安全。
b.如果时间有任何的回拨,那么直接抛出异常。
c.如果当前时间和上一次是同一秒时间,那么sequence自增。如果同一秒内自增值超过2^13-1,那么就会自旋等待下一秒(getNextSecond);
d.如果是新的一秒,那么sequence重新从0开始。
(4)DefaultUidGenerator的实现可知,它对时钟回拨的处理比较简单粗暴。另外如果使用UidGenerator的DefaultUidGenerator方式生成分布式ID,一定要根据业务的情况和特点,调整各个字段占用的位数。
DefaultUidGenerator的产生id的方法如下。
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);
}
nextId方法主要负责ID的生成,这种实现方式很简单,如果毫秒数未发生变化,在序列号加一即可,毫秒数发生变化,重置Sequence为0。
CachedUidGenerator
CachedUidGenerator是DefaultUidGenerator的重要改进实现。核心利用了RingBuffter,它本质上是一个数组,数组中每个项被称为slot。CachedUidGenerator设计了两个RingBuffer,一个保存唯一ID,一个保存flag。RingBuffer的尺寸是2^n,n必须是正整数。
采取的措施和方案规避时钟回拨问题和增强唯一性
(1) 自增列:CachedUidGenerator的workerId在实例每次重启时初始化,且就是数据库的自增ID,从而完美的实现每个实例获取到的workerId不会有任何冲突;
(2)RingBuffer:CachedUidGenerator不再每次取ID时都实时计算分布式ID,而是利用RingBuffer数据结构预先生成若干分布式ID并保存。
(3)时间递增:传统的SnowFlake算法实现都是实现System.currentYimeMills()来获取时间并与上一次时间进行比较,这样的实现严重依赖服务器的时间。而CachedUidGenerator的时间类型是AtomicLong,且通过incrementAndGet()方法获取下一次时间,从而脱离了对服务器时间的依赖,也就不会有时钟回拨的问题。
(4)CachedUidGenerator通过缓存的方式预先生成一批唯一ID列表,可以解决唯一ID获取时候的耗时。但这种方式,一方面需要耗费内存来缓存这部分数据,另外如果访问量不大的情况下,提前生成的UID中的时间戳可能是很早之前的。
基本实现原理
正如名字体现的那样,这是一种缓存型的ID生成方式,当剩余ID不足的时候,会异步的方式重新生成一批ID缓存起来,后续请求的时候直接的时候直接返回现成的ID即可。
在实现上, UidGenerator通过借用未来时间来解决sequence天然存在的并发限制; 采用RingBuffer来缓存已生成的UID, 并行化UID的生产和消费, 同时对CacheLine补齐,避免了由RingBuffer带来的硬件级「伪共享」问题. 最终单机QPS可达600万。
使用RingBuffer缓存生成的id。RingBuffer是个环形数组,默认大小为8192个,里面缓存着生成的id。
CachedUidGenerator采用了双RingBuffer,Uid-RingBuffer用于存储Uid、Flag-RingBuffer用于存储Uid状态(是否可填充、是否可消费)