最近写了个ID生成器: FireWork。项目地址: firework-id-generator
- 16 byte顺序字符串[8byte时间戳 1byte回拨位 2byte ServiceId 5byte序列号]序列号不在下一秒重置
- 总体趋势递增
- 支持时间到 8888年左右
- 支持3844台相同微服务之间id唯一
- 支持时钟回拨无数次,61次后时间还小于上次回拨时间时通过消费未来时间确保ID不重复
- 支持监听时钟回拨告警或者其他业务处理
- 性能在多线程的时候表现良好(12000/ms 多余snowflake 4000/ms),理论上1s能生成1200wID
- 通过实现存取接口来确保下次启动加载回拨位和回拨最大时间(可选)
业界关于ID生成器有比较多的解决方案
- 数据库自增
- UUID
- Snowflake
- Leaf
- MongDB ObjectId
- Uid-generator
- …
但是无论哪个ID生成器,从设计上会考虑的问题可以归纳成几类:
- 无序/有序/趋势递增
- 有服务端/无服务端
- 是否依赖钟/是否依赖存储
- ID长度规划
- 无序/有序/趋势递增的抉择。
一般ID生成直接会影响数据库使用:
对于B+树的存储引擎,拿Innodb举例,每次插入都是更改Page页的数据,因为写入乱序,InnoDB不得不做频繁的页分列操作,为新的数据腾出空间。其次需要加载一下页到内存里就为了插入数据。
对于LSM Tree的存储引擎(比如Ocean base),虽然不影响插入时的性能,但是在做层级合并的时候,如果数据是随机的,会加载更多的文件,使写入放大。
综合起来我们会考虑趋势递增,因为有序递增在多线程上容易发生资源争抢。
- 有服务端/无服务端
1.有服务端的话,需要考虑每次网络请求的开销。基于这个基础上,我们一般会设计一个桶,每次拉取的时候拉取一个号段。这样就能减小开销。同时需要考虑服务端高可用,客户端需要缓存,在服务端无法使用的时候能继续消耗。
- 依赖时钟/依赖存储
- 不管是依赖时钟还是依赖存储,都是为了解决下次服务启动的时候,不会生成重复的ID。对于依赖存储的服务,有强依赖的比如依赖数据库生成号段的。只依赖时钟只能解决运行时的时钟回拨(可以用来消费未来时间),但是无法保障服务重启以后,再出现时钟回拨。
-
长度规划
- 类似Snowflake 8 byte的话支持不超过100年
- 但是如果不用8 byte的话,就需要考虑String了
总体考虑来说:
选了趋势递增,无服务端,依赖时钟,弱依赖存储(可选),长度16byte。
权衡带来的好处也有:
- 插入性能比较好。(按照字符串ascii码趋势递增)
- 使用简单,不用搭建服务端。
- 解决时钟回拨的问题
- 通过回拨位每次时钟回拨修改回拨位的值,并且记录上次回拨时间。
- 如果修改的回拨位已经有回拨记录,并且当前时间少于它,就算出一个差值,来消费未来时间。
- 解决snowflake时钟回拨检测加锁。在多线程下性能是snowflake3倍以上。
- 支持时间到 8888年左右
使用也比较简单
简单使用添加MAVEN 依赖
io.gitee.binaryfox firework-id-generator1.0
使用
FireWorkGenerator.init(0, null); System.out.println(FireWorkGenerator.nextId());高阶使用
FireWorkGenerator.init(0, new FireWorkStepBackHandler() {
@Override
public void notifyStepBack(long[] before) {
//比如回拨到63的一半水位线就告警
int count = 0;
for (long l : before) {
if (l != 0) {
count++;
}
if (count > before.length / 2) {
//输出error日志 并且监控报警
}
}
}
@Override
public long[] getStepBackTimeRecordArray(int serviceId) {
//比如 application_name+serviceId当作key 取一个list
return null;
}
@Override
public void setStepBackTimeRecordArray(int serviceId, int index, long timeBeforeStepBack) {
//比如 application_name+serviceId当作key 存一个list
}
@Override
public void setStep(int serviceId, long step) {
//比如 application_name+serviceId当作key 存一个long
}
@Override
public long getStep(int serviceId) {
//比如 application_name+serviceId当作key 存一个long
return 0;
}
});
//使用
String s = FireWorkGenerator.nextId();



