简单的 ID 生成器设计

关于在分布式系统里如何生成各种 ID 的问题,对于小的业务来说一直是件麻烦的事情。

小业务注定不像大厂的服务一样,有超多的服务器,很多的开发者组合,所以方案要求简单,并不太高的性能。

ID 要求

  • 唯一性
  • 保护业务隐私(不能透漏有多少用户数)
  • 数值型(更少占用空间)
  • 单向增长,精确有序 (写入性能保证)

常见的方式是有两种:

  • 中心式发号器
  • 单机发号规则

中心式发号器需要额外的服务,增加了成本,一旦发号器出了问题,会影响业务。

单机发号,在号码的规则中,约定一定的位数属于某台单独的机器,也需要在机器上执行一定的配置。

这两种对于小企业小业务来讲,都不方便。

用户 ID 的方案

该方案仅使用数据库,搞一个 Cron 进程,不断的维护一个表的 ID 队列,等待业务去消费。

简单的说,做一个小的业务规则。不断的向数据库中生成一些等待使用的 ID,然后业务在用时,从数据库中查最小可用的 ID,做为本次的实例的 ID 即可。

> 流程

用户 ID 从 3000001 开始,从 ID 中选最后一条记录,然后随机 (0-9 间)选一个数值用作跳跃值,然后加在最后一个 ID 上,生成一个新 ID,写入表中。

通过 Cron 进程,在用户表中,维护 1000 个这样的未消费 ID 队列(小时级别的调度就可以了)。

每次有新用户注册,就取最小一个未消费的 ID 值。然后更新状态到待消费。

等用户填写完信息后,将该值变成正常用户状态。

状态值

  • unspent
  • pending
  • normal
  • disable

其中 unspent/pending 这两个就是为了 ID 生成器所专门设计。

- 好处

好处是,简单啊,哪怕有多个服务,也不用关心 ID 的问题,解决了分布式的麻烦。

通过数据库的解决方案,成本较小。实施简单。

订单 ID 的方案

订单和用户不同的是,定单可以使用大长度数值。

不同于普通 ID,定单还有额外的要求:

  • 可以快速分析出有用信息 (在定单中加入时间内容)

- 关于 uint64

uint64 是定单所使用的 ID 值生成范围:

uint64 取值范围:0 - 18446744073709551615

去掉第一个 1 后,可以拆解为如下字段:

1
8446 7440 7370 9551 615

- 符合 uint64 的设计

基于这样的要求,将时间作为定单上的信息,时间天然是自增长的。

1
2020 0210 1212 1455 123
  • 2020 0210 - 日期
  • 1212 1455 - 时间(厘秒单位)中午 12 点 12 分 14 秒 55 厘秒
  • 123 - 随机数

当然也可以把前面的 20 去掉,毕竟一个系统存活几十年的概率几乎为零。

1
2020 0210 1212 1455 123

这样可以在时间精度上进行加长,并且在随机数上进行加长,可以根据实际情况来做调整

1
2002101212 14550 123

在一个小型定单系统里,在厘秒的时间段内+三位随机数,重复的概率非常低(没有做过大型测试),但理论上非常可行。

1
2
3
4
+---------------+----------------+----------------+
|timestamp(cs)14 | timestamp(cs)2 | random(3) |
+---------------+----------------+----------------+
id = sign + delta seconds | workerid | sequence

总结

ID 生成器有很多很好的方案,像 Twitter 的 Snowflake,大型项目非常推荐,但对于小项目来说,一个可行的方案不仅是速度,可行性,还有非常重要的成本。

这个方案,是实践下来一个很不错的方案。