分布式全局唯一id实现-3 springCloud-MyBatis-Plus集成滴滴分布式全局id(Tinyid)

news/2024/10/21 7:47:22/

前言:滴滴通过mysql来定义好id 的初始值和增长的步长,每次可以将一段连续的数字id取出放入到内存中,当需要使用id 的使用,每次id+1 ,如果发现id 的值已经超出了改段最大的id 值,则取下个段的id 继续使用;本文将简单介绍id生成的实现并将其与Mybatis-plus进行整合。

滴滴id 的设计思路:https://github.com/didi/tinyid/wiki

1 tinyid 实现概述:

关于tinyid 的设计思路在https://github.com/didi/tinyid/wiki 中已经阐述的比较清楚了,本文只对个别关注点做概述;

1.1 table:
tinyid 设计时创建了两张表,tiny_id_token和tiny_id_info;

  • tiny_id_token:这个主要做了一层简单的token 检验,在向tinyid 服务获取id端和id时,根据业务类型做了一次校验;
  • tiny_id_info:这个表是id段使用的表:表中字段的标识也比较清楚,通过biz_type 来进行业务的隔离,max_id 来作为下一段id的起始值,step 表明每次要取的id 长度;version 做更新的并发控制;
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=utf8 COMMENT='id信息表';
  • delta和remainder 则决定了Tinyid 使用db 的服务数上限;以及解决多台Tinyid 服务 号段相同的问题;如果现在有5台Tinyid 的生成服务分别连接5个db,每次id的增量步长都为5,如果号段都是100到200,初始id 都是100 则生成id 如下:
    |db | Server | delta | remainder | 生成的id |
    |db1 | server1 | 5 | 0 | 100 |
    | db2| server2 | 5 | 1 | 101 |
    | db3| server3 | 5 | 2 | 102 |
    | db4| server4 | 5 | 3 | 103 |
    | db5| server5 | 5 | 4 | 104 |

  • 通过remainder 余数控制id 的生成,可以使得多台服务即使id段相同,但最终生成的id 是不重复的;
    通过 id 生成的方法 可以清楚的看到 如何通过余数控制 的生成:每次得到新的id段,都进入init 控制 初始值currentId 的生成

public void init() {if (isInit) {return;}synchronized (this) {if (isInit) {return;}// 获取到初始的idlong id = currentId.get();//  通过 id % delta == remainder 最终得到 初始的id 值if (id % delta == remainder) {isInit = true;return;}for (int i = 0; i <= delta; i++) {id = currentId.incrementAndGet();if (id % delta == remainder) {// 避免浪费 减掉系统自己占用的一个idcurrentId.addAndGet(0 - delta);isInit = true;return;}}}}public Result nextId() {// 通过 id % delta == remainder 最终得到 初始的id 值init();// 后续的id 可以直接增加设置好的delta 得到下一idlong id = currentId.addAndGet(delta);// 如果id 超出 id 段if (id > maxId) {return new Result(ResultCode.OVER, id);}// id 超出预设的一定值,则加载下一个id 段if (id >= loadingId) {return new Result(ResultCode.LOADING, id);}return new Result(ResultCode.NORMAL, id);}

1.2 当通过client 获取 Tinyid 的id 段时,如果多个db数据库,则会随机选择一个db 数据库,获取id段:
客户端选择服务端url HttpSegmentIdServiceImpl :

 @Overridepublic SegmentId getNextSegmentId(String bizType) {// 服务端url获取String url = chooseService(bizType);String response = TinyIdHttpUtils.post(url, TinyIdClientConfig.getInstance().getReadTimeout(),TinyIdClientConfig.getInstance().getConnectTimeout());logger.info("tinyId client getNextSegmentId end, response:" + response);if (response == null || "".equals(response.trim())) {return null;}SegmentId segmentId = new SegmentId();String[] arr = response.split(",");segmentId.setCurrentId(new AtomicLong(Long.parseLong(arr[0])));segmentId.setLoadingId(Long.parseLong(arr[1]));segmentId.setMaxId(Long.parseLong(arr[2]));segmentId.setDelta(Integer.parseInt(arr[3]));segmentId.setRemainder(Integer.parseInt(arr[4]));return segmentId;}private String chooseService(String bizType) {List<String> serverList = TinyIdClientConfig.getInstance().getServerList();String url = "";if (serverList != null && serverList.size() == 1) {url = serverList.get(0);} else if (serverList != null && serverList.size() > 1) {Random r = new Random();url = serverList.get(r.nextInt(serverList.size()));}url += bizType;return url;}

server 端 DynamicDataSource:

public class DynamicDataSource extends AbstractRoutingDataSource {private List<String> dataSourceKeys;@Overrideprotected Object determineCurrentLookupKey() {if(dataSourceKeys.size() == 1) {return dataSourceKeys.get(0);}// 随机获取一个id db Random r = new Random();return dataSourceKeys.get(r.nextInt(dataSourceKeys.size()));}public List<String> getDataSourceKeys() {return dataSourceKeys;}public void setDataSourceKeys(List<String> dataSourceKeys) {this.dataSourceKeys = dataSourceKeys;}
}

2 Mybatis-plus整合:

对于Mybatis-plus 整合也比较简单,因为在实体中当 id 使用Mybatis-plus 自带的雪花算法id 生成时,最终会通过package com.baomidou.mybatisplus.core.incrementer DefaultIdentifierGenerator 的 nextId 获取下一id ,所以只要遵从Mybatis-plus 雪花算法id 的生成规范,重写掉DefaultIdentifierGenerator 的nextId 方法即可实现 id 的自定义生成;
2.1 实体中需要通过TableId 修饰

@TableId(value = "id", type = IdType.ASSIGN_ID )private String id;

2.2 map层需要继承BaseMapper:

@Repository
public interface UserMapper extends BaseMapper<User> {}

2.3 创建package com.baomidou.mybatisplus.core.incrementer 包,并创建DefaultIdentifierGenerator 类:重写nextId 方法,通过
TinyId生成id主键。

package com.baomidou.mybatisplus.core.incrementer;import com.alibaba.fastjson2.JSONObject;
import com.xiaoju.uemc.tinyid.client.utils.TinyId;
import lombok.extern.slf4j.Slf4j;import java.util.Map;@Slf4j
public class DefaultIdentifierGenerator implements IdentifierGenerator {@Overridepublic Long nextId(Object entity) {Object o = null;if (null != entity) {Map<String, Object> eMap = JSONObject.parseObject(JSONObject.toJSONString(entity), Map.class);if (eMap.containsKey("bizType")) {o = eMap.get("bizType");}}if (null == o) {o = "test";}Long id = TinyId.nextId(o.toString());return id;}}

git 参考地址:https://codeup.aliyun.com/61cd21816112fe9819da8d9c/dd-uid.git


http://www.ppmy.cn/news/89991.html

相关文章

MIME类型

秋风阁(https://focus-wind.com/) 文章目录 MIME类型参考文档MIME介绍MIME语法mimetype与Content-TypeMIME类型大全常用MIME类型application类型text类型image类型video类型audio类型model类型其他类型multipart复合类型 MIME类型 参考文档 IANA官方MIME类型大全IBM Integrat…

CompletableFuture详解-初遇者-很细

目录 一、创建异步任务 1. supplyAsync 2. runAsync 3.获取任务结果的方法 二、异步回调处理 1.thenApply和thenApplyAsync 2.thenAccept和thenAcceptAsync 2.thenRun和thenRunAsync 3.whenComplete和whenCompleteAsync 4.handle和handleAsync 三、多任务组合处理 1…

MyBatis动态SQL,基本语法加实战,一篇搞懂

问题&#xff1a; 有的时候我们需要实现批量删除&#xff1a;delete from t_car where id in(1,2,3,4,5,6,…这⾥的值是动态的&#xff0c;根据⽤户选择的 id不同&#xff0c;值是不同的); 多条件查询:有时我们需要根据多个不同地条件来进行查询&#xff0c;比如&#xff1a;se…

论文分享 | 视野约束下多机器人系统的最小持久图生成与编队控制

阿木推出的Prometheus项目校园赞助活动&#xff0c;再次迎来开发者参与&#xff01; 北京理工大学自动化学院赵欣悦同学&#xff0c;在Prometheus开源仿真架构的基础上进行了二次开发&#xff0c;且使用P450进行了真机实验并发表了相关论文&#xff0c;其论文《视野约束下多机…

python 处理超大文件

1、生成器 处理超大文件 当处理超大文件时&#xff0c;使用生成器可以避免将整个文件读入内存中&#xff0c;从而减少内存占用&#xff0c;提高程序的效率。 生成器是一种特殊的迭代器&#xff0c;可以通过函数来生成一系列的值&#xff0c;而不需要一次性生成所有值。在处理超…

[论文阅读] Explicit Visual Prompting for Low-Level Structure Segmentations

[论文地址] [代码] [CVPR 23] Abstract 我们考虑了检测图像中低层次结构的通用问题&#xff0c;其中包括分割被操纵的部分&#xff0c;识别失焦像素&#xff0c;分离阴影区域&#xff0c;以及检测隐藏的物体。每个问题通常都有一个特定领域的解决方案&#xff0c;我们表明&am…

【Netty】Netty 解码器(十二)

文章目录 前言一、编解码概述1.1、编解码器概述1.2、Netty 内嵌的编码器 二、解码器2.1、ByteToMessageDecoder 抽象类2.1.1、常用方法2.1.2、将字节转为整形的解码器示例 三、ReplayingDecoder 抽象类四、MessageToMessageDecoder 抽象类总结 前言 回顾Netty系列文章&#xf…

快速上手,使用关键字驱动测试框架作为Web应用程序的自动化测试武器

目录 前言&#xff1a; 一、预备知识 二、关键字驱动测试框架 三、关键字驱动测试脚本 四、总结 前言&#xff1a; 自动化测试是软件测试中的重要环节之一&#xff0c;它可以帮助开发人员提高测试效率&#xff0c;节省时间和人力成本。随着互联网的发展&#xff0c;Web应…