翻译者:shmilychen,腾讯 IEG 后台联合开发技工
1. 分布式系统控制技术惟一 ID 缺点
在分销联合开发中,会存大批的情境都只须惟一 ID 来展开记号。比如,普通用户只须惟一身份记号;货品须要惟一记号;最新消息只须惟一记号;该事件只须惟一记号之类。的的分布式系统控制技术情境下,分销会更为仰赖惟一 ID。
分布式系统控制技术惟一 ID 的缺点如下表所示表下图:
由上而下惟一:要保证裂解的 ID 是自上而下性惟一的,这是分布式系统控制技术 ID 的大体上特别强调;Roybon:裂解的 ID 只须依照此种原则规范化,更易数据库的写入和顺序操作方式;可用性:只须保证高mammalian下的可用性。除对 ID 号码再者的特别强调,分销还对 ID 裂解控制技术的可用性特别强调极高;控制力:分布式系统控制技术自然生态下不仰赖服务站合格证书需先自行裂解 ID;安全性:不暴露控制技术和分销的重要信息。在许多分销情境下,会只须 ID 或是棒状。2. 常用分布式系统控制技术惟一 ID 裂解方案
2.1. UUID
UUID(Universally Unique Identifier,即全功能惟一记号码)算法的目地是裂解此种形式的由上而下惟一 ID 来记号控制技术中的前述Lembron,的的分布式系统控制技术自然生态下,UUID 能不仰赖服务站合格证书需先全自动裂解由上而下惟一 ID。
UUID 的国际标准形式为 32 个十六进制字符串成的字符串,且拆分成五个部份,比如:467e8542-2275-4163-95d6-7adc205580a9。
选用情境的完全相同,会存下列两个完全相同版的 UUID Beauvoisin选用,如下表下图下图:
日数的 UUID:主要仰赖现阶段的日数戳和笔记本电脑 mac 门牌号码。市场竞争竞争优势是能大体上保证东南亚地区惟一性,缺点是尽管选用了 mac 门牌号码,会暴露 mac 门牌号码和裂解日数;分布式系统控制系统安全的 UUID:将日数的 UUID 算法中的日数戳前三位代替为 POSIX 的 UID 或 GID。市场竞争竞争优势是能保证东南亚地区惟一性,缺点是极少选用,常用库大体上没同时实现;解释器的 UUID:解释器或伪解释器裂解。市场竞争竞争优势是同时实现简单,缺点是多次重复机率可顺序;英文名字内部空间的 UUID(MD5 版):选定的英文名字内部空间/英文名字裂解 MD5 杂凑值得称赞到。市场竞争竞争优势是完全相同英文名字内部空间/英文名字下的 UUID 是惟一的,缺点是 MD5 对撞问题,只用于向后相容;英文名字内部空间的 UUID(SHA1 版):将英文名字内部空间的 UUID(MD5 版)中国的杂凑算法修正为 SHA1。市场竞争竞争优势是完全相同英文名字内部空间/英文名字下的 UUID 是惟一的,缺点是 SHA1 顺序相对费时。UUID 的市场竞争竞争优势是操控性非常高,尽管是邻近地区裂解,没互联网消耗。而其也存许多缺陷,包括不更易存储,UUID 太长,16 字节 128 位,通常以 36 长度的字符串表示;重要信息不安全,日数的 UUID 可能会造成笔记本电脑的 mac 门牌号码泄露;ID 作为 DB 主键时在特定的情境下能存许多问题。
2.2. 数据库自增 ID
数据库自增 ID 是最常用的一种裂解 ID 形式。利用数据库本身来展开设置,在全数据库内保持惟一。市场竞争优势是选用简单,满足大体上分销需求,天然规范化;缺点是强仰赖 DB,会尽管数据库部署的许多缺点而存单点故障、数据一致性等问题。
针对上面介绍的数据库自增 ID 的缺陷,会存下列两种优化方案:
资料库水平拆分,设置完全相同的初始值和完全相同的步长。这样能有效的裂解集群中的惟一 ID,也大大降低 ID 裂解数据库操作方式的负载。批量裂解一批 ID。这样能将数据库的压力减小到先前的 N 分之一,且数据库故障后仍可继续选用一段日数。此种方法详见下面的数据库号段模式介绍。数据库自增 ID 方案的市场竞争竞争优势是非常简单,可利用现有数据库控制技术的功能同时实现;ID 号单调自增。其缺陷包括强仰赖 DB,当 DB 异常时整个控制技术将处于不可用的状态;ID 号的裂解速率取决于所选用数据库的读写操控性。
2.3. Redis 裂解 ID
当选用数据库来裂解 ID 操控性不够的时候,能尝试选用 Redis 来裂解 ID。主要选用 Redis 的原子操作方式 INCR 和 INCRBY 来同时实现。市场竞争竞争优势是不仰赖于数据库,选用灵活,操控性也优于数据库;而缺点则是可能要引入新的组件 Redis,如果 Redis 出现单点故障问题,则会影响序号服务的可用性。
2.4. Zookeeper 裂解 ID
主要是利用 Zookeeper 的 znode 数据版来裂解序列号,能裂解 32 位和 64 位的数据版号,客户端能选用这个版号来作为惟一的序列号。尽管只须仰赖 zookeeper,并且是多步调用 API,如果在市场竞争较大的情况下,可能只须考虑选用分布式系统控制技术锁,故此种裂解惟一 ID 的方法的操控性在高mammalian的分布式系统控制技术自然生态下不甚理想。
2.5. Snowflake 算法
snowflake(雪花算法)是一个开源的分布式系统控制技术 ID 裂解算法,结果是一个 long 型的 ID。snowflake 算法将 64bit 划分成多段,分开来记号笔记本电脑、日数等重要信息,具体共同组成结构如下表所示表下图图下图:
snowflake 算法的核心思想是选用 41bit 作为毫秒数,10bit 作为笔记本电脑的 ID(比如说其中 5 个 bit 可作为数据服务站,5 个 bit 作为笔记本电脑 ID),12bit 作为毫秒内的流水号(意味着每个节点在每毫秒能产生 4096 个 ID),最后还有一个符号位,永远是 0。
snowflake 算法能根据再者分销的需求展开一定的调整。比如估算未来的数据服务站个数,每个数据服务站内的笔记本电脑数,以及统一毫秒内的mammalian数来调整在算法中所只须的 bit 数。
snowflake 算法的市场竞争竞争优势是稳定性高,不仰赖于数据库等第三方控制技术;选用灵活方便,能根据分销需求的缺点来调整算法中的 bit 位;单机上 ID 单调自增,毫秒数在高位,自增序列在低位,整个 ID 是趋势递增的。而其也存一定的缺陷,包括强仰赖笔记本电脑时钟,如果笔记本电脑上时钟回拨,会导致发号多次重复或是服务处于不可用状态;ID 可能不是由上而下递增,尽管 ID 在单机上是递增的,但是尽管涉及到分布式系统控制技术自然生态下的每个笔记本电脑节点上的时钟,可能会出现不是由上而下递增的情境。
3. 数据库号段模式
3.1. 号段模式介绍
号段模式是当下分布式系统控制技术 ID 裂解器的主流同时实现形式之一,号段模式能理解成从数据库批量获取 ID,然后将 ID 缓存邻近地区,以此来提高分销获取 ID 的效率。比如,每次从数据库获取 ID 时,获取一个号段,如(1,1000],这个范围表示 1000 个 ID,分销应用在请求获取 ID 时,只只须在邻近地区从 1 开始自增并返回,而不用每次去请求数据库,一直到邻近地区自增到 1000 时,才去数据库重新获取新的号段,后续流程循环往复。
3.2. 美团 Leaf-segment 方案
Leaf-segment 号段模式是对直接用数据库自增 ID 充当分布式系统控制技术 ID 的一种优化,减少对数据库的访问频率。相当于每次从数据库批量的获取自增 ID。
Leaf-server 选用了预分发的形式裂解 ID,需先以在 DB 之上挂 N 个 Server,每个 Server 启动时,都会去 DB 拿固定长度的 ID List。这样就做到了完全分布式控制技术的架构,同时因为 ID 是由内存分发,所以也能做到极高效。接下来是数据持久化问题,Leaf 每次去 DB 拿固定长度的 ID List,然后把最大的 ID 持久化下来,也就是并非每个 ID 都做持久化,仅仅持久化一批 ID 中最大的那一个。其流程如下表所示表下图图下图:
Leaf-server 中缓存的号段耗尽之后再去数据库获取新的号段,能大大地减轻数据库的压力。对 max_id 字段做一次 update 操作方式,update max_id = max_id + step,update 成功则说明新号段获取成功,新的号段范围为(max_id, max_id + step]。
为了解决从资料库获取新的号段阻塞分销获取 ID 的流程的问题,Leaf-server 中选用了异步更新的策略,同时通过双 buffer 的形式,如下表所示表下图图下图。通过这样一种机制能保证无论何时 DB 出现问题,都能有一个 buffer 的号段能正常对外提供服务,只有 DB 在一个 buffer 的下发周期内恢复,都不会影响这个 Leaf 集群的可用性。
3.3. 滴滴 Tingid 方案
Tinyid 方案是在 Leaf-segment 的算法基础上升级而来,不仅支持了数据库多主节点模式,还提供了 tinyid-client 客户端的接入形式,选用起来更为方便。
Tinyid 会将可用号段加载到内存中,并在内存中裂解 ID,可用号段在首次获取 ID 时加载,如现阶段号段选用达到一定比例时,控制技术会异步的去加载下一个可用号段,以此保证内存中始终有可用号段,以便在发号服务宕机后一段日数内还有可用 ID。同时实现原理如下表所示表下图下图:
3.4. 微信序列号裂解方案
微信序列号跟普通用户 uin 绑定,具有下列性质:递增的 64 位整形;选用每个普通用户独立的 64 位 sequence 的体系,而不是用一个自上而下的 64 位(或更高位) sequence ,很大原因是由上而下惟一的 sequence 会有非常严重的申请互斥问题,不容易去同时实现一个高操控性高可靠的架构。其同时实现形式包含如下表所示表下图两个关键点:
1)步进式持久化:增加一个缓存中间层,内存中缓存最近一个分配出现的 sequence:cur_seq,以及分配上限:max_seq;分配 sequence 时,将 cur_seq++,与分配上限 max_seq 比较,如果 cur_seq > max_seq,将分配上限提升一个步长 max_seq += step,并持久化 max_seq;重启时,读出持久化的 max_seq,赋值给 cur_seq。此种处理形式能降低持久化的硬盘 IO 次数,能控制技术的整体吞吐量。
2)分号段共享存储:引入号段 section 的概念,uin 相邻的一段普通用户属于一个号段,共享一个 max_seq。该处理形式能大幅减少 max_seq 数据的大小,同时能进一步地降低 IO 次数。
微信序列号服务的控制技术架构图如下表所示表下图图下图:
4. 雪花模式
4.1. 雪花模式介绍
雪花模式同时实现形式详见上面介绍的 snowflake 算法。
尽管雪花算法强仰赖于笔记本电脑日数,如果日数上的时钟发生回拨,则可能引起裂解的 id 冲突的问题。解决该问题的方案如下表所示表下图下图:
将 ID 裂解交给少量服务器,然后关闭这些服务器的时钟回拨能力;当遇到时钟回拨问题时直接报错,交给上层分销来处理;如果回拨日数较短,在费时特别强调范围内,比如 5ms,等待回拨时长后在裂解 id 返回给分销侧;如果回拨日数很长,无法等待,能匀出少量位作为回拨位,一旦日数回拨,将回拨位加 1,可得到不一样的 ID,2 位回拨可允许记号三次时钟较长日数的回拨,大体上够选用。如果超过回拨次数,能再选择报错或抛出异常。4.2. 美团 Leaf-snowflake 方案
Leaf-snowflake 方案沿用 snowflake 方案的 bit 位设计,即1+41+10+12的形式组装 ID 号(正数位(占 1 比特)+ 日数戳(占 41 比特)+ 笔记本电脑 ID(占 5 比特)+ 机房 ID(占 5 比特)+ 自增值(占 12 比特)),如下表所示表下图图下图:
对于 workerID 的分配,当服务集群较小时,通过配置需先;当服务集群较大时, zookeeper 持久顺序节点的缺点引入 zookeeper 组件配置 workerID。部署架构如下表所示表下图图下图:
Leaf-snowflake 方案在处理时钟回拨问题的策略如下表所示表右图下图:
1)服务启动时
在服务启动时,首先检查自己是否写过 zookeeper leaf_forever 节点;如果写过,则用再者控制技术日数与 leaf_forever/${self}节点记录日数做比较,若小于则认为笔记本电脑日数发生了大步长回拨,服务启动失败并告警;如果没写过,直接创建持久节点 leaf_forever/${self},并写入再者控制技术日数;然后取 leaf_temporary 下的所有临时节点(所有运行中的 Leaf-snowflake 节点)的服务 IP:Port,然后通过 RPC 请求得到所有节点的控制技术日数,顺序 sum(time)/nodeSize;如果若 abs( 控制技术日数-sum(time)/nodeSize ) < 阈值,认为现阶段控制技术日数准确,正常启动服务,同时写临时节点 leaf_temporary/${self} 维持租约;否则认为本机控制系统日数发生大步长偏移,启动失败并报警;每隔一段日数(3s)上报再者控制技术日数写入 leaf_forever/${self}。2)服务运行时
会检查时钟回拨日数是否小于 5ms,若时钟回拨日数小于等于 5ms,等待时钟回拨日数后,重新产生新的 ID;若时钟回拨日数大于 5ms,直接抛异常给到分销侧。4.3. 腾讯 UidGenerator 方案
UidGenerator 方案是 snowflake 算法的惟一 ID 裂解器。其对雪花算法的 bit 位的分配做了微调,如下表所示表下图图下图:
UidGenerator 方案包含下列两种同时实现形式:
1)DefaultUidGenerator 同时实现形式
DefaultUidGenerator 形式的同时实现要点如下表所示表下图下图:
delta seconds:在上图中用 28bit 部份表示,指现阶段日数与 epoch 日数的日数差,单位为秒。epoch 日数指集成 DefaultUidGenerator 裂解分布式系统控制技术 ID 服务第一次上线的日数,可配置。worker id:在上图中用 22bit 部份表示,在选用 DefaultUidGenerator 形式裂解分布式系统控制技术 ID 的实例启动的时候,往 db 中写入一行数据得到的自增 id 值。尽管 worker id 默认 22 位,允许集成 DefaultUidGenerator 裂解分布式系统控制技术 id 的所有实例的重启次数不超过 4194303 次,否则会抛出异常sequence:在上图中用 13bit 部份表示,通过 synchronized 保证线程安全;如果日数有任何的回拨,直接抛出异常;如果现阶段日数和上一次是同一秒日数,sequence 自增,如果同一秒内自增至超过 2^13-1,自旋等待下一秒;如果是新的一秒,sequence 从 0 开始。DefaultUidGenerator 形式在出现任何刻度的时钟回拨时都会直接抛异常给到分销层,同时实现比较简单粗暴。故选用 DefaultUidGenerator 形式裂解分布式系统控制技术 ID,只须根据分销情况和特点,调整各个字段占用的位数。
2)CachedUidGenerator 同时实现形式
CachedUidGenerator 的核心是利用 RingBuffer,本质上是一个字符串,字符串中每个项被称为 slot。CachedUidGenerator 设计了两个 RingBuffer,一个保存惟一 ID,一个保存 flag。其同时实现要点如下表所示表下图下图:
自增列:UidGenerator 的 workerId 在实例每次重启时初始化,且就是数据库的自增 ID,从而完美的同时实现每个实例获取到的 workerId 不会有任何冲突。RingBuffer:UidGenerator 不再在每次取 ID 时都实时顺序分布式系统控制技术 ID,而是利用 RingBuffer 数据结构预先裂解若干个分布式系统控制技术 ID 并保存。日数递增:UidGenerator 的日数类型是 AtomicLong,且通过 incrementAndGet()方法获取下一次的日数,从而脱离了对服务器日数的仰赖,也就不会有时钟回拨的问题。4.4. 多日数线改进的雪花算法
多日数线改进的雪花算法在 snowflake 基础上增加了日数线部份(1~2 位),可同时支持 2~4 条日数线并行。其对雪花算法的 bit 位的分配做了微调,如下表所示表下图图下图:
多日数线改进的雪花算法裂解 ID 过程如下表所示表下图下图:
初始时,所有日数线进度均为基准日数,随机选定一条日数线作为现阶段日数线;在现阶段日数线上裂解 ID,同时推进现阶段日数线进度;一旦发生时钟回退,且回退距离小于一定阈值,等待日数推进直到回退前的日数,会到步骤 2 继续裂解 ID;如果回退距离大于阈值,暂停现阶段日数线进度,选择一条合适的日数线(进度<现阶段日数)并切换到该日数线,回到步骤 2 继续裂解 ID。如果找不到合适的日数线,报错返回。该方案尽管通过设置日数线形式有效解决了时钟回退问题,但是削弱了 snowflake 的趋势递增缺点。比较适合对于许多频繁地、小步长的时钟回退情况,即能做到由上而下惟一,又能很好地兼顾递增趋势。