V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
爱意满满的作品展示区。
tlanyan
V2EX  ›  分享创造

一个改进的 Java 短 ID 生成库

  •  
  •   tlanyan · 15 天前 · 1438 次点击

    目前知名且好用的短整数 ID 生成器是 Yitter 的 IdGenerator。相对于其他雪花类算法库,IdGenerator 最大的优点就是不固定长度,可按需配置各个段的值或者偏移,大幅减少了 ID 的长度,避免了号段浪费。如果并发量上来了,可以直接更改配置实现无缝升级。

    IdGenerator 改进明显,但是对于本人手上的小项目来说 ID 还是太长了(默认配置下随手甩出来的 ID 就是上万亿的数),也用不着这么高的并发(别说一秒 5W 个 id ,一天能有 5W 个订单都能让人笑醒)。此外全局只能一个生成器,不能根据业务场景采用不同的生成器:购买订单和退货订单 ID 是可以重复的,并且一般情况下退货订单数量比购买订单少一个量级,可以采用更短的 ID 。

    出于上面的考虑,本人对 IdGenerator 库进行了如下改进:

    1. 支持不同的时间精度。原版中默认是 1 毫秒,现在新增了 10 毫秒 – 10 秒(实际上是 8 毫秒、128 毫秒、1024 毫秒 和 8192 毫秒,方便用位运算代替整数除法)的精度选项。低精度选项(例如 1 秒、10 秒)适合并发量小的项目(例如 1 秒内最多 30 个订单),能大大减少 ID 的长度;

    2. WorkIdBitLength 支持为 0 ,让单机应用能生成更短或数值更小的 ID ;

    3. 支持不同场景使用不同的 Id 生成器,同时兼容原来的接口:

    // 兼容原版使用方式
    var options = new IdGeneratorOptions();
    YitIdHelper.setIdGenerator(options);
    var newId = YitIdHelper.nextId();
    
    // 退款订单场景
    final var REFUND = "refund";
    var options2 = new IdGeneratorOptions();
    // 退款相当于购买订单频率更低,可以降低时间精度要求
    options2.Precision=1;
    YitIdHelper.setIdGenerator(REFUND, options2);
    var newId = YitIdHelper.nextId(REFUND);
    
    本人测试了不同算法和时间精度下生成的 ID ,结果为:
    
    # 调用 Hutool 的 IdUtil.getSnowflakeNextId() 方法生成
    这是用方法 snowflake  生成的 Id: 1957067304089067520 (长度 19 位)
    =====================================
    # 使用 IdGenerator 的默认配置,无任何改动
    这是用方法 默认 生成的 Id:       80881995821061 (长度 14 位)
    =====================================
    # 修改 options.Precision = 1
    这是用方法 precision1 生成的 Id:10110249734149 (长度 14 位)
    =====================================
    # 修改 options.Precision = 2
    这是用方法 precision2 生成的 Id:631890624517 (长度 12 位)
    =====================================
    # 修改 options.Precision = 3
    这是用方法 precision3 生成的 Id:78986326021 (长度 11 位)
    =====================================
    # 修改 options.Precision = 4
    这是用方法 precision4 生成的 Id:9873293317 (长度 10 位)
    

    对于本人手上的小项目,没有多大的并发( 1 天大概几百个订单),可以配置生成更短的 ID:

    var options = new IdGeneratorOptions();
    options.Precision = 4;   // 时间精度为 10 秒(实际上是 8 秒)
    options.WorkerIdBitLength = 1;   // 最多两个应用实例
    options.SeqBitLength = 6; // 每 10 秒可以生成 29 个订单,高峰期超过也没关系,会自动进行时间漂移
    YitIdHelper.setIdGenerator("order", options);
    var id = YitIdHelper.nextId("order");
    # 生成的 ID:2707882245
    

    此配置下订单号为 10 位,这个长度是非常能接受的,并且值也很小。

    Java 版本的改进代码已经放到 Github ,其他语言版本本人暂时用不着,因为未更改。如果有需要按照 Java 版本的方式更改即可,代码量其实很少。另外目前没有 Maven 包,使用时需要将代码下载下来直接使用。

    10 条回复    2025-08-24 20:44:06 +08:00
    maokg
        1
    maokg  
       15 天前
    改进->改退(开玩笑,适合自己场景的库才是最好的
    xhawk
        2
    xhawk  
       15 天前 via Android
    前几天看了一下,如果订单是跟数据库做一个绑定的话,那这个订单号其实逻辑上就可以设计的很短。意思就是说,我们先做一个简单的假设,一个数据库可以存放 1 亿的订单,经过这样子分库之后,那么这个订单就可以无限地变大了
    tlanyan
        3
    tlanyan  
    OP
       15 天前
    @maokg 确实可以认为是并发量下降了
    tlanyan
        4
    tlanyan  
    OP
       15 天前
    @xhawk 远不到分库的时候,十多天才 1 万单
    netnr
        5
    netnr  
       15 天前
    一直在用 53 位的雪花 ID ,兼容前端 JS Number.MAX_SAFE_INTEGER ,单机版

    Timestamp 41 位的时间戳,约 69 年
    Sequence 12 位的序列号,4096/ms
    tlanyan
        6
    tlanyan  
    OP
       15 天前
    @netnr 只是不想要那么长又没什么意义的 ID ,能用多少年倒是其次,另外完全用不到这么高的并发
    clarkethan
        7
    clarkethan  
       14 天前
    追求很短的 id ,建议全局递增即可

    另外,同一个系统内的购买订单和退货订单,如果可能,id 还是尽量别重复为好,毕竟 id 是一个还算充足的的资源
    tlanyan
        8
    tlanyan  
    OP
       14 天前
    @clarkethan 就是不想要递增 id 才用这类 id 算法,另外这里说的可以重复只是一个避免心智负担的要求,这类 id 生成算法基本上就不太可能生成重复 id
    LoNeZ
        9
    LoNeZ  
       12 天前
    ...不都是 4 字节 8 字节吗...为什么会嫌长?
    tlanyan
        10
    tlanyan  
    OP
       9 天前
    8 个字节字符串最大将近 20 位,那是非常长
    关于   ·   帮助文档   ·   自助推广系统   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   903 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 20:35 · PVG 04:35 · LAX 13:35 · JFK 16:35
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.