支持回拨性能超过SNOWFLAKE3倍的烟花ID生成器

最近写了个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生成器有比较多的解决方案

  1. 数据库自增
  2. UUID
  3. Snowflake
  4. Leaf
  5. MongDB ObjectId
  6. Uid-generator

但是无论哪个ID生成器,从设计上会考虑的问题可以归纳成几类:

  1. 无序/有序/趋势递增
  2. 有服务端/无服务端
  3. 是否依赖钟/是否依赖存储
  4. ID长度规划
  1. 无序/有序/趋势递增的抉择。

一般ID生成直接会影响数据库使用:

  1. 对于B+树的存储引擎,拿Innodb举例,每次插入都是更改Page页的数据,因为写入乱序,InnoDB不得不做频繁的页分列操作,为新的数据腾出空间。其次需要加载一下页到内存里就为了插入数据。

  2. 对于LSM Tree的存储引擎(比如Ocean Base),虽然不影响插入时的性能,但是在做层级合并的时候,如果数据是随机的,会加载更多的文件,使写入放大。

综合起来我们会考虑趋势递增,因为有序递增在多线程上容易发生资源争抢。

  1. 有服务端/无服务端

1.有服务端的话,需要考虑每次网络请求的开销。基于这个基础上,我们一般会设计一个桶,每次拉取的时候拉取一个号段。这样就能减小开销。同时需要考虑服务端高可用,客户端需要缓存,在服务端无法使用的时候能继续消耗。

  1. 依赖时钟/依赖存储
  1. 不管是依赖时钟还是依赖存储,都是为了解决下次服务启动的时候,不会生成重复的ID。对于依赖存储的服务,有强依赖的比如依赖数据库生成号段的。只依赖时钟只能解决运行时的时钟回拨(可以用来消费未来时间),但是无法保障服务重启以后,再出现时钟回拨。
  1. 长度规划

    1. 类似Snowflake 8 byte的话支持不超过100年
    2. 但是如果不用8 byte的话,就需要考虑String了

总体考虑来说:

选了趋势递增,无服务端,依赖时钟,弱依赖存储(可选),长度16byte。

权衡带来的好处也有:

  1. 插入性能比较好。(按照字符串ascii码趋势递增)
  2. 使用简单,不用搭建服务端。
  3. 解决时钟回拨的问题
    1. 通过回拨位每次时钟回拨修改回拨位的值,并且记录上次回拨时间。
    2. 如果修改的回拨位已经有回拨记录,并且当前时间少于它,就算出一个差值,来消费未来时间。
    3. 解决snowflake时钟回拨检测加锁。在多线程下性能是snowflake3倍以上。
  4. 支持时间到 8888年左右

使用也比较简单

简单使用

添加MAVEN 依赖

<dependency>
  <groupId>io.gitee.binaryfox</groupId>
  <artifactId>firework-id-generator</artifactId>
  <version>1.0</version>
</dependency>

使用

  FireWorkGenerator.init(0, null);
  System.out.println(FireWorkGenerator.nextId());

高阶使用

  
        /**
         *  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();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值