关于在分布式系统里如何生成各种 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 | +---------------+----------------+----------------+ |
总结
ID 生成器有很多很好的方案,像 Twitter 的 Snowflake,大型项目非常推荐,但对于小项目来说,一个可行的方案不仅是速度,可行性,还有非常重要的成本。
这个方案,是实践下来一个很不错的方案。