如何实现一个64 bit ID Unique Generator

如何实现一个64 bit ID Unique Generator

Requirements

  • 要求ID唯一但非自增长
  • 高性能,节点/实例可以扩展 (Scability)
  • 节点/实例重启后可以接着分配ID (Durability)

Scability 的考虑

要求可以多实例扩展,那就需要每个实例需要有唯一的Identity来参与生成 ID 的分段。

如果这个实例是无状态实例,那么这个实例被scale in 之后,该 identity 分段的ID 也不能就此废弃,等新的实例被scale out 出来后,需要重复利用这个分段的ID.

为什么不能使用有状态的实例, 使用实例自身状态的identity 来做分段呢? 例如 POD-0, POD-1 之类的就用 0,1 之类的来标识分段。 因为这样的话,假如 0 标识的这个分段用完了,那 POD-0 就不能再 分配ID 了。
而如果无状态的POD, 而 Identity 是从其他地方获取的,和自身无关,那么这个identity 用完了,可以申请获取新的identity,然后又可以重新分配新的段的ID 了。

这里可以使用zookeeper 来分配 instance 的 identity.

Durability

当实例或者说pod 重启之后,client 重新申请ID 时,可以被分配到没有被使用的ID 或者是接着之前用的ID 段的新ID.

那就需要将这部分的已经分配了哪些 ID 的 信息/状态 保持到数据库中,以实现持久化。

实现

ID 的结构如下,其中每个worker 可用的位数是 40 bit, 如果按照1000 tps 来算,每个实例可以使用 34 年。计算公式如下 (2^40 -1 )/1000/31536000 = 34 years.

+--------+-----------+----------------+----------------------------------------+
| 63     | 62 - 56   | 55 - 40        | 39 - 0                                 |
+--------+-----------+----------------+----------------------------------------+
| sign   |  zone id  |   worker id    |               sequence                 |
| 1 bit  |  7 bits   |    16 bits     |               40 bits                  |
+--------+-----------+----------------+----------------------------------------+

sign:最高位(第63位),表示符号位(一般为0表示正数)。

zone id:接下来的7位(62到56),代表逻辑区域或机房。

worker id:16位(55到40),代表节点/实例/POD的 ID。

sequence:剩下的40位(39到0),表示这个段内可用的 ID。

这个ID Generator 的实现可以是一个lib,也可以是一个service.

以lib 的实现方式为例子, 需要提供一个接口来分配ID.


public long next();

lib 也需要连接 zookeeper 去获取worker ID, 去 数据库 存放 当前这个worker ID 最大已经分配到那个ID 了。
还需要一些配置来定义是属于那个business 的, 以便在 zookeeper 创建对于的 属于这个 business 的 worker 节点。 例如 /idg/business-1/worker-- 这种znode.

而 parent znode /idg/business-1 是持久化的,而子节点 worker-N 这种节点就是 临时节点。 在parent 节点上的value 上持久化worker ID 和 instance/POD 的mapping关系。 例如

{"workerId2Node":{"2":"/idg/business-1/_w_-144807203059138568-0000000002","3":"/idg/business-1/_w_-144807203059138569-0000000003","0":"/idg/business-1/_w_-144807472447553536-0000000004","1":"/idg/business-1/_w_-144807472447553537-0000000005"}}

这样当POD 连接到zk 的时候,就可以通过子节点的名字就可以知道自己有没有已经分配到 worker ID 了,如果没有就新创建一个。 如果有,则可以通过读取worker-N 的这种临时节点的内容去获取worker ID.

另外一个问题是,假如一个pod 断开很久,临时节点已经消失了,那重新连接上ZK,那么之前旧的worker id mapping信息依然存在于parent 节点,但是其实际对应的临时节点已经消失了。 这个时候就可以重新读取parent 节点下所有的子节点list,然后比较parent节点的value来去掉value 中已经不存在的mapping信息对,并且排序查看那个worker ID对应的mapping 信息被去掉了,那就重新使用这个已经不存在mapping信息的 worker ID 来关联本session. 这样就可以达到复用 worker ID 的目标了。

另外一个就是lib 可以在申请ID 时,可以一段一段地申请。 例如一次申请1000 个ID 来允许 next() 接口使用。
next() 接口使用的ID 已经不够 500 了,就触发异步线程获取另外 1000 个ID 来备用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值