什么是分布式ID?
分布式系统全局唯一的 id
分布式ID需要满足什么条件?
- 唯一性:必须保证ID是全局性唯一的,基本要求
- 高性能:高可用低延时,ID生成响应要块,否则反倒会成为业务瓶颈
- 高可用:100%的可用性是骗人的,但是也要无限接近于100%的可用性
- 有序性:最好趋势递增,这个要求就得看具体业务场景了,一般不严格要求
- 易拓展:要秉着拿来即用的设计原则,在系统设计和实现上要尽可能的简单
有哪些分布式ID的生成方式?
- UUID
- 数据库自增ID
- 数据库多主模式
- 号段模式
- Redis
- 雪花算法(SnowFlake)
- 滴滴出品(TinyID)
- 百度 (Uidgenerator)
- 美团(Leaf)
UUID
UUID, 自不必多说, 简单好用, 全球任何机器执行下都唯一
执行后, 生成一条类似订单号的字符串 c2b8c2b9e46c47e3b30dca3b0d447718
缺点是:
- 字符过长, 占用大量存储空间
- 字符串是无序的, 对于查询, 排序的性能影响很大
数据库自增ID
数据库的 auto_increment
自增ID完全可以充当分布式ID
需要一个单独的MySQL实例用来生成ID, 在业务量不是很大时使用比较方便
但不推荐在业务量大的情况下使用这种方式, 单机数据库无法承受足够大的压力
数据库多主模式
既然一台无法达成效果, 那么就多加一台。
但是会存在一个问题: 生成重复的ID怎么办?
解决问题也有, 给每一个MySQL实例设置不同的步长, 区分开来, 就可以了
但随着业务增长, 机器也可能会逐步累加, 每增加一次, 就要连带着以往的Mysql实例配置的ID步长都修改
不方便扩容
号段模式
号段模式是当下分布式ID生成器的主流实现方式之一,号段模式可以理解为从数据库批量的获取自增ID,每次从数据库取出一个号段范围
例如 (1,1000] 代表1000个ID,具体的业务服务将本号段,生成1~1000的自增ID并加载到内存。表结构如下:
1 | CREATE TABLE id_generator ( |
biz_type :代表不同业务类型
max_id :当前最大的可用id
step :代表号段的长度
version :是一个乐观锁,每次都更新version,保证并发时数据的正确性
等这批号段ID用完,再次向数据库申请新号段,对 max_id
字段做一次update操作,
update max_id= max_id + step
update成功则说明新号段获取成功,新的号段范围是 (max_id ,max_id + step]
。
由于多业务端可能同时操作,所以采用版本号 version
乐观锁方式更新
这种分布式ID生成方式不强依赖于数据库,不会频繁的访问数据库,对数据库的压力小很多。
基于Redis模式
Redis也同样可以实现,原理就是利用 redis
的 incr
命令实现ID的原子性自增。
用 redis
实现需要注意一点,要考虑到 redis
持久化的问题。 redis
有两种持久化方式 RDB
和 AOF
RDB
会定时打一个快照进行持久化,假如连续自增但redis没及时持久化,而这会Redis挂掉了,重启Redis后会出现ID重复的情况。AOF
会对每条写命令进行持久化,即使Redis挂掉了也不会出现ID重复的情况,但由于incr命令的特殊性,会导致Redis重启恢复的数据时间过长。
基于雪花算法(Snowflake)模式
雪花算法(Snowflake)是twitter公司内部分布式项目采用的ID生成算法,
开源后广受国内大厂的好评,在该算法影响下各大公司相继开发出各具特色的分布式生成器。
Snowflake生成的是Long类型的ID,一个Long类型占8个字节,每个字节占8比特,也就是说一个Long类型占64个比特。
Snowflake ID组成结构:
正数位
(占1比特)+ 时间戳
(占41比特)+ 机器ID
(占5比特)+ 数据中心
(占5比特)+ 自增值
(占12比特),
总共64比特组成的一个Long类型。
- 第一个bit位(1bit):Java中long的最高位是符号位代表正负,正数是0,负数是1,一般生成ID都为正数,所以默认为0。
- 时间戳部分(41bit):毫秒级的时间,不建议存当前时间戳,而是用(当前时间戳 - 固定开始时间戳)的差值,可以使产生的ID从更小的值开始;41位的时间戳可以使用69年,(1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69年
- 工作机器id(10bit):也被叫做
workId
,这个可以灵活配置,机房或者机器号组合都可以。 - 序列号部分(12bit),自增值支持同一毫秒内同一个节点可以生成4096个ID