
最近写了个ID生成器: FireWork。项目地址: firework-id-generator
业界关于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。
权衡带来的好处也有:
使用也比较简单
简单使用添加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();