【分布式】分布式ID
本文为分布式学习笔记,参考了JavaGuide
设计分布式ID有两种思路:
- 第一种是让所有获取ID的机器从同一个地方获取,ID生成器只需要简单自增即可。
- 第二种是通过一定的算法,使各个机器产生的ID不重复,这也是一种方式。
同一ID生成方式
数据库
数据库主键自增
利用数据库主键自增来产生唯一ID,通过在同一个事务中replace into
插入,让着通过last_insert_id()
函数来获取ID。
例子:
首先创建一张表
CREATE TABLE `sequence_id`
(
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`stub` char(10) NOT NULL DEFAULT '站位字段',
PRIMARY KEY (`id`),
UNIQUE KEY `stub` (`stub`)
);
stub为占位字段,方便插入修改数据,有唯一索引。
然后通过一下方式获取一个唯一ID:
BEGIN;
REPLACE INTO sequence_id (stub) VALUES ('stub');
SELECT LAST_INSERT_ID();
COMMIT;
优缺点:
- 优点:实现起来比较简单、ID 有序递增、存储消耗空间小
- 缺点:支持的并发量不大、存在数据库单点问题、ID 没有具体业务含义、安全问题(比如根据订单 ID 的递增规律就能推算出每天的订单量,商业机密啊!)、每次获取 ID 都要访问一次数据库(增加了对数据库的压力,获取速度也慢)
数据库号段模式
相比第一种每次获取ID都需要访问一次数据库,第二种方式是批量获取ID,然后放在内存中,避免了每次都需要访问数据库的弊端。
TinyID 就是基于这种原理。
例子:
CREATE TABLE `sequence_id_generator`
(
`id` int(10) NOT NULL,
`current_max_id` bigint(20) NOT NULL COMMENT '当前最大id',
`step` int(10) NOT NULL COMMENT '号段的长度',
`version` int(20) NOT NULL COMMENT '版本号',
`biz_type` int(20) NOT NULL COMMENT '业务类型',
PRIMARY KEY (`id`)
);
current_max_id
和step
用来获取ID,version
用来解决并发问题,biz_type
用来区分业务类型。
插入一条初始数据:
INSERT INTO `sequence_id_generator` (`id`, `current_max_id`, `step`, `version`, `biz_type`)
VALUES (1, 0, 100, 0, 101);
每次获取ID时,先查询然后再更新,如果更新没成功则需要重新查询并更新:
select id, current_max_id, step, version, biz_type
from sequence_id_generator
where biz_type = 101;
-- 1,100,100,1,101
UPDATE sequence_id_generator
SET current_max_id = 0 + 100,
version = version + 1
WHERE version = 0
AND `biz_type` = 101;
相比于数据库主键自增方式,数据库的号段模式对于数据库的访问次数更少,数据库压力更小,并且可以使用主从模式提高可用性。
优缺点:
- 优点:ID 有序递增、存储消耗空间小
- 缺点:存在数据库单点问题、ID 没有具体业务含义、安全问题
开源实现有TinyID和Leaf。
NoSQL
Redis
一般NoSQL使用Redis比较多,通过Redis的incr
命令可以实现ID原子顺序递增。
本地:db0> set seq 1
OK
本地:db0> incr seq
2
本地:db0> get seq
2
为了提高可用性和高并发,可以将Redis集群化,可以使用官方集群解决方案Redis Cluster或者Codis。
Redis基于内存,防止宕机后数据丢失,需要开启持久化,两种持久化方式:RDB和AOF,RDB是快照模式,AOF是保存修改命令方式。
优缺点:
- 优点:性能优良、ID有序自增
- 缺点:和数据库ID自增类似
MongoDB
MongoDB ObjectId也会拿来做分布式ID。
MongoDB ObjectId 一共需要 12 个字节存储:
- 0~3:时间戳
- 3~6:代表机器 ID
- 7~8:机器进程 ID
- 9~11:自增值
优缺点:
- 优点:性能优良、ID有序自增
- 缺点:ID可能重复(时间不对时)、安全问题
算法
UUID
UUID包含32个16进制数,一般不会用来做数据库主键。
- 主键尽量越短越好,UUID为32位字符串,总共128位
- UUID 是无顺序的,InnoDB 引擎下,数据库主键的无序性会严重影响数据库性能
优缺点:
- 优点:生成速度快、简单易用
- 缺点:存储消耗空间大、无序、需要解决重复ID问题(时间设置重置后可能会有重复ID)
雪花算法
雪花算法由64 bit二进制数字组成,其中每一部分都有特定含义:
1 + 41 + 10 + 12
- sign(1bit):符号位(标识正负),始终为 0,代表生成的 ID 为正数。
- timestamp (41 bits):一共 41 位,用来表示时间戳,单位是毫秒,可以支撑 2 ^41 毫秒(约 69 年)
- datacenter id + worker id (10 bits):一般来说,前 5 位表示机房 ID,后 5 位表示机器 ID
- sequence (12 bits):一共 12 位,用来表示序列号。 序列号为自增值,代表单台机器每毫秒能够产生的最大 ID 数(2^12 = 4096),也就是说单台机器每毫秒最多可以生成 4096 个 唯一 ID
优缺点:
- 优点:生成速度比较快、生成的 ID 有序递增、比较灵活(可以简单改造,比如加入业务ID)
- 缺点:需要解决重复 ID 问题(ID生成依赖时间,时间回拨时会生成重复ID)、依赖机器ID对分布式环境不友好
一般使用雪花算法不需要自己造轮子,有许多开源实现,比如Leaf(支持数据库号段模式和雪花算法模式)、UidGenerator(改良雪花算法)、IdGenerator等。