
滴滴的分布式ID生成器(Tinyid),好用的一批
不了解分布式ID生成器的同学,先复习一下之前的 《9种分布式ID生成方式》
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
。
CREATE TABLE `tiny_id_info` (
`id` bigint(20) unsigned 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=1 DEFAULT CHARSET=utf8 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=1 DEFAULT CHARSET=utf8 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');
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
中
\offline\application.properties
文件配置数据库,由于
tinyid
支持数据库多
master
模式,可以配置多个数据库信息。启动
TinyIdServerApplication
测试一下。
datasource.tinyid.primary.driver-class-name=com.mysql.jdbc.Driver
datasource.tinyid.primary.url=jdbc:mysql://127.0.0.1:3306/xin-master?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8
datasource.tinyid.primary.username=junkang
datasource.tinyid.primary.password=junkang
datasource.tinyid.primary.testOnBorrow=false
datasource.tinyid.primary.maxActive=10
datasource.tinyid.secondary.driver-class-name=com.mysql.jdbc.Driver
datasource.tinyid.secondary.url=jdbc:mysql://localhost:3306/db2?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8
datasource.tinyid.secondary.username=root
datasource.tinyid.secondary.password=123456
datasource.tinyid.secondary.testOnBorrow=false
datasource.tinyid.secondary.maxActive=10
1、Http方式
tinyid
内部一共提供了四个
http
接口来获取ID和号段。
package com.xiaoju.uemc.tinyid.server.controller;
* @author du_imba
@RestController
@RequestMapping("/id/")
public class IdContronller {
private static final Logger logger = LoggerFactory.getLogger(IdContronller.class);
@Autowired
private IdGeneratorFactoryServer idGeneratorFactoryServer;
@Autowired
private SegmentIdService segmentIdService;
@Autowired
private TinyIdTokenService tinyIdTokenService;
@Value("${batch.size.max}")
private Integer batchSizeMax;
@RequestMapping("nextId")
public Response<List<Long>> nextId(String bizType, Integer batchSize, String token) {
Response<List<Long>> response = new Response<>();
try {
IdGenerator idGenerator = idGeneratorFactoryServer.getIdGenerator(bizType);
List<Long> ids = idGenerator.nextId(newBatchSize);
response.setData(ids);
} catch (Exception e) {
response.setCode(ErrorCode.SYS_ERR.getCode());
response.setMessage(e.getMessage());
logger.error("nextId error", e);
return response;
@RequestMapping("nextIdSimple")
public String nextIdSimple(String bizType, Integer batchSize, String token){
String response = "";
try {
IdGenerator idGenerator = idGeneratorFactoryServer.getIdGenerator(bizType);
if (newBatchSize == 1) {
Long id = idGenerator.nextId();
response = id + "";
} else {
List<Long> idList = idGenerator.nextId(newBatchSize);
StringBuilder sb = new StringBuilder();
for (Long id : idList) {
sb.append(id).append(",");
response = sb.deleteCharAt(sb.length() - 1).toString();
} catch (Exception e) {
logger.error("nextIdSimple error", e);
return response;
@RequestMapping("nextSegmentId")
public Response<SegmentId> nextSegmentId(String bizType, String token){
try {
SegmentId segmentId = segmentIdService.getNextSegmentId(bizType);
response.setData(segmentId);
} catch (Exception e) {
response.setCode(ErrorCode.SYS_ERR.getCode());
response.setMessage(e.getMessage());
logger.error("nextSegmentId error", e);
return response;
@RequestMapping("nextSegmentIdSimple")
public String nextSegmentIdSimple(String bizType, String token){
String response = "";
try {
SegmentId segmentId = segmentIdService.getNextSegmentId(bizType);
response = segmentId.getCurrentId() + "," + segmentId.getLoadingId() + "," + segmentId.getMaxId()
+ "," + segmentId.getDelta() + "," + segmentId.getRemainder();
} catch (Exception e) {
logger.error("nextSegmentIdSimple error", e);
return response;
}
nextId
、
nextIdSimple
都是获取下一个ID,
nextSegmentIdSimple
、
getNextSegmentId
是获取下一个可用号段。区别在于接口是否有返回状态。
nextId:
'http://localhost:9999/tinyid/id/nextId?bizType=test&token=0f673adf80504e2eaa552f5d791b644c'
response :
"data": [2],
"code": 200,
"message": ""
nextId Simple:
'http://localhost:9999/tinyid/id/nextIdSimple?bizType=test&token=0f673adf80504e2eaa552f5d791b644c'
response: 3
2、
Tinyid-client
客户端
如果不想通过http方式,
Tinyid-client
客户端也是一种不错的选择。
引用
tinyid-server
包
<dependency>
<groupId>com.xiaoju.uemc.tinyid</groupId>
<artifactId>tinyid-client</artifactId>
<version>${tinyid.version}</version>
</dependency>
启动
tinyid-server
项目打包后得到
tinyid-server-0.1.0-SNAPSHOT.jar
,设置版本
${tinyid.version}
为0.1.0-SNAPSHOT。
在我们的项目
application.properties
中配置
tinyid-server
服务的请求地址 和 用户身份token
tinyid.server=127.0.0.1:9999
tinyid.token=0f673adf80504e2eaa552f5d791b644c```
在Java代码调用
TinyId
也很简单,只需要一行代码。
// 根据业务类型 获取单个ID
Long id = TinyId.nextId("test");
// 根据业务类型 批量获取10个ID
List<Long> ids = TinyId.nextId("test", 10);
Tinyid
整个项目的源码实现也是比较简单,像与数据库交互更直接用jdbcTemplate实现
@Override
public TinyIdInfo queryByBizType(String bizType) {
String sql = "select id, biz_type, begin_id, max_id," +
" step, delta, remainder, create_time, update_time, version" +
" from tiny_id_info where biz_type = ?";
List<TinyIdInfo> list = jdbcTemplate.query(sql, new Object[]{bizType}, new TinyIdInfoRowMapper());
if(list == null || list.isEmpty()) {
return null;
return list.get(0);
}
总结
两种方式推荐使用
Tinyid-client
,这种方式ID为本地生成,号段长度(
step
)越长,支持的
qps
就越大,如果将号段设置足够大,则qps可达1000w+。而且
tinyid-client
对
tinyid-server
访问变的低频,减轻了server端的压力。
文章转载自公众号:程序员小富

-
如何理解 Python 中 的 可迭代对象、迭代器和 生成器feiry • 8471浏览 • 0回复
-
【公开课课件】家庭 分布式 demo源码和自动 生成 代码插件钟洪发老师 • 1.2w浏览 • 9回复
-
【答疑汇总】HarmonyOS组件 的 分布式 适配和应用畅想董昱 • 1.5w浏览 • 5回复
-
分布式 数据 生成帅了不要不要的 • 5431浏览 • 0回复
-
用鸿蒙 的 分布式 助力七夕丨张明亮丨 • 1.3w浏览 • 10回复
-
一 文带你看懂 分布式 软总线在家庭场景 的 应用HarmonyOS开发者 • 9392浏览 • 1回复
-
OpenHarmony 分布式 启动远程设备 的 FANL_AIDC_XJS • 9345浏览 • 8回复
-
MyBatis Generator 代码自动 生成器 ,从此解放你 的 双手skite001 • 7729浏览 • 0回复
-
「SpringCloud」代码 生成器 实现前后端代码自动 生成ponymar • 3114浏览 • 0回复
-
「SpringCloud」代码 生成器 使用配置说明ponymar • 2786浏览 • 0回复
-
OpenHarmony3.1 分布式 应用开发 第 一 讲 分布式 数据管理un_garçon • 6596浏览 • 4回复
-
分布式 : 分布式 系统下 的 唯 一 序列爱新觉羅丶高 • 1533浏览 • 0回复
-
雪花算法: 分布式 唯 一 ID 生成 利器梦叶草789 • 2911浏览 • 0回复
-
手把手教你开发代码 生成器 ,学不会 的 来怼我!nill0705 • 1291浏览 • 0回复
-
分布式 全局唯 一 ID 方案这么多?d_hero • 827浏览 • 0回复
-
分布式 环境下如何保证 ID 的 唯 一 性fatherlaw • 1610浏览 • 0回复
-
分库分表 的 9种 分布式 主键 ID 生成 方案,挺全乎 的heatdog • 719浏览 • 0回复
-
美团(Leaf) 分布式 ID 生成器 , 好用 的 一批 !heatdog • 926浏览 • 0回复
-
OpenHarmony 分布式 相机(中)NL_AIDC_XJS • 3009浏览 • 3回复
- 豪横!程序员搭个人博客,一个 GitHub 账号足矣! 2022-12-21 11:00:02发布
- 一口气说出 过滤器 和 拦截器 6个区别,别再傻傻分不清了 2022-12-21 10:57:46发布