Distributed ID Generator

Table of content:

About

这篇文章是对业内 ID 生成方案的一些调研和思考, 后续会增加一些实现。

发号器需要哪些特性

特性说明
唯一性这是发号器的基础要求
高并发能够在很多时间内生成大量的唯一 id
高可用不能一台机器 down 了,整个发号器都不可用
按照时间排序构建索引,按照范围查询
可反解通过反解,能够快速定位问题
格式优雅通常对外的 id 要求足够短
不容易被猜出比如自增 id 就是顺着 + 1,这样很容易被猜到

方案

UUID

UUID 算法,生成的 ObjectId 占 12 个字节,由以下几个部分组成,

  • 4 个字节表示的 Unix timestamp,
  • 3 个字节表示的机器的 ID
  • 2 个字节表示的进程 ID
  • 3 个字节表示的计数器

UUID 是一类算法的统称,具体有不同的实现。UUID 的有点是每台机器可以独立产生 ID,理论上保证不会重复,所以天然是分布式的,缺点是生成的 ID 太长,不仅占用内存,而且索引查询效率低。UUID 是由 128 位二进制组成,一般转换成十六进制,然后用 String 表示。

生成策略包括:

  • 随机生成
  • 通过当前时间,随机数,和本地 Mac 地址来计算出来
  • 基于名字的 UUID,通过计算名字和名字空间的 MD5 来计算 UUID

优点:本地生成,生成简单,性能好,没有高可用风险
缺点:长度过长,存储冗余,且无序不可读,查询效率低

数据库自增 ID

使用数据库的 id 自增策略,如 MySQL 的 auto_increment。并且可以使用两台数据库分别设置不同步长,生成不重复 ID 的策略来实现高可用, 一个例子:

Server1:
auto-increment-increment = 2
auto-increment-offset = 1

Server2:
auto-increment-increment = 2
auto-increment-offset = 2

优点:数据库生成的 ID 绝对有序,高可用实现方式简单
缺点:

  • 需要独立部署数据库实例,成本高,有性能瓶颈
  • 分库分表会带来问题,需要进行改造
  • 不是严格实现的全局递增
  • 简单递增容易被其他人猜测利用

Flickr 使用了类似的方案, http://code.flickr.net/2010/02/08/ticket-servers-distributed-unique-primary-keys-on-the-cheap/

Redis

Redis 是单线程的,所以也可以用生成全局唯一的 ID。可以用 Redis 的原子操作 INCR 和 INCRBY 来实现。
使用 Redis 集群来获取更高的吞吐量。假如一个集群中有 5 台 Redis。可以初始化每台 Redis 的值分别是 1,2,3,4,5,然后步长都是 5。各个 Redis 生成的 ID 为:

A:1,6,11,16,21
B:2,7,12,17,22
C:3,8,13,18,23
D:4,9,14,19,24
E:5,10,15,20,25

优点:

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

缺点:

  • 如果系统中没有 Redis,还需要引入新的组件
  • 需要配置的工作量比较大

Twitter 的 snowflake 算法

Twitter 利用 zookeeper 实现了一个全局 ID 生成的服务 Snowflake:https://github.com/twitter-archive/snowflake

如上图的所示,Twitter 的 snowflake 算法下面几部分组成:

  • 1 位一般是符号位,不做处理
  • 41 位的时间序列,精确到毫秒,可以使用 69 年
  • 10 位的机器标识,最多支持部署 1024 个节点
  • 12 位的序列号,支持每个节点每毫秒产生 4096 个 ID 序号,最高位是符号位始终为 0。

这种方案性能好,在单机上是递增的, 但是不是全局递增,但是由于涉及到分布式环境,每台机器上的时钟不可能完全同步,也许有时候也会出现不是全局递增的情况。
而且这个项目在 2010 就停止维护了,但这个设计思路还是应用于其他各个 ID 生成器及变种。

国内有很多厂家基于 snowflake 算法进行了国产化,例如:

TDDL

TDDL 是阿里的分库分表中间件,它里面包含了全局数据库 ID 的生成方式,主要思路:

  • 使用数据库同步 ID 信息。
  • 每次批量取一定数量的可用 ID 在内存中,使用完后,再请求数据库重新获取下一批可用 ID,每次获取的可用 ID 数量由步长控制,实际业务中可根据使用速度进行配置。
  • 每个业务可以给自己的序列起个唯一的名字,隔离各个业务系统的 ID。

常见问题

基于时间戳的方案的时间戳同步以及时钟回拨等问题怎么处理(todo)

Reference 和推荐阅读

关于头图

拍摄自水魔方

2019 年 1 月摘要
Run sshd in Docker container