本文为分布式学习笔记,参考了JavaGuide


设计分布式ID有两种思路:

  1. 第一种是让所有获取ID的机器从同一个地方获取,ID生成器只需要简单自增即可。
  2. 第二种是通过一定的算法,使各个机器产生的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_idstep用来获取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等。

标签: 分布式

添加新评论