【开源项目】TinyId 全网最好的分布式ID生成系统的源码解析

news2024/11/28 3:48:28

TINYID介绍

项目地址:https://github.com/didi/tinyid

Tinyid是滴滴开发的一款分布式ID系统,Tinyid是在美团(Leaf)leaf-segment算法基础上升级而来,不仅支持了数据库多主节点模式,还提供了tinyid-client客户端的接入方式,使用起来更加方便。但和美团(Leaf)不同的是,Tinyid只支持号段一种模式不支持雪花模式。

适用场景:只关心ID是数字,趋势递增的系统,可以容忍ID不连续,可以容忍ID的浪费

不适用场景:像类似于订单ID的业务,因生成的ID大部分是连续的,容易被扫库、或者推算出订单量等信息

TINYID原理

Tinyid是基于号段模式实现,再简单啰嗦一下号段模式的原理:就是从数据库批量的获取自增ID,每次从数据库取出一个号段范围,例如 (1,1000] 代表1000个ID,业务服务将号段在本地生成1~1000的自增ID并加载到内存.。

Tinyid会将可用号段加载到内存中,并在内存中生成ID,可用号段在首次获取ID时加载,如当前号段使用达到一定比例时,系统会异步的去加载下一个可用号段,以此保证内存中始终有可用号段,以便在发号服务宕机后一段时间内还有可用ID。

核心源码

客户端

获取ID入口

public class ClientTest {

    @Test
    public void testNextId() {
        for (int i = 0; i < 100; i++) {
            Long id = TinyId.nextId("test");
            System.out.println("current id is: " + id);
        }
    }
}

TinyId初始化

public class TinyId {
    private static IdGeneratorFactoryClient client = IdGeneratorFactoryClient.getInstance(null);

    private TinyId() {

    }
}

IdGeneratorFactoryClient#getInstance,初始化连接工厂,主要是指定tinyid.server的地址信息。

    public static IdGeneratorFactoryClient getInstance(String location) {
        if (idGeneratorFactoryClient == null) {
            synchronized (IdGeneratorFactoryClient.class) {
                if (idGeneratorFactoryClient == null) {
                    if (location == null || "".equals(location)) {
                        init(DEFAULT_PROP);
                    } else {
                        init(location);
                    }
                }
            }
        }
        return idGeneratorFactoryClient;
    }

    private static void init(String location) {
        idGeneratorFactoryClient = new IdGeneratorFactoryClient();
        Properties properties = PropertiesLoader.loadProperties(location);
        String tinyIdToken = properties.getProperty("tinyid.token");
        String tinyIdServer = properties.getProperty("tinyid.server");
        String readTimeout = properties.getProperty("tinyid.readTimeout");
        String connectTimeout = properties.getProperty("tinyid.connectTimeout");

        if (tinyIdToken == null || "".equals(tinyIdToken.trim())
                || tinyIdServer == null || "".equals(tinyIdServer.trim())) {
            throw new IllegalArgumentException("cannot find tinyid.token and tinyid.server config in:" + location);
        }

        TinyIdClientConfig tinyIdClientConfig = TinyIdClientConfig.getInstance();
        tinyIdClientConfig.setTinyIdServer(tinyIdServer);
        tinyIdClientConfig.setTinyIdToken(tinyIdToken);
        tinyIdClientConfig.setReadTimeout(TinyIdNumberUtils.toInt(readTimeout, DEFAULT_TIME_OUT));
        tinyIdClientConfig.setConnectTimeout(TinyIdNumberUtils.toInt(connectTimeout, DEFAULT_TIME_OUT));

        String[] tinyIdServers = tinyIdServer.split(",");
        List<String> serverList = new ArrayList<>(tinyIdServers.length);
        for (String server : tinyIdServers) {
            String url = MessageFormat.format(serverUrl, server, tinyIdToken);
            serverList.add(url);
        }
        logger.info("init tinyId client success url info:" + serverList);
        tinyIdClientConfig.setServerList(serverList);
    }

TinyId#nextId(java.lang.String),根据业务类型获取IdGenerator

    public static Long nextId(String bizType) {
        if(bizType == null) {
            throw new IllegalArgumentException("type is null");
        }
        IdGenerator idGenerator = client.getIdGenerator(bizType);
        return idGenerator.nextId();
    }

AbstractIdGeneratorFactory,生成IdGenerator的工厂。

public abstract class AbstractIdGeneratorFactory implements IdGeneratorFactory {

    private static ConcurrentHashMap<String, IdGenerator> generators = new ConcurrentHashMap<>();

    @Override
    public IdGenerator getIdGenerator(String bizType) {
        if (generators.containsKey(bizType)) {
            return generators.get(bizType);
        }
        synchronized (this) {
            if (generators.containsKey(bizType)) {
                return generators.get(bizType);
            }
            IdGenerator idGenerator = createIdGenerator(bizType);
            generators.put(bizType, idGenerator);
            return idGenerator;
        }
    }

    /**
     * 根据bizType创建id生成器
     *
     * @param bizType
     * @return
     */
    protected abstract IdGenerator createIdGenerator(String bizType);
}

IdGeneratorFactoryClient#createIdGenerator,生成CachedIdGenerator,对应的SegmentIdService的实现是HttpSegmentIdServiceImpl。这边的IdGenerator是不能用Spring的单例bean来实现的,因为每一个业务都对应一个CachedIdGenerator

    @Override
    protected IdGenerator createIdGenerator(String bizType) {
        return new CachedIdGenerator(bizType, new HttpSegmentIdServiceImpl());
    }

CachedIdGenerator初始化,初始化currentnext。这里特别注意的是loadCurrent用的是同步方法。current.useful()会判断当前号段是否已经用完。

    protected volatile SegmentId current;
    protected volatile SegmentId next;    

	public CachedIdGenerator(String bizType, SegmentIdService segmentIdService) {
        this.bizType = bizType;
        this.segmentIdService = segmentIdService;
        loadCurrent();
    }

    public synchronized void loadCurrent() {
        if (current == null || !current.useful()) {
            if (next == null) {
                SegmentId segmentId = querySegmentId();
                this.current = segmentId;
            } else {
                current = next;
                next = null;
            }
        }
    }

    private SegmentId querySegmentId() {
        String message = null;
        try {
            SegmentId segmentId = segmentIdService.getNextSegmentId(bizType);
            if (segmentId != null) {
                return segmentId;
            }
        } catch (Exception e) {
            message = e.getMessage();
        }
        throw new TinyIdSysException("error query segmentId: " + message);
    }

CachedIdGenerator#nextId(),获取下一个ID的方法,判断当前号段是否用完,没有用完,从当前号段中获取;用完,则加载下一个号段。CachedIdGenerator#loadNext,用的是异步的方式。

    public Long nextId() {
        while (true) {
            if (current == null) {
                loadCurrent();
                continue;
            }
            Result result = current.nextId();
            if (result.getCode() == ResultCode.OVER) {
                loadCurrent();
            } else {
                if (result.getCode() == ResultCode.LOADING) {
                    loadNext();
                }
                return result.getId();
            }
        }
    }

    public void loadNext() {
        if (next == null && !isLoadingNext) {
            synchronized (lock) {
                if (next == null && !isLoadingNext) {
                    isLoadingNext = true;
                    executorService.submit(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                // 无论获取下个segmentId成功与否,都要将isLoadingNext赋值为false
                                next = querySegmentId();
                            } finally {
                                isLoadingNext = false;
                            }
                        }
                    });
                }
            }
        }
    }

HttpSegmentIdServiceImpl#getNextSegmentId,通过Http调用,获取号段信息。

    @Override
    public SegmentId getNextSegmentId(String bizType) {
        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;
    }

服务端

IdContronller#nextSegmentId,判断是否可以访问,核心是查询tiny_id_token表里面是否存在对应的业务和token数据。调用segmentIdService.getNextSegmentId(bizType);

    @RequestMapping("nextSegmentId")
    public Response<SegmentId> nextSegmentId(String bizType, String token) {
        Response<SegmentId> response = new Response<>();
        if (!tinyIdTokenService.canVisit(bizType, token)) {
            response.setCode(ErrorCode.TOKEN_ERR.getCode());
            response.setMessage(ErrorCode.TOKEN_ERR.getMessage());
            return response;
        }
        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;
    }

DbSegmentIdServiceImpl#getNextSegmentId,该方法用到了重试和读已提交事务。主要是并发情况下可能存在版本冲突。获取号段的信息,主要是根据业务更新tiny_id_info这张表。

    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED)
    public SegmentId getNextSegmentId(String bizType) {
        // 获取nextTinyId的时候,有可能存在version冲突,需要重试
        for (int i = 0; i < Constants.RETRY; i++) {
            TinyIdInfo tinyIdInfo = tinyIdInfoDAO.queryByBizType(bizType);
            if (tinyIdInfo == null) {
                throw new TinyIdSysException("can not find bizType:" + bizType);
            }
            Long newMaxId = tinyIdInfo.getMaxId() + tinyIdInfo.getStep();
            Long oldMaxId = tinyIdInfo.getMaxId();
            int row = tinyIdInfoDAO.updateMaxId(tinyIdInfo.getId(), newMaxId, oldMaxId, tinyIdInfo.getVersion(),
                    tinyIdInfo.getBizType());
            if (row == 1) {
                tinyIdInfo.setMaxId(newMaxId);
                SegmentId segmentId = convert(tinyIdInfo);
                logger.info("getNextSegmentId success tinyIdInfo:{} current:{}", tinyIdInfo, segmentId);
                return segmentId;
            } else {
                logger.info("getNextSegmentId conflict tinyIdInfo:{}", tinyIdInfo);
            }
        }
        throw new TinyIdSysException("get next segmentId conflict");
    }

tiny_id_info这张表,step表示步长,号段的长度。begin_id和max_id是用来记录当前获取的号段的起始位和终止位。

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信息表';

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/524556.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

23种设计模式之适配器模式(Adapter Pattern)

前言&#xff1a;大家好&#xff0c;我是小威&#xff0c;24届毕业生&#xff0c;在一家满意的公司实习。本篇文章将23种设计模式中的适配器模式&#xff0c;此篇文章为一天学习一个设计模式系列文章&#xff0c;后面会分享其他模式知识。 如果文章有什么需要改进的地方还请大佬…

[GYCTF2020]EasyThinking

功能&#xff1a;登录&#xff0c;注册&#xff0c;搜索 回显登录用户名&#xff0c;搜索历史 简单测试搜索历史发现可能不存在sql注入 www.zip下载源码 访问一个不存在的路径&#xff0c;爆出 thinkphp的框架&#xff0c;版本是6.0.0 参考&#xff1a;ThinkPHP 6.0.1 漏洞…

MyBatis操作数据库实现增删改查

创建数据库 语句要分别执行 CREATE DATABASE mybatis;USE mybatis;CREATE TABLE user(id INT(10) NOT NULL PRIMARY KEY,name VARCHAR(20) DEFAULT NULL,INSERT INTO user(id,name,pwd) VALUES (1,张三,123456), (2,李四,121212), (3,王五,1314520) 搭配环境 1、在pojo包创…

25.基于混合整数规划方法的微网电池储能容量优化配置

关键词&#xff1a;储能容量优化 储能配置 微网 编程语言&#xff1a;matlab 主题&#xff1a;基于混合整数规划方法的微网电池储能容量优化配置 主要内容&#xff1a; 本代码目的为实现微电网内电池容量的优化配置&#xff0c;目标函数为配置过程中整体的运行成本最小或…

【Spring】Spring AOP面向切面编程

文章目录 什么是Spring AOP&#xff1f;为什么要使用AOP&#xff1f;AOP相关组成的概念切面切点通知连接点 Spring AOP实现创建切面创建切点创建通知创建连接点示例演示 Spring AOP的实现原理 什么是Spring AOP&#xff1f; 想要知道Spring AOP&#xff0c;就得先了解AOP AOP是…

【周期信号】工程测试-数据处理-信号分析课程试题:周期信号与周期信号相加,所得信号一定是周期信号吗?

一、问题分析 某课程的作业题中&#xff0c;有下面的一种题目&#xff0c;判断两个周期信号相加&#xff0c;是否是周期信号&#xff0c;以及计算周期长短是多少。 非常显然&#xff0c;1、3、4题都很容易判断。 第2题&#xff0c;我们重点分析。 二、网上的错误论述 在百…

Vivado运用 Language Template 来创建set_input_delay/set_output...

时序约束中的 set_input_delay/set_output_delay 约束一直是一个难点&#xff0c;无论是概念、约束值的计算&#xff0c;还是最终的路径分析&#xff0c;每一次都要费一番脑子。Vivado为方便用户创建输入输出接口的约束&#xff0c;整理出了一套非常实用的InputDelay/Output De…

【C++】类与对象(3)

【C】类与对象&#xff08;3&#xff09; 作者&#xff1a;爱写代码的刚子 时间&#xff1a;2023.5.9 本篇博客干货比较多&#xff0c;主要是对类和对象知识的进一步加深&#xff0c;可能有点晦涩。主要介绍的内容为&#xff1a;深入构造函数&#xff0c;初始化列表&#xff0c…

《操作系统》——进程与线程

在上一期博客中&#xff0c;我们学习了关于操作系统中计算机系统概述的基本知识。今天&#xff0c;我将带领学习的是关于操作系统中一个非常重要的概念——进程与线程&#xff01;&#xff01;&#xff01; 目录 前言 &#xff08;一&#xff09;进程的基本概念和特征 1、进…

[golang gin框架] 33.Gin 商城项目- 集成支付宝微信支付、生成支付二维码、监听处理异步通知跳转到订单页面

一.界面展示 当用户点击去支付时,请求支付界面,并 展示对应订单相关数据,以及 支付方式相关操作,点击对应的支付方式,进行支付操作 该界面对应的功能: 1.进入该界面,后台逻辑判断: 是否存在该订单,如果不存在,则跳转到购物车页面;如果存在,则获取对应订单相关数据,并 渲染到页面…

0511课后作业(C高级)

1.编写一个名为myfirstshell.sh的脚本&#xff0c;它包括以下内容。 1、包含一段注释&#xff0c;列出您的姓名、脚本的名称和编写这个脚本的目的 2、和当前用户说“hello 用户名” 3、显示您的机器名 hostname 4、显示上一级目录中的所有文件的列表 5、显示变量PATH和HOM…

iOS播放与编辑HDR视频

在iPhone12发布后&#xff0c;支持使用Dolby Vision来录制HDR视频。至此&#xff0c;升级到iOS14.1系统后&#xff0c;已经支持录制、播放、编辑和导出HDR视频。接下来&#xff0c;让我们一起探索HDR视频的各种操作。 一、HDR视频边编辑边预览 1、Profile与Level HDR视频中&…

Java奠基】实现面向对象编程方法

目录 标准的JavaBean类 设计对象并使用 对象封装 this关键字 构造方法 要知道对象是一个又一个能帮助我们解决问题的东西&#xff0c;但是这些东西并不是凭空出现的&#xff0c;需要我们根据设计图来进行制造&#xff0c;而这些一个一个的设计图就是一个一个的类。 标准的…

ChatGPT分销版多开v3.9.1-新增 语音识别和绘画多个引擎-已测试

众所周知ChatGPT在国内是无法正常使用的 而我们模块要做的就是这一点让普通人使用上ChatGPT 或娱乐或作为生产力工具 当前ChatGPT是非常火的 但是国内环境复杂&#xff0c;所以机会来了。。。 ChatGPT分销版多开v3.9.1&#xff1a;公众号H5版本 目前没反编译前端容易封号…

计算机网络-SNMP协议与pysnmp

1.概念 2.典型架构 3.snmp的信息交互 4.MIB 4.1常见MIB节点 5.SNMP管理模型 MIB位于被管理进程 6.SNMP的三个版本 6.1 SNMPv1 6.2 SNMPv2C 6.3 SNMPv3 6.3.1 SNMP3的基本操作 6.3.2 SNMP交互GET 6.3.3 SNMP交互-GETBULK 6.3.4 SNMP交互-SET 6.3.5 SNMP交互-trap 6.3.6 SNMP交…

【开源之夏 2023】欢迎报名 Dragonfly、Kata Containers、Nydus 社区项目!

开源之夏是由“开源软件供应链点亮计划”发起并长期支持的一项暑期开源活动&#xff0c;旨在鼓励在校学生积极参与开源软件的开发维护&#xff0c;促进优秀开源软件社区的蓬勃发展&#xff0c;培养和发掘更多优秀的开发者。 活动联合国内外各大开源社区&#xff0c;针对重要开…

Dubbo消费端源码深入分析(8)

目录 简介 过滤器 &#xff08;Filter&#xff09; 负载均衡接口 &#xff08;LoadBalance&#xff09; 容错接口 &#xff08;Cluster&#xff09; 源码分析 1. 获取Invoker过程 2. 获取动态代理对象proxy 3. 最后调用此动态代理对象的invoke方法 过滤器、容错组件、负…

基础IO(二)

磁盘 1.基础概念2.磁盘线性理解3.文件系统4.inode与文件名5.理解增删查改6.补充细节 &#x1f31f;&#x1f31f;hello&#xff0c;各位读者大大们你们好呀&#x1f31f;&#x1f31f; &#x1f680;&#x1f680;系列专栏&#xff1a;【Linux的学习】 &#x1f4dd;&#x1f4…

Chapter7:非线性控制系统分析(下)

第七章:非线性控制系统分析 Exercise7.11 设非线性系统结构图如下图所示,分析系统运动并计算自振参数。 解: 将 3 3 3个串联非线性环节进行等效合并,由于反馈通道饱和特性与前向通道饱和特性同时进入饱和状态,所以反馈通道的非线性环节相当于不起作用,将前向通道的另两…

【计算机视觉 | 自然语言处理】Hugging Face 超详细介绍和使用教程

文章目录 一、前言二、可以获得什么&#xff1f;三、入门实践3.1 帮助文档3.2 安装3.3 模型的组成3.4 BERT模型的使用3.4.1 导入模型3.4.2 使用模型3.4.2.1 tokenizer 3.5 model3.6 后处理 一、前言 Hugging Face 起初是一家总部位于纽约的聊天机器人初创服务商&#xff0c;他…