1.常用的分布式ID生成方法
在应用程序中,经常需要全局唯一的ID用作标识,可能是需要入库数据的主键,也可能是发出的消息的唯一性标识等。这种ID一般有一些一些要求:
关于这样的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 解决回拨的策略:
- 决绝:直接拒绝回拨请求,有上层业务决定如何处理。
- 等待:如果发现回拨的时间很小,比如1ms,那么主动阻塞等待1ms可能也是可以接受的简单策略。
- 利用特殊标志位:标准的snowflake,每ms可以生产4096个ID,一般的服务都不会有这么大的并发量。我们可以拿出一部分数据段专门用来处理回拨的请求,比如最高位为1的那部分数据正常情况是不使用的,只有在收到回拨请求(当前系统时间小于已经处理过的最大时间)的时候才使用。
2.5.5 超发问题(应对流量洪峰)
在标准snowflake的策略下,如果单台服务器对ID的需求在某毫秒超过了4096个,可以想下一毫秒去借,直接将当前的处理毫秒数增加1。
这时候所有的请求都会判断为回拨请求,但我们知道这是合理的,不会有产生重复ID的风险,我们可以用一个标志位标示当前的ID生成器处于超发状态。不用关心回拨请求,当机器时间最上自己的时候再将状态置为正常状态。