发号器实践,企业发号器实例--snowflake系列

美团发号器Leaf-snowflake方案node

Leaf-snowflake方案彻底沿用snowflake方案的bit位设计,便是“1+41+10+12”的方式组装ID号。对于workerID的分配,当服务集群数量较小的状况下,彻底能够手动配置。Leaf服务规模较大,动手配置成本过高。因此使用Zookeeper持久顺序节点的特性自动对snowflake节点配置wokerID。Leaf-snowflake是按照下面几个步骤启动的:git

  1. 启动Leaf-snowflake服务,链接Zookeeper,在leaf_forever父节点下检查本身是否已经注册过(是否有该顺序子节点)。
  2. 若是有注册过直接取回本身的workerID(zk顺序节点生成的int类型ID号),启动服务。
  3. 若是没有注册过,就在该父节点下面建立一个持久顺序节点,建立成功后取回顺序号当作本身的workerID号,启动服务。

image

弱依赖ZooKeeper

除了每次会去ZK拿数据之外,也会在本机文件系统上缓存一个workerID文件。当ZooKeeper出现问题,刚好机器出现问题须要重启时,能保证服务可以正常启动。这样作到了对三方组件的弱依赖。必定程度上提升了SLAgithub

解决时钟问题

由于这种方案依赖时间,若是机器的时钟发生了回拨,那么就会有可能生成重复的ID号,须要解决时钟回退的问题。算法

image

参见上图整个启动流程图,服务启动时首先检查本身是否写过ZooKeeper leaf_forever节点:docker

  1. 若写过,则用自身系统时间与leaf_forever/${self}节点记录时间作比较,若小于leaf\_forever/${self}时间则认为机器时间发生了大步长回拨,服务启动失败并报警。
  2. 若未写过,证实是新服务节点,直接建立持久节点leaf_forever/${self}并写入自身系统时间,接下来综合对比其他Leaf节点的系统时间来判断自身系统时间是否准确,具体作法是取leaf_temporary下的全部临时节点(全部运行中的Leaf-snowflake节点)的服务IP:Port,而后经过RPC请求获得全部节点的系统时间,计算sum(time)/nodeSize。
  3. 若abs( 系统时间-sum(time)/nodeSize ) < 阈值,认为当前系统时间准确,正常启动服务,同时写临时节点leaf_temporary/${self} 维持租约。
  4. 不然认为本机系统时间发生大步长偏移,启动失败并报警。
  5. 每隔一段时间(3s)上报自身系统时间写入leaf_forever/${self}。

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

京东到家发号器方案缓存

百度发号器方案
UidGenerator是Java实现的, 基于Snowflake算法的惟一ID生成器。UidGenerator以组件形式工做在应用项目中, 支持自定义workerId位数和初始化策略, 从而适用于docker等虚拟化环境下实例自动重启、漂移等场景。 在实现上, UidGenerator经过借用将来时间来解决sequence自然存在的并发限制; 采用RingBuffer来缓存已生成的UID, 并行化UID的生产和消费, 同时对CacheLine补齐,避免了由RingBuffer带来的硬件级「伪共享」问题. 最终单机QPS可达600万。
ringbuffer.png
RingBuffer环形数组,数组每一个元素成为一个slot。RingBuffer容量,默认为Snowflake算法中sequence最大值,且为2^N。可经过boostPower配置进行扩容,以提升RingBuffer 读写吞吐量。并发

Tail指针、Cursor指针用于环形数组上读写slot:性能

  • Tail指针
    表示Producer生产的最大序号(此序号从0开始,持续递增)。Tail不能超过Cursor,即生产者不能覆盖未消费的slot。当Tail已遇上curosr,此时可经过rejectedPutBufferHandler指定PutRejectPolicy
  • Cursor指针
    表示Consumer消费到的最小序号(序号序列与Producer序列相同)。Cursor不能超过Tail,即不能消费未生产的slot。当Cursor已遇上tail,此时可经过rejectedTakeBufferHandler指定TakeRejectPolicy

CachedUidGenerator采用了双RingBuffer,Uid-RingBuffer用于存储Uid、Flag-RingBuffer用于存储Uid状态(是否可填充、是否可消费)ui

因为数组元素在内存中是连续分配的,可最大程度利用CPU cache以提高性能。但同时会带来「伪共享」FalseSharing问题,为此在Tail、Cursor指针、Flag-RingBuffer中采用了CacheLine 补齐方式。
cacheline_padding.png

RingBuffer填充时机
  • 初始化预填充
    RingBuffer初始化时,预先填充满整个RingBuffer.
  • 即时填充
    Take消费时,即时检查剩余可用slot量(tail-cursor),如小于设定阈值,则补全空闲slots。阈值可经过paddingFactor来进行配置,请参考Quick Start中CachedUidGenerator配置
  • 周期填充
    经过Schedule线程,定时补全空闲slots。可经过scheduleInterval配置,以应用定时填充功能,并指定Schedule时间间隔
相关文章
相关标签/搜索