写在前面

本篇来学习9种常用的分布式ID生成方案,掌握这些对于提升自身能力有极大帮助。

为什么使用分布式ID

分布式ID的定义

这里以MySQL数据库为例进行说明,当我们的业务数据量不大时,单库单表完全可以支撑现有业务,数据再大一点时,使用MySQL主从同步、读写分离模式也能应付。但是当数据日益增长,达到亿级别的时候,就需要对数据库进行分库分表操作,而分库分表后需要有一个唯一的ID来标识每条数据,显然数据库的自增ID无法满足需求,那么这个 全局唯一ID 就叫 分布式ID 。

分布式ID满足的条件

一般来说,分布式ID需要满足如下五个要求:

  • 全局唯一:必须保证ID是全局性唯一;
  • 高性能:高可用低延时,ID生成响应快,否则会成为业务瓶颈;
  • 高可用:100%可用无法做到,但是要无限接近于100%的可用;
  • 好接入:秉承拿来即用的设计原则,在系统设计和实现上要尽可能的简单;
  • 趋势递增:最好有趋势递增,不过要结合实际业务进行分析。

    分布式ID的生成方式

    分布式ID的生成方式有很多种,此处列举常用的9种方式,如下所示:
    (1)UUID;
    (2)数据库自增ID;
    (3)数据库多主模式;
    (4)号段模式;
    (5)Redis;
    (6)SnowFlake(雪花算法);
    (7)Uidgenerator (百度);
    (8)TinyID(滴滴);
    (9)Leaf(美团)。
    本篇文章将学习以上9种分布式ID的生成方式,并学习其原理和优缺点,以便在实际开发过程中能有针对性的进行选择使用。

    UUID

    由于笔者使用的是Java语言,而Java自带了一个能生成全球唯一ID的UUID类,如下所示:
    1
    2
    3
    4
    public static void main(String[] args) {
    String str = UUID.randomUUID().toString().replace("-", "");
    System.out.println(str);
    }
    执行上述方法,输出结果为:15751784476b47719021f39dffe49003。实际上生成的UUID可以作为分布式ID,但是并不建议使用它。因为生成的UUID体现不出业务的含义,而且对于数据库而言,UUID的长度过长且是字符串,存储和查询性能较差。

优点:

  • 生成逻辑简单,一行代码搞定;
  • 本地生成无网络消耗且具有唯一性。

缺点:

  • 无序的字符串,不具备趋势自增特性;
  • 无法体现具体的业务含义;
  • 长度过长且是字符串,存储和查询性能较差。

    数据库自增ID

    这里以MySQL数据库为例进行说明,可以使用MySQL的 auto_increment 自增ID来充当分布式ID。

举个例子,下面是一个表,使用它的自增主键ID作为分布式ID,对应的建表语句如下:

1
2
3
4
5
6
7
CREATE TABLE sequence_id (
id bigint(20) unsigned NOT NULL auto_increment,
value char(10) NOT NULL default '',
PRIMARY KEY (id),
) ENGINE=InnoDB;

insert into sequence_id(value) VALUES ('zhangsan');

当我们需要一个ID时,只需往该表中插入一条记录,即可返回该记录的主键ID,不过这种方式仅适用于并发量不大的情况,在分布式系统中不建议使用。

优点:

  • 实现简单且ID单调自增;
  • 采用数值类型,因此查询速度快。

缺点:

  • 数据库存在宕机故障,无法抗住高并发场景。

    数据库集群模式

    既然单点数据库方式存在高并发问题,那么我们可对上述方式进优化,采用主从模式集群模式。考虑到单主节点挂掉,无法高可用,那么可采用双主模式集群,即两个Mysql实例都能单独生成自增ID。

不过这里还有一个问题,就是两个MySQL实例的自增ID都从1开始,肯定会生成重复的ID,此时可以考虑错开设置起始值以及采用自增步长。

举个例子,假设这里有两个MySQL实例,接下来我们尝试对其做如下配置:
(1)MySQL_1的配置如下:

1
2
set @@auto_increment_offset = 1;     -- 起始值
set @@auto_increment_increment = 2; -- 步长

(2)MySQL_2的配置如下:

1
2
set @@auto_increment_offset = 2;     -- 起始值
set @@auto_increment_increment = 2; -- 步长

通过上面的配置,那么这两个MySQL实例的自增ID分别如下:

1
2
MySQL_1:1、3、5、7、9 
MySQL_2:2、4、6、8、10

当然了,如果使用此集群后,性能还是不佳,无法扛住高并发,可考虑对MySQL集群进行扩容处理,即增加节点,不过这个操作比较复杂:
image.png
可以看到,由于上述数据库集群支持水平扩展,因此有利于解决数据库单点压力的问题,同时为了ID生成特性,需要将自增步长按照机器数量来设置。

这个操作其实比较复杂,我们仅仅是往集群中添加了一个实例,就需要对上述三个实例的起始值和步长都进行调整,将第三台机器的ID起始值调整为比现有最大自增ID的值都大,而且必须在前面两台机器还没有增长到第三台机器的起始ID值时才能操作,否则自增ID就重复了,这是非常严重的问题,必要时还可能需要停机修改。

优点:

  • 解决数据库的单节点宕机故障。

缺点:

  • 水平扩容较为复杂,且实际上单个数据库压力还是很大,依旧无法满足高并发场景。

    数据库号段模式

    数据库号段模式是当前分布式ID生成器的主流实现方式之一,号段模式可以这样理解:首先从数据库批量获取自增ID,然后每次从数据库取出一个号段范围,如 (1,1000] 代表1000个ID,接着某一业务微服务将本号段生成1~1000的自增ID并加载到内存中。对应的表结构如下:
    1
    2
    3
    4
    5
    6
    7
    8
    CREATE TABLE id_generator (
    id int(10) NOT NULL,
    max_id bigint(20) NOT NULL COMMENT '当前最大id',
    step int(20) NOT NULL COMMENT '号段的步长',
    biz_type int(20) NOT NULL COMMENT '业务类型',
    version int(20) NOT NULL COMMENT '版本号',
    PRIMARY KEY (`id`)
    )
    简单解释一下上述字段的含义:
    (1)max_id ,表示当前可用的最大id;
    (2)step,表示号段的步长;
    (3)biz_type,表示不同的业务类型;
    (4)version ,一个乐观锁,注意每次对数据进行更新操作,都需要更新version字段,保证并发时数据的正确性。

下面是一条示例,如下所示:
image.png
后续等这批号段ID用完了,可再次向数据库申请新号段,只需对 max_id 字段做一次 update 操作,执行如下语句:

1
2
3
4
5
update id_generator
set max_id = max_id + step, version = version + 1
where biz_type = #{bizType}
and max_id = #{maxId}
and version = #{version}

上述语句执行成功,则说明新号段获取成功,此时新的号段范围为 (max_id , max_id +step] 。考虑到多个业务可能同时操作,因此引入版本号 version 字段,采用乐观锁方式更新。

优点:

  • 不强依赖数据库,且不会频繁的访问数据库,对数据库的压力很小。

缺点:

  • 当存在多个业务,且业务使用频率差距较大时,会造成有些业务号段不够用,有些使用率不高的情况。

    Redis模式

    Redis提供了incr命令,因此通过该命令也可以实现分布式ID的生成功能。举个例子,如下所示:
    1
    2
    3
    4
    127.0.0.1:6379> set sequence_id 1     // 初始化自增ID为1
    OK
    127.0.0.1:6379> incr sequence_id // 增加1,并返回递增后的数值
    (integer) 2
    请注意,在使用Redis实现时需要考虑Redis的持久化问题。我们知道Redis有两种持久化方式:RDB和AOF。下面说一下这两种情况分别可能存在的问题:
    (1)RDB方式是定时生成一个快照,进而进行持久化。这样就有一个问题,假如连续自增的Redis没及时进行持久化就挂掉了,那么重启后就会出现ID重复的情况。
    (2)AOF方式是针对每条写命令进行持久化,这样即使Redis挂掉也不会出现ID重复的情况。但是这样会有一个情况,由于incr命令的特殊性,它会导致Redis重启恢复数据的时间较长。

优点:

  • 实现简单,不依赖与数据库,可实现高可用。

缺点:

  • RDB方式可能出现ID重复、AOF方式会导致Redis重启恢复数据的时间较长。

    雪花算法(Snowflake)模式

    雪花算法(Snowflake)是twitter公司内部分布式项目采用的ID生成算法,各大公司在此基础上开发出了各具特色的分布式ID生成器。下图是雪花算法生成的Long类型ID的组成结构示意图:
    image.png
    Snowflake 生成的是一个Long类型的ID,而一个Long类型占8个字节,每个字节又占8比特,即一个Long类型占64 个比特。

该Long类型的ID由64个比特组成,结构如下:正数位(占1比特)+ 时间戳(占41比特)+ 机器ID(占5比特)+ 数据中心(占5比特)+ 自增值(占12比特)。

  • 第一个bit位(1bit):Java中Long的最高位是符号位,代表正负,其中正数是0,负数是1,一般生成ID都为正数,所以默认是0。
  • 时间戳部分(41bit):毫秒级的时间,不建议存当前时间戳,而是用(当前时间戳 - 固定开始时间戳)的差值,这样可使产生的ID从更小的值开始。41位的时间戳可以使用69年,(1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69年。
  • 工作机器id(10bit):也被叫做 workId ,这个可灵活配置,一般是机器ID+数据中心ID组成。
  • 序列号部分(12bit):自增值支持同一毫秒时间内,同一个节点可生成4096个ID。

根据雪花算法的逻辑,开发者只需使用Java语言将其实现出来,并封装为一个工具方法,然后在各个业务系统中直接使用该工具方法来获取分布式ID,此时只需保证每个业务都有自己的工作机器id,不需要单独搭建一个获取分布式ID的系统。

下面是使用Java实现的雪花算法代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
/**
* Twitter的SnowFlake算法,使用SnowFlake算法生成一个整数,然后转化为62进制变成一个短地址URL
*
* https://github.com/beyondfengyu/SnowFlake
*/
public class SnowFlakeShortUrl {

/**
* 起始的时间戳
*/
private final static long START_TIMESTAMP = 1480166465631L;

/**
* 每一部分占用的位数
*/
//序列号占用的位数
private final static long SEQUENCE_BIT = 12;
//数据中心占用的位数
private final static long DATA_CENTER_BIT = 5;
//机器标识占用的位数
private final static long MACHINE_BIT = 5;

/**
* 每一部分的最大值
*/
//序列号的最大值
private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);
//数据中心的最大值
private final static long MAX_DATA_CENTER_NUM = -1L ^ (-1L << DATA_CENTER_BIT);
//机器标识的最大值
private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);


/**
* 每一部分向左的位移
*/
//机器标识向左的位移
private final static long MACHINE_LEFT = SEQUENCE_BIT;
//数据中心向左的位移
private final static long DATA_CENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
//时间戳向左的位移
private final static long TIMESTAMP_LEFT = DATA_CENTER_LEFT + DATA_CENTER_BIT;

//数据中心
private long dataCenterId;
//机器标识
private long machineId;
//序列号
private long sequence = 0L;
//上一次时间戳
private long lastTimeStamp = -1L;

private long getNextMill() {
long mill = getNewTimeStamp();
while (mill <= lastTimeStamp) {
mill = getNewTimeStamp();
}
return mill;
}

private long getNewTimeStamp() {
return System.currentTimeMillis();
}

/**
* 根据指定的数据中心ID和机器标志ID生成指定的序列号
*
* @param dataCenterId 数据中心ID
* @param machineId 机器标志ID
*/
public SnowFlakeShortUrl(long dataCenterId, long machineId) {
if (dataCenterId > MAX_DATA_CENTER_NUM || dataCenterId < 0) {
throw new IllegalArgumentException("DtaCenterId can't be greater than MAX_DATA_CENTER_NUM or less than 0!");
}
if (machineId > MAX_MACHINE_NUM || machineId < 0) {
throw new IllegalArgumentException("MachineId can't be greater than MAX_MACHINE_NUM or less than 0!");
}
this.dataCenterId = dataCenterId;
this.machineId = machineId;
}

/**
* 生成下一个ID
*/
public synchronized long nextId() {
long currTimeStamp = getNewTimeStamp();
if (currTimeStamp < lastTimeStamp) {
throw new RuntimeException("Clock moved backwards. Refusing to generate id");
}

if (currTimeStamp == lastTimeStamp) {
//相同毫秒内,序列号自增
sequence = (sequence + 1) & MAX_SEQUENCE;
//同一毫秒的序列数已经达到最大
if (sequence == 0L) {
currTimeStamp = getNextMill();
}
} else {
//不同毫秒内,序列号置为0
sequence = 0L;
}

lastTimeStamp = currTimeStamp;

return (currTimeStamp - START_TIMESTAMP) << TIMESTAMP_LEFT //时间戳部分
| dataCenterId << DATA_CENTER_LEFT //数据中心部分
| machineId << MACHINE_LEFT //机器标识部分
| sequence; //序列号部分
}

public static void main(String[] args) {
SnowFlakeShortUrl snowFlake = new SnowFlakeShortUrl(2, 3);

for (int i = 0; i < (1 << 4); i++) {
//10进制
System.out.println(snowFlake.nextId());
}
}
}

Uidgenerator (百度)

uid-generator 是百度基于 Snowflake 算法实现的,项目 GitHub地址 。与原始的 snowflake 算法不同,uid-generator 支持 自定义时间戳、工作机器ID 和 序列号 等各部分的位数,且 uid-generator 中采用用户自定义 workId 的生成策略。

uid-generator 需要与数据库配合来使用,数据库中需要新建一个名为 WORKER_NODE 的表。当系统启动时,会往数据库表中插入一条数据,插入成功后会返回自增的ID,该ID就是该机器的 workId 数据。workId 由host和port组成。

请注意,uid-generator ID的组成结构与原始的Snowflake 算法生成的ID不同,uid-generator ID的workId占用22个bit位,而时间占用28个bit位,序列化占用13个bit位,还有它的时间单位是秒,不是毫秒。同时workId 也不一样,且同一应用每次重启就会消费一个 workId 。关于 Uidgenerator中文说明文档可以点击 GitHub地址 进行阅读。

TinyID(滴滴)

TinyID是滴滴基于号段模式原理开发的,项目 GitHub地址 。每个服务获取一个号段,如(1000,2000]、(2000,3000]、(3000,4000],之后各服务使用号段进行业务调用:
image.png
Tinyid 提供两种方式来进行接入:http 和 tinyid-client,下面将分别进行学习。

http方式接入

第一步,使用如下命令来下载tinyid的源码:

1
git clone https://github.com/didi/tinyid.git

第二步,创建对应的数据库表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
CREATE TABLE `tiny_id_info`
(
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
`biz_type` varchar(63) NOT NULL DEFAULT '' COMMENT '业务类型,唯一',
`begin_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '开始id,仅记录初始值,无其他含义。初始化时begin_id和max_id应相同',
`max_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '当前最大id',
`step` int(11) DEFAULT '0' COMMENT '步长',
`delta` int(11) NOT NULL DEFAULT '1' COMMENT '每次id增量',
`remainder` int(11) NOT NULL DEFAULT '0' COMMENT '余数',
`create_time` timestamp NOT NULL DEFAULT '2010-01-01 00:00:00' COMMENT '创建时间',
`update_time` timestamp NOT NULL DEFAULT '2010-01-01 00:00:00' COMMENT '更新时间',
`version` bigint(20) NOT NULL DEFAULT '0' COMMENT '版本号',
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_biz_type` (`biz_type`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='id信息表';

CREATE TABLE `tiny_id_token`
(
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增id',
`token` varchar(255) NOT NULL DEFAULT '' COMMENT 'token',
`biz_type` varchar(63) NOT NULL DEFAULT '' COMMENT '此token可访问的业务类型标识',
`remark` varchar(255) NOT NULL DEFAULT '' COMMENT '备注',
`create_time` timestamp NOT NULL DEFAULT '2010-01-01 00:00:00' COMMENT '创建时间',
`update_time` timestamp NOT NULL DEFAULT '2010-01-01 00:00:00' COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='token信息表';

INSERT INTO `tiny_id_info` (`id`, `biz_type`, `begin_id`, `max_id`, `step`, `delta`, `remainder`, `create_time`,`update_time`, `version`)
VALUES (1, 'test', 1, 1, 100000, 1, 0, '2018-07-21 23:52:58', '2018-07-22 23:19:27', 1);

INSERT INTO `tiny_id_info` (`id`, `biz_type`, `begin_id`, `max_id`, `step`, `delta`, `remainder`, `create_time`,`update_time`, `version`)
VALUES (2, 'test_odd', 1, 1, 100000, 2, 1, '2018-07-21 23:52:58', '2018-07-23 00:39:24', 3);


INSERT INTO `tiny_id_token` (`id`, `token`, `biz_type`, `remark`, `create_time`, `update_time`)
VALUES (1, '0f673adf80504e2eaa552f5d791b644c', 'test', '1', '2017-12-14 16:36:46', '2017-12-14 16:36:48');

INSERT INTO `tiny_id_token` (`id`, `token`, `biz_type`, `remark`, `create_time`, `update_time`)
VALUES (2, '0f673adf80504e2eaa552f5d791b644c', 'test_odd', '1', '2017-12-14 16:36:46', '2017-12-14 16:36:48');

第三步,配置tinyid-server项目offline包下的application.properties配置文件为如下所示:

1
2
3
4
5
server.port=8086
datasource.tinyid.primary.driver-class-name=com.mysql.cj.jdbc.Driver
datasource.tinyid.primary.url=jdbc:mysql://localhost:3306/sequence_id?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8
datasource.tinyid.primary.username=root
datasource.tinyid.primary.password=root

注意笔者使用的MySQL版本为8系列。
第四步,启动tinyid-server项目,开始进行测试。访问如下链接,来获取单个的ID:

1
http://localhost:8086/tinyid/id/nextIdSimple?bizType=test&token=0f673adf80504e2eaa552f5d791b644c

返回结果为:2。

如果要批量获取ID,可使用如下链接:

1
http://localhost:8086/tinyid/id/nextIdSimple?bizType=test&token=0f673adf80504e2eaa552f5d791b644c&batchSize=3

返回结果为:3,4,5。

Java客户端方式接入

第一步,创建对应的数据库表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
CREATE TABLE `tiny_id_info`
(
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
`biz_type` varchar(63) NOT NULL DEFAULT '' COMMENT '业务类型,唯一',
`begin_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '开始id,仅记录初始值,无其他含义。初始化时begin_id和max_id应相同',
`max_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '当前最大id',
`step` int(11) DEFAULT '0' COMMENT '步长',
`delta` int(11) NOT NULL DEFAULT '1' COMMENT '每次id增量',
`remainder` int(11) NOT NULL DEFAULT '0' COMMENT '余数',
`create_time` timestamp NOT NULL DEFAULT '2010-01-01 00:00:00' COMMENT '创建时间',
`update_time` timestamp NOT NULL DEFAULT '2010-01-01 00:00:00' COMMENT '更新时间',
`version` bigint(20) NOT NULL DEFAULT '0' COMMENT '版本号',
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_biz_type` (`biz_type`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='id信息表';

CREATE TABLE `tiny_id_token`
(
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增id',
`token` varchar(255) NOT NULL DEFAULT '' COMMENT 'token',
`biz_type` varchar(63) NOT NULL DEFAULT '' COMMENT '此token可访问的业务类型标识',
`remark` varchar(255) NOT NULL DEFAULT '' COMMENT '备注',
`create_time` timestamp NOT NULL DEFAULT '2010-01-01 00:00:00' COMMENT '创建时间',
`update_time` timestamp NOT NULL DEFAULT '2010-01-01 00:00:00' COMMENT '更新时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='token信息表';

INSERT INTO `tiny_id_info` (`id`, `biz_type`, `begin_id`, `max_id`, `step`, `delta`, `remainder`, `create_time`,`update_time`, `version`)
VALUES (1, 'test', 1, 1, 100000, 1, 0, '2018-07-21 23:52:58', '2018-07-22 23:19:27', 1);

INSERT INTO `tiny_id_info` (`id`, `biz_type`, `begin_id`, `max_id`, `step`, `delta`, `remainder`, `create_time`,`update_time`, `version`)
VALUES (2, 'test_odd', 1, 1, 100000, 2, 1, '2018-07-21 23:52:58', '2018-07-23 00:39:24', 3);


INSERT INTO `tiny_id_token` (`id`, `token`, `biz_type`, `remark`, `create_time`, `update_time`)
VALUES (1, '0f673adf80504e2eaa552f5d791b644c', 'test', '1', '2017-12-14 16:36:46', '2017-12-14 16:36:48');

INSERT INTO `tiny_id_token` (`id`, `token`, `biz_type`, `remark`, `create_time`, `update_time`)
VALUES (2, '0f673adf80504e2eaa552f5d791b644c', 'test_odd', '1', '2017-12-14 16:36:46', '2017-12-14 16:36:48');

第二步,配置tinyid-server项目offline包下的application.properties配置文件为如下所示:

1
2
3
4
5
server.port=8086
datasource.tinyid.primary.driver-class-name=com.mysql.cj.jdbc.Driver
datasource.tinyid.primary.url=jdbc:mysql://localhost:3306/sequence_id?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8
datasource.tinyid.primary.username=root
datasource.tinyid.primary.password=root

第三步,创建一个项目,然后在POM文件中新增如下依赖:

1
2
3
4
5
<dependency>
<groupId>com.xiaoju.uemc.tinyid</groupId>
<artifactId>tinyid-client</artifactId>
<version>${tinyid.version}</version>
</dependency>

第四步,通过阅读源码可知,需要新建一个名为 tinyid_client.properties 的配置文件,然后往里面新增如下配置信息,后面两个配置项可以不配置:

1
2
3
4
tinyid.token=0f673adf80504e2eaa552f5d791b644c
tinyid.server=localhost:8086
tinyid.readTimeout=
tinyid.connectTimeout=

image.png
第五步,启动项目,开始进行测试。通过对源码分析,可以看到实际上我们只需通过 TinyId 这个工具类提供的方法就能获取到生成的ID:
image.png
如果你想获取单个的ID,可调用TinyId的 nextId(String bizType) 方法:

1
TinyId.nextId("test");

返回结果为:2。

如果想要批量获取ID,可调用TinyId的 nextId(String bizType, Integer batchSize) 方法:

1
TinyId.nextId("test",3);

返回结果为:3,4,5。请注意,上面的bizType和token必须对应起来,否则数据是无法查询出来的。

Leaf(美团)

Leaf 由美团开发,同时支持号段模式和 snowflake 算法模式(可切换使用)的分布式ID生成器,项目 GitHub地址

号段模式接入

第一步,使用如下命令来下载 Leaf 的源码:

1
git clone https://github.com/Meituan-Dianping/Leaf

第二步,创建对应的数据库表:

1
2
3
4
5
6
7
8
9
10
DROP TABLE IF EXISTS `leaf_alloc`;

CREATE TABLE `leaf_alloc` (
`biz_tag` varchar(128) NOT NULL DEFAULT '' COMMENT '业务标签,唯一',
`max_id` bigint(20) NOT NULL DEFAULT '1' COMMENT '当前最大id',
`step` int(11) NOT NULL COMMENT '步长',
`description` varchar(256) DEFAULT NULL COMMENT '描述信息',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
PRIMARY KEY (`biz_tag`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='号段分配表';

第三步,修改项目的一些依赖版本,具体如下:
(1)mysql-connector-java,由 5.1.38 调整为 8.0.33;
(2)druid,由 1.0.18 调整为 1.1.10。
第四步,修改 leaf-server 项目中的leaf.properties文件内容,我们这里使用号段模式,配置信息如下:

1
2
3
4
5
6
7
leaf.name=com.sankuai.leaf.opensource.test
leaf.segment.enable=true
leaf.jdbc.url=jdbc:mysql://localhost:3306/sequence_id?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8
leaf.jdbc.username=root
leaf.jdbc.password=root

leaf.snowflake.enable=false

既然我们这里使用了号段模式,因此需要关闭雪花算法模式。
第五步,启动 leaf-server 项目的入口类 LeafServerApplication,即可启动项目。然后往数据表leaf_alloc中插入一条数据,如下所示:
image.png
接着在浏览器地址栏中访问[http://localhost:8080/api/segment/get/test_order](http://localhost:8080/api/segment/get/test_order)链接,即可获取到生成的ID。
实际上,Leaf还提供了一个接口[http://localhost:8080/cache](http://localhost:8080/cache)来监控号段模式,界面如下所示:
image.png

雪花算法模式接入

Leaf和原始的雪花算法主要区别在 workId 的生成上。我们知道 Leaf 的雪花算法模式依赖ZooKeeper,它的 workId 是基于ZooKeeper的顺序Id来生成的。每个应用在使用 Leaf 的雪花模式接入时,在启动的时候,都会在Zookeeper中生成一个顺序Id,就相当于一台机器对应一个顺序节点,也就是一个workId。

前面三步和之前的一样,第四步配置信息有点差异,如下所示:

1
2
3
4
5
leaf.segment.enable=false

leaf.snowflake.enable=true
leaf.snowflake.zk.address=127.0.0.1
leaf.snowflake.port=2187

之后启动 leaf-server 项目的入口类 LeafServerApplication,即可启动项目。然后在浏览器地址栏中访问[http://localhost:8080/api/snowflake/get/test_order](http://localhost:8080/api/segment/get/test_order)链接,即可获取到生成的ID。