Snowflake(推荐)

zk+雪花算法

雪花算法组成

雪花算法生成的ID是一个64 bit的long型的数字且按时间趋势递增。大致由首位无效符、时间戳差值、机器编码,序列号四部分组成。

  • 首位无效符:第一个 bit 作为符号位,因为我们生成的都是正数,所以第一个 bit 统一都是 0。
  • 时间戳:占用 41 bit ,精确到毫秒。41位最好可以表示2^41-1毫秒,转化成单位年为 69 年。
  • 机器编码:占用10bit,其中高位 5 bit 是数据中心 ID,低位 5 bit 是工作节点 ID,最多可以容纳 1024 个节点。
  • 序列号:占用12bit,每个节点每毫秒0开始不断累加,最多可以累加到4095,一共可以产生 4096 个ID。

Leaf-snowflake方案完全沿用snowflake方案的bit位设计,即是“1+41+10+12”的方式组装ID号。对于workerID的分配,当服务集群数量较小的情况下,完全可以手动配置。Leaf服务规模较大,动手配置成本太高。所以使用Zookeeper持久顺序节点的特性自动对snowflake节点配置wokerID。
启动Leaf-snowflake服务,连接Zookeeper,在leaf_forever父节点下检查自己是否已经注册过(是否有该顺序子节点)。
如果有注册过直接取回自己的workerID(zk顺序节点生成的int类型ID号),启动服务。
如果没有注册过,就在该父节点下面创建一个持久顺序节点,创建成功后取回顺序号当做自己的workerID号,启动服务。
但Leaf-snowflake对Zookeeper是一种弱依赖关系,除了每次会去ZK拿数据以外,也会在本机文件系统上缓存一个workerID文件。一旦ZooKeeper出现问题,恰好机器出现故障需重启时,依然能够保证服务正常启动。

时间回拨

  • 若写过,则用自身系统时间与leaf_forever/s e l f 节 点 记 录 时 间 做 比 较 , 若 小 于 l e a f f o r e v e r / {self}节点记录时间做比较,若小于leaf_forever/self节点记录时间做比较,若小于leaf forever/{self}时间则认为机器时间发生了大步长回拨,服务启动失败并报警。
  • 若未写过,证明是新服务节点,直接创建持久节点leaf_forever/${self}并写入自身系统时间,接下来综合对比其余Leaf节点的系统时间来判断自身系统时间是否准确,具体做法是取leaf_temporary下的所有临时节点(所有运行中的Leaf-snowflake节点)的服务IP:Port,然后通过RPC请求得到所有节点的系统时间,计算sum(time)/nodeSize。
  • 若abs( 系统时间-sum(time)/nodeSize ) < 阈值,认为当前系统时间准确,正常启动服务,同时写临时节点leaf_temporary/${self} 维持租约。
  • 否则认为本机系统时间发生大步长偏移,启动失败并报警。每隔一段时间(3s)上报自身系统时间写入leaf_forever/${self}。



由于强依赖时钟,对时间的要求比较敏感,在机器工作时NTP同步也会造成秒级别的回退,建议可以直接关闭NTP同步。要么在时钟回拨的时候直接不提供服务直接返回ERROR_CODE,等时钟追上即可。或者做一层重试,然后上报报警系统,更或者是发现有时钟回拨之后自动摘除本身节点并报警,

做法
  1. 直接抛出异常
  2. 延迟等待(线程阻塞3ms,之后再获取时间,看时间是否比上一次请求的时间大)
    • 如果大了,说明恢复正常了,则不用管
    • 如果还小,说明真出问题了,则抛出异常,呼唤程序员处理
  3. 备用机(当前机器出现问题,则换一台机器,通过高可用来解决该问题)
  4. 采用之前最大时间(通过当前时间和上次最后(大)的时间进行比较,采用上次最大时间的最大序号之后的序号来进行继续使用)
  5. 追赶时间(不返回id,然后将我们的seq增加比如1024个,继续操作直到实现时间正常)

    Segment

    Leaf-segment方案可以生成趋势递增的ID,同时ID号是可计算的,不适用于订单ID生成场景
    数据库+分段

    开源框架优化

    durid数据源配置
    线程池配置