![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5CM5AzYlNzNzkDN2IzM4QDZhFGOiZjN5EGZ0ETN4QjN48CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
引言
接着《一口氣說出 9種 分布式ID生成方式,面試官有點懵了》來繼續詳細的介紹分布式ID生成器,大家比較感興趣的
美團(Leaf)
、
滴滴(Tinyid)
、
百度(uid-generator)
三個開源項目,美團(Leaf)已經講完,詳見《9種分布式ID生成之美團(Leaf)實戰》,今天結合實戰搞一下滴滴開源的(
Tinyid
)。
`Tinyid`介紹
Tinyid
是滴滴開發的一款分布式ID系統,
Tinyid
是在
美團(Leaf)
的
leaf-segment
算法基礎上更新而來,不僅支援了資料庫多主節點模式,還提供了
tinyid-client
用戶端的接入方式,使用起來更加友善。但和美團(Leaf)不同的是,Tinyid隻支援号段一種模式不支援雪花模式。
Tinyid的特性
- 全局唯一的long型ID
- 趨勢遞增的id
- 提供 http 和 java-client 方式接入
- 支援批量擷取ID
- 支援生成1,3,5,7,9…序列的ID
- 支援多個db的配置
:隻關心ID是數字,趨勢遞增的系統,可以容忍ID不連續,可以容忍ID的浪費
不适用場景:像類似于訂單ID的業務,因生成的ID大部分是連續的,容易被掃庫、或者推算出訂單量等資訊
`Tinyid`原理
Tinyid
是基于号段模式實作,再簡單啰嗦一下号段模式的原理:就是從資料庫批量的擷取自增ID,每次從資料庫取出一個号段範圍,例如
(1,1000]
代表1000個ID,業務服務将号段在本地生成
1~1000
的自增ID并加載到記憶體.。
Tinyid
會将可用号段加載到記憶體中,并在記憶體中生成ID,可用号段在首次擷取ID時加載,如目前号段使用達到一定比例時,系統會異步的去加載下一個可用号段,以此保證記憶體中始終有可用号段,以便在發号服務當機後一段時間内還有可用ID。
原理圖大緻如下圖:
`Tinyid`實作
Tinyid
的GitHub位址 : https://github.com/didi/tinyid.git
Tinyid
提供了兩種調用方式,一種基于
Tinyid-server
提供的http方式,另一種
Tinyid-client
用戶端方式。
不管使用哪種方式調用,搭建
Tinyid
都必須提前建表
tiny_id_info
、
tiny_id_token
。
1CREATE TABLE `tiny_id_info` (
2 `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主鍵',
3 `biz_type` varchar(63) NOT NULL DEFAULT '' COMMENT '業務類型,唯一',
4 `begin_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '開始id,僅記錄初始值,無其他含義。初始化時begin_id和max_id應相同',
5 `max_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '目前最大id',
6 `step` int(11) DEFAULT '0' COMMENT '步長',
7 `delta` int(11) NOT NULL DEFAULT '1' COMMENT '每次id增量',
8 `remainder` int(11) NOT NULL DEFAULT '0' COMMENT '餘數',
9 `create_time` timestamp NOT NULL DEFAULT '2010-01-01 00:00:00' COMMENT '建立時間',
10 `update_time` timestamp NOT NULL DEFAULT '2010-01-01 00:00:00' COMMENT '更新時間',
11 `version` bigint(20) NOT NULL DEFAULT '0' COMMENT '版本号',
12 PRIMARY KEY (`id`),
13 UNIQUE KEY `uniq_biz_type` (`biz_type`)
14) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT 'id資訊表';
15
16CREATE TABLE `tiny_id_token` (
17 `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增id',
18 `token` varchar(255) NOT NULL DEFAULT '' COMMENT 'token',
19 `biz_type` varchar(63) NOT NULL DEFAULT '' COMMENT '此token可通路的業務類型辨別',
20 `remark` varchar(255) NOT NULL DEFAULT '' COMMENT '備注',
21 `create_time` timestamp NOT NULL DEFAULT '2010-01-01 00:00:00' COMMENT '建立時間',
22 `update_time` timestamp NOT NULL DEFAULT '2010-01-01 00:00:00' COMMENT '更新時間',
23 PRIMARY KEY (`id`)
24) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT 'token資訊表';
25
26INSERT INTO `tiny_id_info` (`id`, `biz_type`, `begin_id`, `max_id`, `step`, `delta`, `remainder`, `create_time`, `update_time`, `version`)
27VALUES
28 (1, 'test', 1, 1, 100000, 1, 0, '2018-07-21 23:52:58', '2018-07-22 23:19:27', 1);
29
30INSERT INTO `tiny_id_info` (`id`, `biz_type`, `begin_id`, `max_id`, `step`, `delta`, `remainder`, `create_time`, `update_time`, `version`)
31VALUES
32 (2, 'test_odd', 1, 1, 100000, 2, 1, '2018-07-21 23:52:58', '2018-07-23 00:39:24', 3);
33
34
35INSERT INTO `tiny_id_token` (`id`, `token`, `biz_type`, `remark`, `create_time`, `update_time`)
36VALUES
37 (1, '0f673adf80504e2eaa552f5d791b644c', 'test', '1', '2017-12-14 16:36:46', '2017-12-14 16:36:48');
38
39INSERT INTO `tiny_id_token` (`id`, `token`, `biz_type`, `remark`, `create_time`, `update_time`)
40VALUES
41 (2, '0f673adf80504e2eaa552f5d791b644c', 'test_odd', '1', '2017-12-14 16:36:46', '2017-12-14 16:36:48');
tiny_id_info
表是具體業務方号段資訊資料表
max_id
:号段的最大值
step
:步長,即為号段的長度
biz_type
:業務類型
号段擷取對
max_id
字段做一次
update
操作,
update max_id= max_id + step
,更新成功則說明新号段擷取成功,新的号段範圍是
(max_id ,max_id +step]
。
tiny_id_token
是一個權限表,表示目前token可以操作哪些業務的号段資訊。
修改
tinyid-server
中
offlineapplication.properties
檔案配置資料庫,由于
tinyid
支援資料庫多
master
模式,可以配置多個資料庫資訊。啟動
TinyIdServerApplication
測試一下。
1datasource.tinyid.primary.driver-class-name=com.mysql.jdbc.Driver
2datasource.tinyid.primary.url=jdbc:mysql://127.0.0.1:3306/xin-master?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8
3datasource.tinyid.primary.username=junkang
4datasource.tinyid.primary.password=junkang
5datasource.tinyid.primary.testOnBorrow=false
6datasource.tinyid.primary.maxActive=10
7
8datasource.tinyid.secondary.driver-class-name=com.mysql.jdbc.Driver
9datasource.tinyid.secondary.url=jdbc:mysql://localhost:3306/db2?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8
10datasource.tinyid.secondary.username=root
11datasource.tinyid.secondary.password=123456
12datasource.tinyid.secondary.testOnBorrow=false
13datasource.tinyid.secondary.maxActive=10
1、Http方式
tinyid
内部一共提供了四個
http
接口來擷取ID和号段。
1package com.xiaoju.uemc.tinyid.server.controller;
2
3/**
4 * @author du_imba
5 */
[email protected]
[email protected]("/id/")
8public class IdContronller {
9
10 private static final Logger logger = LoggerFactory.getLogger(IdContronller.class);
11 @Autowired
12 private IdGeneratorFactoryServer idGeneratorFactoryServer;
13 @Autowired
14 private SegmentIdService segmentIdService;
15 @Autowired
16 private TinyIdTokenService tinyIdTokenService;
17 @Value("${batch.size.max}")
18 private Integer batchSizeMax;
19
20 @RequestMapping("nextId")
21 public Response<List<Long>> nextId(String bizType, Integer batchSize, String token) {
22 Response<List<Long>> response = new Response<>();
23 try {
24 IdGenerator idGenerator = idGeneratorFactoryServer.getIdGenerator(bizType);
25 List<Long> ids = idGenerator.nextId(newBatchSize);
26 response.setData(ids);
27 } catch (Exception e) {
28 response.setCode(ErrorCode.SYS_ERR.getCode());
29 response.setMessage(e.getMessage());
30 logger.error("nextId error", e);
31 }
32 return response;
33 }
34
35
36
37 @RequestMapping("nextIdSimple")
38 public String nextIdSimple(String bizType, Integer batchSize, String token) {
39 String response = "";
40 try {
41 IdGenerator idGenerator = idGeneratorFactoryServer.getIdGenerator(bizType);
42 if (newBatchSize == 1) {
43 Long id = idGenerator.nextId();
44 response = id + "";
45 } else {
46 List<Long> idList = idGenerator.nextId(newBatchSize);
47 StringBuilder sb = new StringBuilder();
48 for (Long id : idList) {
49 sb.append(id).append(",");
50 }
51 response = sb.deleteCharAt(sb.length() - 1).toString();
52 }
53 } catch (Exception e) {
54 logger.error("nextIdSimple error", e);
55 }
56 return response;
57 }
58
59 @RequestMapping("nextSegmentId")
60 public Response<SegmentId> nextSegmentId(String bizType, String token) {
61 try {
62 SegmentId segmentId = segmentIdService.getNextSegmentId(bizType);
63 response.setData(segmentId);
64 } catch (Exception e) {
65 response.setCode(ErrorCode.SYS_ERR.getCode());
66 response.setMessage(e.getMessage());
67 logger.error("nextSegmentId error", e);
68 }
69 return response;
70 }
71
72 @RequestMapping("nextSegmentIdSimple")
73 public String nextSegmentIdSimple(String bizType, String token) {
74 String response = "";
75 try {
76 SegmentId segmentId = segmentIdService.getNextSegmentId(bizType);
77 response = segmentId.getCurrentId() + "," + segmentId.getLoadingId() + "," + segmentId.getMaxId()
78 + "," + segmentId.getDelta() + "," + segmentId.getRemainder();
79 } catch (Exception e) {
80 logger.error("nextSegmentIdSimple error", e);
81 }
82 return response;
83 }
84
85}
nextId
、
nextIdSimple
都是擷取下一個ID,
nextSegmentIdSimple
、
getNextSegmentId
是擷取下一個可用号段。差別在于接口是否有傳回狀态。
1nextId:
2'http://localhost:9999/tinyid/id/nextId?bizType=test&token=0f673adf80504e2eaa552f5d791b644c'
3response :
4{
5 "data": [2],
6 "code": 200,
7 "message": ""
8}
9
10nextId Simple:
11'http://localhost:9999/tinyid/id/nextIdSimple?bizType=test&token=0f673adf80504e2eaa552f5d791b644c'
12response: 3
2、`Tinyid-client`用戶端
如果不想通過http方式,
Tinyid-client
用戶端也是一種不錯的選擇。
引用
tinyid-server
包
1<dependency>
2 <groupId>com.xiaoju.uemc.tinyid</groupId>
3 <artifactId>tinyid-client</artifactId>
4 <version>${tinyid.version}</version>
5</dependency>
啟動
tinyid-server
項目打包後得到
tinyid-server-0.1.0-SNAPSHOT.jar
,設定版本
${tinyid.version}
為0.1.0-SNAPSHOT。
在我們的項目
application.properties
中配置
tinyid-server
服務的請求位址 和 使用者身份token
1tinyid.server=127.0.0.1:9999
2tinyid.token=0f673adf80504e2eaa552f5d791b644c```
3
在Java代碼調用
TinyId
也很簡單,隻需要一行代碼。
1 // 根據業務類型 擷取單個ID
2 Long id = TinyId.nextId("test");
3
4 // 根據業務類型 批量擷取10個ID
5 List<Long> ids = TinyId.nextId("test", 10);
Tinyid
整個項目的源碼實作也是比較簡單,像與資料庫互動更直接用jdbcTemplate實作
[email protected]
2 public TinyIdInfo queryByBizType(String bizType) {
3 String sql = "select id, biz_type, begin_id, max_id," +
4 " step, delta, remainder, create_time, update_time, version" +
5 " from tiny_id_info where biz_type = ?";
6 List<TinyIdInfo> list = jdbcTemplate.query(sql, new Object[]{bizType}, new TinyIdInfoRowMapper());
7 if(list == null || list.isEmpty()) {
8 return null;
9 }
10 return list.get(0);
11 }
總結
兩種方式推薦使用
Tinyid-client
,這種方式ID為本地生成,号段長度(
step
)越長,支援的
qps
就越大,如果将号段設定足夠大,則qps可達1000w+。而且
tinyid-client
對
tinyid-server
通路變的低頻,減輕了server端的壓力。