引言
接着《一口氣說出 9種 分佈式ID生成方式,面試官有點懵了》來繼續詳細的介紹分佈式ID生成器,你們比較感興趣的美團(Leaf)
、滴滴(Tinyid)
、百度(uid-generator)
三個開源項目,美團(Leaf)已經講完,詳見《9種分佈式ID生成之美團(Leaf)實戰》,今天結合實戰搞一下滴滴開源的(Tinyid
)。javascript
Tinyid
介紹
Tinyid
是滴滴開發的一款分佈式ID系統,Tinyid
是在美團(Leaf)
的leaf-segment
算法基礎上升級而來,不只支持了數據庫多主節點模式,還提供了tinyid-client
客戶端的接入方式,使用起來更加方便。但和美團(Leaf)不一樣的是,Tinyid只支持號段一種模式不支持雪花模式。java
Tinyid的特性
- 全局惟一的long型ID
- 趨勢遞增的id
- 提供 http 和 java-client 方式接入
- 支持批量獲取ID
- 支持生成1,3,5,7,9...序列的ID
- 支持多個db的配置
適用場景:只關心ID是數字,趨勢遞增的系統,能夠容忍ID不連續,能夠容忍ID的浪費mysql
不適用場景:像相似於訂單ID的業務,因生成的ID大部分是連續的,容易被掃庫、或者推算出訂單量等信息git
Tinyid
原理
Tinyid
是基於號段模式實現,再簡單囉嗦一下號段模式的原理:就是從數據庫批量的獲取自增ID,每次從數據庫取出一個號段範圍,例如 (1,1000]
表明1000個ID,業務服務將號段在本地生成1~1000
的自增ID並加載到內存.。程序員
Tinyid
會將可用號段加載到內存中,並在內存中生成ID,可用號段在首次獲取ID時加載,如當前號段使用達到必定比例時,系統會異步的去加載下一個可用號段,以此保證內存中始終有可用號段,以便在發號服務宕機後一段時間內還有可用ID。github
原理圖大體以下圖: 面試
Tinyid
實現
Tinyid
的GitHub地址 : https://github.com/didi/tinyid.git算法
Tinyid
提供了兩種調用方式,一種基於Tinyid-server
提供的http方式,另外一種Tinyid-client
客戶端方式。 無論使用哪一種方式調用,搭建Tinyid
都必須提早建表tiny_id_info
、tiny_id_token
。sql
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
一、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
二、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](https://my.oschina.net/u/1162528) 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端的壓力。
整理了幾百本各種技術電子書,有須要的同窗能夠,在我同名公衆號回覆[ 666 ]自取。技術羣快滿了,想進的同窗能夠加我好友,和大佬們一塊兒吹吹技術,期待你的加入。
不管你是剛入行、仍是已經有幾年經驗的程序員,相信這份面試提綱都會給你很多助力,長按二維碼關注 『 程序員內點事 』 ,回覆 『 offer 』 自行領取,祝你們 offer 拿到手軟