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

前言:滴滴通过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;
            }
            // 获取到初始的id
            long 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) {
                    // 避免浪费 减掉系统自己占用的一个id
                    currentId.addAndGet(0 - delta);
                    isInit = true;
                    return;
                }
            }
        }
    }

    public Result nextId() {
        // 通过 id % delta == remainder 最终得到 初始的id 值
        init();
        // 后续的id 可以直接增加设置好的delta 得到下一id
        long 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 :

 @Override
    public 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;

    @Override
    protected 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 {
    @Override
    public 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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值