美文网首页
分布式唯一ID生成

分布式唯一ID生成

作者: 萧然AND沐橦 | 来源:发表于2020-03-30 13:46 被阅读0次

    设计目的:

    在分布式条件下,生成唯一ID,速度快

    要求:

    • 长度8字节,首位不能使用,故只能使用63bits

    • 全局唯一

    设计思路:

    由于不是集中分配,所以每台机器必须唯一,生成过程中必须要有机器唯一的信息,可以使用:

    1. 每个机器分配一个唯一机器ID,需要外部机制的依赖如etcd

    2. 取MAC地址(太长了128bits)

    3. 取IP地址(可能冲突,不同机器的子网ip可能冲突)

    设计参考:

    思路,按照生成uuid的思路生成,参考twitter的snow-flake算法来生成

    格式
    • tag:1bit,不使用

    • physic delta time:31bits,当前物理时间的秒-Base时间秒值,最大支持到68年以后,按照某一时间点为Base时间(如项目上线时间),可以hard code到代码中。

    • worker node id:12bits,每个进程用的唯一标识 ID

    • sequence id:20bits,1s内自增

    提供的性能:

    每秒最大理论提供获取104w个id生成。

    难点及解决:

    会出现的问题 解决方案
    A. 如果当前时间⼩于记录的上⼀个秒值,则说明这台机器的时钟回拨了 更换worker id
    B. 如果当前时间等于记录的上⼀个秒值,则说明这台机器的时钟回拨了 ⾃旋⾄下⼀秒
    C. 如果这台机器的系统时间在启动之前回拨过,那么有可能出现ID重复的危险 可以重启时候使⽤与上次不同的worker id来避免确定时钟可能回拨的最⼤值,为了防⽌重复id分配,可以等待最⼤回拨时间后分配确定时钟可能回拨的间隔时间,保证不会连续出现回拨!
    D. 最后⼀次获取的ID值是否需要保存 可以通过上⾯⽅法C避免持久化保存

    (每次启动使⽤不同的worker id弊端:从ID不能确定是哪⼀个机器的进程发送的事务请求。)

    限制因素的处理:

    由于集群中时钟回拨的时间和次数很难确定,所以我们倾向每次使用不同的worker id来回避这个问题。

    这里我们会强依赖Etcd,用来保存进程 的worker id信息和已分配worker id的map。

    实现思路如下:

    1. 每个进程在启动的时候,都会生成一个uuid,将此ID作为唯一键,Value为当前进程的worker id,并且维持续Lease

    2. 维护一个已分配的worker id的bitmap,由于worker id最大为4096,故该bitmap需要空间4096/8 bytes = 512bytes,用来标记已分配的worker id情况。

    3. 程序每次启动时,需先waitNextSecond(或者0.5s,这个wait时间可以自己根据实际情况需求设定),保证多次重启的情况下,能够有进程的Lease失效。

      每次分配的流程如下:

      1. 都先获取现在集群中所有的进程的worker id分配信息,和该bitmap

      2. 找出目前已分配到了最大worker id值,在此基础上+1操作,作为此次分配的worker id

      3. 计算出新的bitmap,用Etcd的事务,写入该bitmap到Etcd中,如果事务失败,重新启动整个流程

      4. 之后,将自己的uuid作为键,此次分配的worker id作为value,写入到Etcd中,并且维持续Lease

      5. 过程中如果失败,重新启动整个流程

    由于bitmap的延后清除,即当存在已经挂了的进程时,即使其Lease最终消失,但bitmap中仍旧会存在其已经占用,此时我们保持向后递增分配,就可以绕过当前已存在的worker id,保证不会立即复用编号较小的worker id。

    由于步骤3会重新生成bitmap,以此来保证Lease已经消失的进程的worker id,可以被回收掉。

    我们保证使用最大值+1来分配worker id,如果已经存在到达了4095的worker id我们需要回环去找。即最大值回环向右第一个当前未使用的worker id。并且用Etcd事务保证了更新bitmap的原子性。

    正确性分析:

    • Lease ,worker id过期时间

    • t,重启一次进程,开始获取worker id前等待时间

    • N,Lease时间内重启次数为

    • n,集群内进程数量

    • Range,Worker Id 分配区间

    那么,在Lease时间内,单个进程能够重启的次数为:

    N = Lease / t ;

    那么n个进程,在Lease时间内,可以重启的次数为:

    C = N * n ;

    即,C = Lease / t * n

    C的值的含义就是,在Lease时间内可能消耗掉的worker id的数量最大值。

    那么我们只要保证 C < Range 就可以保证Lease时间内是有足够的worker id可以分配给集群中的进程。

    举例:

    Range = 4096; 分配worker ID 的区间

    n = 100 ; 集群中进程数量为 100 个

    Lease = 5 s;worker id 的 Lease 时间

    t = 0.5 s ; 重启等待时间0.5s

    由此,计算出来C的值为:C = 5 / 0.5 * 100 => 1000

    4096 > 1000 ,可以满足Lease内的分配需求。

    允许的最大时钟跳变:

    即最后一次分配worker id复用了第一次分配的worker id经历的时间,也即 worker id 的Range 在多少秒会被使用完。

    我们假设集群中的进程断重启。那么理论上,在Range用完时(不回收worker id),可以重启的总次数为:

    MaxRestartCount = Range / n ;

    重启花费的总时间为:

    ReverseTimeCount = MaxRestartCount * t ;

    (Lease 必须小于 ReverseTimeCount ,这样 再次循环使用第一次分配的worker id时,才可用。)

    故,我们允许的最大时钟跳变为 ReverseTimeCount

    举例:

    目前我们的Range为【0,4096),也就是可以分配4096个worker id,即 Range = 4096 ;

    假设集群中存在100个进程,即 n = 100 ;

    假设重启一次时间为0.5s,即 t = 0.5 s

    故 ReverseTimeCount = Range/n * t = 20.48 s

    允许的最大时钟跳变为 20.48 s 已满足大多数场景

    算法的限制条件:

    1. 对集群内的进程数量有限制要求
    2. 对于允许的最大时钟跳变时间有限制
    3. 强依赖Etcd

    假设,一个集群4个进程,也就是 n = 4 。

    假设我们的Lease 为5 s,重启等待时间为0.5s

    那么允许的最大时钟跳变:
    (假设目前worker id的数量是4096个)
    ReverseTimeCount = 4096 / 4 * 0.5 = 512 s

    Lease 内 最大消耗的worker id 数量为:

    C = 5 / 0.5 * 4 = 40 个

    要保证一个Lease不能消耗完所有worker id并且根据集群时钟同步状况,来控制允许的最大时钟跳变。

    如果集群中进程是1000个,那么
    C = 5 / 0.5 * 1000 = 10000 ,10000大于4096 ,这个超过了worker id的分配范围。所以此时必须调整 重启等待时间,调长一些。

    此时允许的最大时钟调变:
    ReverseTimeCount = 4096/1000*0.5 = 4 * 0.5 = 2 s

    The End ;

    相关文章

      网友评论

          本文标题:分布式唯一ID生成

          本文链接:https://www.haomeiwen.com/subject/qxxvuhtx.html