发号器技术方案调研

811 阅读3分钟

为什么需要发号器

在分布式应用中,经常需要生成全局唯一的ID来保证数据不重复,例如订单号,交易批次号,uid等等。

发号器必须具有的特性

  • crash safe
    • 即需要保证在服务崩溃重新恢复后,不会产生已经发过的ID
  • 不受外部环境变化影响
    • 很多发号器实现是基于时间戳的。但是有些实现直接采用了机器上的时间戳作为 ID 的一部分。如果机器时间发生回跳(例如同步时间服务器校正时间),就会造成 ID 重复
  • 高可用
    • 发号器作为各项服务的关键环节,如果一但无法响应会影响大面积的应用无法使用
    • 同时作为全局唯一ID生成器,提供给所有业务方使用,必须支撑足够大的吞吐量

现有解决方案参考

SnowFlake

推特开源的分布式自增ID算法,41bit留给毫秒时间,10bit给MachineID,也就是机器要预先配置,剩下12位留给Sequence。

理论上单机速度:2 ^ 12 * 1000 = 4,096,000/s

由于绝对多数应用每秒不需要400W个ID,可以考虑精简该算法,从64位缩短到53位

53位bit,ID由32bit秒级时间戳+16bit自增+5bit业务标识组成,累计32个业务

理论上单机速度: 2 ^ 16 = 65536/s

缩短到53位是考虑到web应用要和javascript打交道,而JavaScript支持的最大整型就是2^53,超过这个位数,JavaScript将丢失精度,就必须要转化为字符串数字,缩短到53位可以不用做转化

基于时间戳

比如流水号规则如下:XX-YYYYMMDD-N位随机数,这也是企业级应用开发常用的规则。此流水号对人比较友好,可识别性高,但容量受后面随机数的限制,且数据量越大,生成时难度越高。前三部分每天的流水号基本固定,后面的N位随机数生成后,需要校验此前不存在,可依赖redis的set机制,每天的随机数都写到一个set集合中[set容易达42亿之多,完全够用],重新生成后要与set集合作比对,以确保其唯一性。一天内不重复,再结合确定日期来保证其唯一性。

N位随机数生成时,可基于系统时间戳,再与一个大数取模生成

基于redis的分布式ID生成器

GitHub 地址:github.com/hengyunabc/…

依赖redis的EVAL,EVALSHA两个命令,利用redis的lua脚本执行功能,在每个节点上通过lua脚本生成唯一ID。 生成的ID是64位的:

使用41 bit来存放时间,精确到毫秒,可以使用41年。

使用12 bit来存放逻辑分片ID,最大分片ID是4095

使用10 bit来存放自增长ID,意味着每个节点,每毫秒最多可以生成1024个ID

Redis提供了TIME命令,可以取得redis服务器上的秒数和微秒数。因些lua脚本返回的是一个四元组。

second, microSecond, partition, seq

客户端要自己处理,生成最终ID。

((second * 1000 + microSecond / 1000) << (12 + 10)) + (shardId << 10) + seq;

UUID

PHP使用 扩展 PECL :: Package :: uuid生成

使用方法

echo uuid_create(1)