点击“ 程序员内点事 ”关注,选择“ 设置星标 ”
坚持学习,好文每日送达!
引言
接着《一口气说出 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信息表';1516CREATE 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信息表';2526INSERT INTO `tiny_id_info` (`id`, `biz_type`, `begin_id`, `max_id`, `step`, `delta`, `remainder`, `create_time`, `update_time`, `version`)27VALUES28 (1, 'test', 1, 1, 100000, 1, 0, '2018-07-21 23:52:58', '2018-07-22 23:19:27', 1);2930INSERT INTO `tiny_id_info` (`id`, `biz_type`, `begin_id`, `max_id`, `step`, `delta`, `remainder`, `create_time`, `update_time`, `version`)31VALUES32 (2, 'test_odd', 1, 1, 100000, 2, 1, '2018-07-21 23:52:58', '2018-07-23 00:39:24', 3);333435INSERT INTO `tiny_id_token` (`id`, `token`, `biz_type`, `remark`, `create_time`, `update_time`)36VALUES37 (1, '0f673adf80504e2eaa552f5d791b644c', 'test', '1', '2017-12-14 16:36:46', '2017-12-14 16:36:48');3839INSERT INTO `tiny_id_token` (`id`, `token`, `biz_type`, `remark`, `create_time`, `update_time`)40VALUES41 (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
测试一下。
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-810datasource.tinyid.secondary.username=root11datasource.tinyid.secondary.password=12345612datasource.tinyid.secondary.testOnBorrow=false13datasource.tinyid.secondary.maxActive=10
1、Http方式
tinyid
内部一共提供了四个http
接口来获取ID和号段。
1package com.xiaoju.uemc.tinyid.server.controller; 2 3/** 4 * @author du_imba 5 */ 6@RestController 7@RequestMapping("/id/") 8public class IdContronller { 910 private static final Logger logger = LoggerFactory.getLogger(IdContronller.class);11 @Autowired12 private IdGeneratorFactoryServer idGeneratorFactoryServer;13 @Autowired14 private SegmentIdService segmentIdService;15 @Autowired16 private TinyIdTokenService tinyIdTokenService;17 @Value("${batch.size.max}")18 private Integer batchSizeMax;1920 @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 }34353637 @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 }5859 @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 }7172 @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 }8485}
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
包
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:99992tinyid.token=0f673adf80504e2eaa552f5d791b644c```3
在Java代码调用TinyId
也很简单,只需要一行代码。
1 // 根据业务类型 获取单个ID2 Long id = TinyId.nextId("test");34 // 根据业务类型 批量获取10个ID5 List<Long> ids = TinyId.nextId("test", 10);
Tinyid
整个项目的源码实现也是比较简单,像与数据库交互更直接用jdbcTemplate实现
1@Override 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端的压力。
往期精彩回顾
技术/面试/吐槽
程序员内点事这都有长按扫码可关注
在看点这里