# firework-id-generator **Repository Path**: binaryFox/firework-id-generator ## Basic Information - **Project Name**: firework-id-generator - **Description**: No description available - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2021-09-26 - **Last Updated**: 2022-08-26 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ### FireWorkId生成器 - 16 byte顺序字符串[8byte时间戳 1byte回拨位 2byte ServiceId 5byte序列号]序列号不在下一秒重置 - 总体趋势递增 - 支持时间到 8888年左右 - 支持3844台相同微服务之间id唯一 - 支持时钟回拨无数次,61次后时间还小于上次回拨时间时通过消费未来时间确保ID不重复 - 支持监听时钟回拨告警或者其他业务处理 - 性能在多线程的时候表现良好(10000/ms 多余snowflake 4000/ms),理论上1s能生成1200wID - 通过实现存取接口来确保下次启动加载回拨位和回拨最大时间(可选) 业界关于ID生成器有比较多的解决方案 > 1. 数据库自增 > 2. UUID > 3. Snowflake > 4. Leaf > 5. MongDB ObjectId > 6. Uid-generator > 7. .... 但是无论哪个ID生成器,从设计上会考虑的问题可以归纳成几类: > 1. 无序/有序/趋势递增 > 2. 有服务端/无服务端 > 3. 是否依赖钟/是否依赖存储 > 4. ID长度规划 1. 无序/有序/趋势递增的抉择。 一般ID生成直接会影响数据库使用: > 1. 对于B+树的存储引擎,拿Innodb举例,每次插入都是更改Page页的数据,因为写入乱序,InnoDB不得不做频繁的页分列操作,为新的数据腾出空间。其次需要加载一下页到内存里就为了插入数据。 > > 2. 对于LSM Tree的存储引擎(比如Ocean Base),虽然不影响插入时的性能,但是在做层级合并的时候,如果数据是随机的,会加载更多的文件,使写入放大。 综合起来我们会考虑趋势递增,因为有序递增在多线程上容易发生资源争抢。 2. 有服务端/无服务端 > 1.有服务端的话,需要考虑每次网络请求的开销。基于这个基础上,我们一般会设计一个桶,每次拉取的时候拉取一个号段。这样就能减小开销。同时需要考虑服务端高可用,客户端需要缓存,在服务端无法使用的时候能继续消耗。 3. 依赖时钟/依赖存储 > 1. 不管是依赖时钟还是依赖存储,都是为了解决下次服务启动的时候,不会生成重复的ID。对于依赖存储的服务,有强依赖的比如依赖数据库生成号段的。只依赖时钟只能解决运行时的时钟回拨(可以用来消费未来时间),但是无法保障服务重启以后,再出现时钟回拨。 4. 长度规划 > 1. 类似Snowflake 8 byte的话支持不超过100年 > 2. 但是如果不用8 byte的话,就需要考虑String了 总体考虑来说: 选了趋势递增,无服务端,依赖时钟,弱依赖存储(可选),长度16byte。 权衡带来的好处也有: 1. 插入性能比较好。(按照字符串ascii码趋势递增) 2. 使用简单,不用搭建服务端。 3. 解决时钟回拨的问题 1. 通过回拨位每次时钟回拨修改回拨位的值,并且记录上次回拨时间。 2. 如果修改的回拨位已经有回拨记录,并且当前时间少于它,就算出一个差值,来消费未来时间。 3. 解决snowflake时钟回拨检测加锁。在多线程下性能是snowflake3倍以上。 4. 支持时间到 8888年左右 ### 简单使用 > 添加MAVEN 依赖 ``` maven io.gitee.binaryfox firework-id-generator 1.0 ``` > 使用 ``` java FireWorkGenerator.init(0, null); System.out.println(FireWorkGenerator.nextId()); ``` ### 高阶使用 ``` java /** * FireWorkStepBackHandler * 不实现的话请自己去实现业务告警,能保障运行时ID不重复(因为它自己会维护一个回拨列表在内存 * 只要内存还存在它再次回拨发现时间小于上次时间 可以自动消费未来时间), * 但是不能保障服务重启的时候要去计算下看看会不会生成重复id * 解法有几种(改成未使用的ServiceId 但是无法保障大步幅回拨时id发生碰撞) * * 实现的话要实现服务加载的时候 * 加载一个回拨列表和回拨下标。 * 更新回拨下标和回拨列表。 * 如果实现了存取,其实业务告警不告警都可以 * * */ FireWorkGenerator.init(0, new FireWorkStepBackHandler() { @Override public void notifyStepBack(long[] before) { /** * before 是一个length 63的数组 * index表示的回拨flag * before[index]表示的是上次回拨回拨前的时间戳 * before[index]=0表示未发生回拨 */ //比如回拨到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(); ```