雪花算法 | 青训营

194 阅读5分钟

对雪花算法的学习

  • Snowflake IDs或者snowflakes 生成分布式全局ID的算法
  • Twitter创建,用于推文的ID,京东仓储平台生成ID是用的雪花算法修改后的版本
    • 生成的ID分布式唯一且按时间递增有序,毫秒数在高位,自增序列在低位,ID都是趋势递增
    • 不依赖数据库等第三方系统,稳定性尤其是性能非常高
    • 根据自身业务特性分配bit位,非常灵活

其他分布式唯一ID生成方案

  • 数据库生成

    • 字段默认自增(设置auto_increment来生成全局唯一ID)
    • 优点:
      • 简单,维护成本低
      • ID唯一且单调递增,并且可以设置固定步长
    • 缺点:
      • 可用性难以保证
      • 每次生成ID都要访问数据,瓶颈在于数据库实例的读写性能
      • 数据库挂了就会导致服务不可用
  • UUID

    • 标准式:由一组32位数的16进制数字所构成

      • 以连字号分为五段,形式为8-4-4-4-12的32个字符
      • e.g.550e8400-e29b-41d4-a716-446655440000
    • 总数:16^32=2^128,差不多3.4 x 10^38

    • 每纳秒产生1M个UUID,要花100亿年才能将所有UUID用完

    • 优点

      • 本地生成ID,不需要rpc调用,没有网络上的耗时
      • 没有性能上限
    • 缺点

      • 可读性差
      • 长度过长,16字节128位,生成UUID通常是36位(包括了-),某些场景不适用
      • 数据库主键的话,会让InnoDB引擎下长度过程,二级索引会占据很大空间
      • 无法保证趋势增性,在存储引擎下,新插入数据会根据主键找合适位置,会导致频繁的移动、分页的开销增多

snowflake

  • 算法生成的id通常位64位,java中用long类型(8字节)

  • image.png

    • 符号位 1 即范围 [-2^(64-1), 2^(64-1)] (不包括id)
    • 41时间位(单位毫秒):1 x 2^41 / (1000 x 3600 x 24 x 365) = 69年
    • 10bit机器位 [0,1023]
    • 12-bit表示1ms内自动递增的序列号,1 x 2^12 = 4096个 范围[0,4095]。
      • 单机1ms可以生成4096个不重复的ID!!!

    -这种方式可以支撑大部分业务,如果有划分IDC的需求,可以将10-bit分5-bit给IDC,分5-bit给工作机器。这样就可以表示32个IDC,每个IDC下可以有32台机器。

    • 可以做后续调整,如果时间位为秒级,省出10bit留给机器位
    • 1-31-22-10,时间变为1 x 2^31/ (3600 x 24 x 365) = 68年
      • 通过上述方式进行生成ID,可以保证4194303台机器在任意68年的时间段里不会出现重复的ID,而且单台机器支持一秒能够生成1024个ID。
    • 最大ID:maxInstanceId = -1 ^( -1<<10),补码方式

计算序列号

  • 如果上个生成ID的时间位与当前ID的时间位冲突,则会生成一个序列号进行区分,如果序列号用尽,则等待下一个时间点再生成。如果上个生成ID的时间位与当前ID的时间位不冲突,则将序列号设置成0。
if (lastTimestamp == timestamp) { 
    sequence = (sequence + 1) & sequenceMask; 
    //通过掩码获取序列号,超过序列后等待下一个时间点
    if (sequence == 0) { 
        timestamp = tilNextSecs(lastTimestamp); 
        } 
    } 
    else {
        //上一个时间戳和当前时间戳,出现不同,生辰一个序列号区分
        sequence = 0L; 
    }

生成 ID

    ((timestamp - from) << timestampLeftShift) // (当前时间 - 起始时间) 向左移位 
    | (instanceId << instanceIdShift) // 机器位 向左移位 
    | sequence; // 序列位
//((timestamp - from) << timestampLeftShift) 计算时间位  
from是固定的1422720000000,范围取值:[0, 2^41 - 1],from解释:相对时间,以from为起始来算的过去了多少时间

注意事项

  • 机器位取值
    • 主机唯一标识,运维平台取,但不一定能存下以及唯一标识后续改变
    • ip进行计算,最大255.255.255.255,采用IP段数值相加<1024即可生成机器位,不受ip位限制,这种方式一定要保证网段的相同,不然可能出现重复
    • redis或者数据库或者zk协调,在应用启动的时候给每个机器分配不会重复的机器位id
  • 单例方式生成ID,会依赖上一次生成的ID的时间来判断是否需要对序列号进行增加的操作,如果不是单例,两个业务用两个对象同时获取ID,则可能会生成相同的ID
  • 雪花算法强依赖时间,如果时间发生回拨,有可能会生成重复的ID,在我们上面的nextId中我们用当前时间和上一次的时间进行判断,如果当前时间小于上一次的时间那么肯定是发生了回拨
    • 雪花算法的做法是简单的抛出了一个异常,我们自己可以通过配置等待时间,让机器时间追回来
    • 若出现ID重复可以通过逆运算进行问题定位

总结

  • 之前一直听说雪花算法,今天了解了一下感觉确实有它的实际应用(比如机器位取值,利用到时间戳这些系统上的东西)