分布式唯一Id生成

1.常用的分布式ID生成方法

在应用程序中,经常需要全局唯一的ID用作标识,可能是需要入库数据的主键,也可能是发出的消息的唯一性标识等。这种ID一般有一些一些要求:

  • 长整型。
  • 趋势自增。
  • 全局唯一。

    2.常见的生成策略

关于这样的ID的常见生成方式有一下几种

2.1 数据库自增ID

依赖数据库(如Mysql)的auto_increment主键自增能力,每次需要生成ID时,向数据库插入一条数据,得到插入数据的自增ID值即为唯一的ID。

  • 优点:实现简单。
  • 缺点:有性能瓶颈和单点故障(SPOF)。

2.2 多台数据库自增ID

针对2.1中的缺点,将系统拓展到多台数据库。比如5台数据库组成的系统。

数据库编号 起始 步长 生成的id序列
1 1 5 1,6,11……
2 2 5 2,7,12……
3 3 5 3,8,13……
4 4 5 4,9,14……
5 5 5 5,10,15……
  • 优点:解决单点故障风险,性能相对单台较好。
  • 缺点:需要在业务层实现路由。无法横向拓展(由于步长=数据库数量)。

2.3 依赖支持原子inc(1)的数据库

很多数据库都支持原子的inc,比如redis天然支持,mysql也可以通过数据库自身的锁来实现。

有了这样一个inc能力,我们每次inc(1)来实现自增id的获取。

  • 优点:实现简单。
  • 缺点:每次都需要依赖外部的存储介质,效率不高。

2.4 依赖支持原子inc(n)的数据库

针对2.2中的问题,每次调用inc不是增1,而是增加n,代表客户端一次性申请n个个id,这样处理可以将外部介质的并缩小n倍

2.5 snowflake算法

将一个long型(64bit)分为4个部分:

部分 起始 步长
part1 1 标识符,始终是0
part2 41 时间戳,单位ms。
2^41-1 个毫秒,约69年。
part3 10 服务内机器编号
支持1024台机器
part4 12 每台机器每毫秒可生产4096个ID

2.5.1 调整各个part

这4个部分的长度不是固定的,是可以相互协调的。比如你的某个服务机器很少(小于1024),但并发量大于 4096/ms,那么就可以减小part3,增大part4。

2.5.2 优缺点

优点:

  • 没有外部依赖(分配机器ID可能会有外部依赖,比如zk等,但只在服务启动时需要),性能好。
  • 时间戳在高位,趋势自增。
  • 灵活度高。

缺点:

  • 依赖机器的时钟,如果服务器时钟回拨(闰秒回拨,NTP同步,手动调整都可能造成回拨),会导致重复ID生成。

2.5.4 解决回拨的策略:

  1. 决绝:直接拒绝回拨请求,有上层业务决定如何处理。
  2. 等待:如果发现回拨的时间很小,比如1ms,那么主动阻塞等待1ms可能也是可以接受的简单策略。
  3. 利用特殊标志位:标准的snowflake,每ms可以生产4096个ID,一般的服务都不会有这么大的并发量。我们可以拿出一部分数据段专门用来处理回拨的请求,比如最高位为1的那部分数据正常情况是不使用的,只有在收到回拨请求(当前系统时间小于已经处理过的最大时间)的时候才使用。

2.5.5 超发问题(应对流量洪峰)

在标准snowflake的策略下,如果单台服务器对ID的需求在某毫秒超过了4096个,可以想下一毫秒去借,直接将当前的处理毫秒数增加1。

这时候所有的请求都会判断为回拨请求,但我们知道这是合理的,不会有产生重复ID的风险,我们可以用一个标志位标示当前的ID生成器处于超发状态。不用关心回拨请求,当机器时间最上自己的时候再将状态置为正常状态。

参考

https://juejin.im/post/5b3a23746fb9a024e15cad79