分布式ID | 青训营笔记

129 阅读4分钟

这是我参与「第五届青训营 」笔记创作活动的第13天

分布式唯一ID特征

在业务开发中,会存在大量的场景都需要唯一 ID 来进行标识。比如,用户需要唯一身份标识;商品需要唯一标识;消息需要唯一标识;事件需要唯一标识等等。尤其是在分布式场景下,业务会更加依赖唯一 ID。

分布式唯一 ID 的特性如下:

  • 全局唯一:必须保证生成的 ID 是全局性唯一的,这是分布式 ID 的基本要求;
  • 有序性:生成的 ID 需要按照某种规则有序,便于数据库的写入和排序操作;
  • 可用性:需要保证高并发下的可用性。除了对 ID 号码自身的要求,业务还对 ID 生成系统的可用性要求极高;
  • 自主性:分布式环境下不依赖中心认证即可自行生成 ID;
  • 安全性:不暴露系统和业务的信息。在一些业务场景下,会需要 ID 无规则或者不规则。

常用分布式唯一ID生成方案

UUID

UUID 一共有 5 个版本:

  • 版本1 - 基于时间的 UUID:主要依赖当前的时间戳及机器 mac 地址,因此可以保证全球唯一性。
  • 版本2 - 分布式安全的 UUID:将版本1的时间戳前四位换为 POSIX 的 UID 或 GID,很少使用。
  • 版本3 - 基于名字空间的 UUID(MD5 版):基于指定的名字空间/名字生成 MD5 散列值得到,标准不推荐。
  • 版本4 - 基于随机数的 UUID:基于伪随机数,生成 16byte 随机值填充 UUID。重复机率与随机数产生器的质量有关。
  • 版本5 - 基于名字空间的 UUID(SHA1版):将版本 3 的散列算法改为 SHA1。

大家多数情况下使用的是 v4 版本,Node.js 的 uuid 包支持所有版本的 uuid 生成。Java 的 UUID.randomUUID() 是基于 v4 版本,UUID.nameUUIDFromBytes() 是基于 v3 版本。

UUID 的优势:

  • 本地生成,不依赖外部服务,生成的性能也还不错。

UUID 的劣势:

  • v1 版本存在信息安全问题,直接将 mac 地址暴露出去,这个漏洞曾被用于寻找梅丽莎病毒的制作者位置。
  • v3、v5 都是在命名空间 + 名称输入的情况下可以输出统一的 UUID,不适合用于唯一 ID 生成。
  • v4 版本如果基于伪随机数,理论上会存在出现重复的概率。
  • 通常在数据库中存储为字符串,相比整型会花费更多存储空间。
  • 字符串无法保证有序,在 MySQL 基于 B+ 树的聚簇索引结构下,写入性能不佳。

在一些简单场景下,对于性能的要求不严格,并且系统并发不高的情况下,使用 UUID 可能是最简单、最低成本的方案。

Rdis生成ID

Redis的所有命令操作都是单线程的,本身提供像 incr 和 increby 这样的自增原子命令,所以能保证生成的 ID 肯定是唯一有序的。

  • 优点:不依赖于数据库,灵活方便,且性能优于数据库;数字ID天然排序,对分页或者需要排序的结果很有帮助。

  • 缺点:如果系统中没有Redis,还需要引入新的组件,增加系统复杂度;需要编码和配置的工作量比较大。

使用 Redis 来生成每天从0开始的流水号。比如订单号 = 日期 + 当日自增长号。可以每天在 Redis 中生成一个 Key ,使用 INCR 进行累加。

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 中最大的那一个。其流程如下图所示:

image.png 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 集群的可用性。