分布式Id生成方案

元气小坏坏 提交于 2020-11-03 11:23:35

        类SnowFlake这种方式为划分具体命名空间的一种ID生成方式。把64bits划分为多段,大致为标识+worker+时间戳+递增序列。具体位数划分决定了ID使用年限及ID的利用率。比如以上图示例:41位的时间戳可标识(1<<41)/(1000*60*60*24*365)大概为65年时间。10bit的worker可部署在1024台机器上10bit的自增序列可每毫秒产生1024个id.理论上每台机器可达到百万QPS值。具体划分根据公司内业务并发情况,业务可接入系统数来决定。

优点:

1.时间戳在高位,序列在低位保证ID是递增的。

2.无任何依赖外部系统,数据库以及中间件,保证服务更高的稳定性以及生成id的高性能。

3.可灵活分配bit位数。


缺点:

   1.强依赖于硬件时钟,如果硬件时间回拨,可能会导致ID重复。

 

避免时钟回拨方案Id生成方式选择上还是选择类snowflake的方式,生成方式更灵活,可控,但需解决硬件时钟回拨的问题。解决时钟回拨的问题可以有以下两种方案:
A. 代码发现发生时钟回拨后判断回拨长度,如果<=5ms则等待时间追上上次更新时间,如果>则抛出异常,返回错误码给调用端。(美团leaf分布式id做法)
B. 消费未来时间,只在系统初次启动时记录System.currentTimeMillis().赋值为lastTime之后通过消费未来时间得到lastTime.(百度uid-genarator分布式id做法


优劣对比
A方案:

       优点: 实现简单,理解容易。

       缺点:发生时钟回拨时,调用端可能会有感知。并且并发量特别大,5ms的等待也会对吞吐量造成影响。
B方案:   

        优点: 能真正避免时钟回拨的影响,调用端无异常感知。缺点:生成id逆转后表示的不是真正id生成的时间。相关衡量下,我个人倾向于B方案。对于B方案缺点,其实并不算是真正的缺陷,对于这个id到底是哪个时间生成的,很多时候我们并不关心。

        根据具体业务情况以及实现64位分配转为long整数id的方式,采用类snowFlakeID生成方式采用4+12+32+14模板,不足64位的高位补0.

分别为4位的数据中心或者机房id,12位的workerid(即标识运行唯一id节点),32位的秒级时间戳(可使用近85年)以及14位的自增序列,即每秒可产生16384个id。按照平均TPS200,可满足接入80个应用而无需跨秒等待(指ID生成方面)。足以应付目前业务能力。

不同的分布式ID服务节点,采用获取不同workerId作为区分。获取workerID目前提供mysql表插入获取自增ID,ZK注册永久节点获取节点id,并且弱依赖于这些外部设施。获取之后会持久化这些id在本机磁盘。在获取ID过程时,避免每次获取id时的计算。采用预生成加Buffer缓存的技术,将计算ID消耗平摊在后台异步线程计算上,获取ID线程则将以O(1)的时间复杂度获取id返回,降低了延迟。细节上可采用负载因子配置,在Buffer中剩余可用id低于BufferSize*factor时进行异步线程填充。尽可能避免获取id线程在获取id时的计算,提升性能。采用类SnowFlake生成算法就不得不避免硬件时钟回拨,故采用上诉方案B进行,通过消费未来时间来避免时钟回拨,仅仅在应用启动时依赖本地时钟值。

大致为在应用启动时获取本地时钟值(秒级时间戳)给lastTime字段。并且得到这秒级的id总序列,存入Buffer中。之后在每次序列用完时候都自增1s来获取这1s内的id来填充Buffer。达到消费未来时间,避免硬件时钟回拨问题。理论上在应用第二次启动时候,由于最开始依赖的还是本地时钟,依然还是会有时钟回拨问题。但是从实际上来看应用从停止到启动最快可能需要花费10s以上时间,一般来讲时钟回退不会那么长,并且启停止后再启动的时间也不会那么短。故个人认为可以忽略这部分可能发生的时钟回退。因此我们只需要考虑应用在运行之中的时钟倒退问题。大家有什么不同建议也可以提供。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!