RocketMQ源码剖析之createUniqID方法

目录

版本信息:

写在前面:

源码剖析:

总计:


版本信息:

RocketMQ-5.1.3

源码地址:https://github.com/apache/rocketmq

写在前面:

首先,笔者先吐槽一下RocketMQ的官方,源码中啥注释都没有,虽然文档给的多,但是很多都是版本过时不及时更新,阅读者只能靠自己的强硬的技术去理解~

回归正题,如今互联网的技术离不开微服务、分布式的体系,所以在分布式的体系中如何创建一个全局唯一的ID是大家所面对的问题。现大厂都提出了解决方案:Twitter的雪花算法(Snowflake)、美团的Leaf算法、以及Mysql、Redis 这种自带原子性操作的中间件。

当然RocketMQ为分布式而生的消息队列中间件肯定也需要有他的分布式ID解决方案(虽然笔者不知道该如何称呼,源码中也没有给出)~ 

源码剖析:

createUniqID 方法是本文章所论述的点,此方法在生产者往Broker 发送消息时,给发送的消息创建一个唯一KEY时调用。

public static void setUniqID(final Message msg) {
    // 如果用户自定义了唯一key,RocketMQ就不提供默认实现
    // 否则RocketMQ调用createUniqID 方法提供默认的实现
    if (msg.getProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX) == null) {
        msg.putProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX, createUniqID());
    }
}

在看createUniqID之前,我们先需要看一些变量的初始化作为看createUniqID 方法的铺垫~

org.apache.rocketmq.common.message MessageClientIDSetter类中。

public class MessageClientIDSetter {
    
    private static final int LEN;                   // 原有长度
    private static final char[] FIX_STRING;         // 变化后的char字符数组(其实就是字符串)
    private static final AtomicInteger COUNTER;     // 原子变量
    private static long startTime;                  // 记录开始时间
    private static long nextStartTime;              // 记录最后时间(用于更新)

    static {
        byte[] ip;
        try {
            // 获取到本机的IP地址。
            // 一共占用4个字节。
            ip = UtilAll.getIP();
        } catch (Exception e) {
            ip = createFakeIP();
        }
        // 4(ip) + 2(pid进程id) + 4(类加载器的HashCode) + 4(时间差值) + 2(自增位) 
        LEN = ip.length + 2 + 4 + 4 + 2;

        // 拼接处理分布式体系的10字节
        // 处理 本机IP + JVM进程PID + HashCode
        ByteBuffer tempBuffer = ByteBuffer.allocate(ip.length + 2 + 4);
        tempBuffer.put(ip);
        tempBuffer.putShort((short) UtilAll.getPid());
        tempBuffer.putInt(MessageClientIDSetter.class.getClassLoader().hashCode());

        // 把10字节中的内容 作为索引值 转换成16进制的字符串表示
        // 简单来说,这一步就是编码,因为ID不可能用负数或者二进制01表示。
        FIX_STRING = UtilAll.bytes2string(tempBuffer.array()).toCharArray();
        // 设置当前启动的时间(用来4字节的计算时间差值)
        // 并且设置末尾时间,末尾时间用来更新时间
        // 如果有小伙伴看过雪花算法,就明白,雪花算法的时间差值是41位,限制只能用多少年,而这里做了优化,动态更新时间。
        // 这里的起始时间是本月的1号。
        // 末尾时间是下月的1号。
        setStartTime(System.currentTimeMillis());
        // 原子性自增,用于最后2位的自增位。
        COUNTER = new AtomicInteger(0);
    }
}

这里是核心所在,所以在提供的源码中笔者有非常详细的注释,并且这里做一个总结:

  1. RocketMQ的分布式ID算法核心就在这里,用了16字节表示:4(本机IP) + 2(进程的PID) + 4(类加载器的HashCode) + 4(时间差值) + 2(自增位)
  2. 本机IP + 进程PID + 类加载器HashCode 解决了分布式环境下集群的重复可能性
  3. 最后2位的自增位,用于处理本机RocketMQ的并发重复可能性
  4. 时间差值用于解码时获得创建的时间

看到这里,有读者会问,那源码中FIX_STRING 变量是干啥的,这很简单,如上图所示总共16字节,因为byte用10进制可能会有负数,作为分布式ID总不能是一串负数或者二进制01表示把。所以RocketMQ用16字节的Byte数组转换成 16进制的字符串表示,存储在FIX_STRING中。

这里需要注意,在上文的初始化代码中,只对 本机IP + JVM进程PID + HashCode做了处理,后续的时间差值和自增位在createUniqID方法中做处理。

以上的铺垫已做完,直接看到org.apache.rocketmq.common.message MessageClientIDSetter类中createUniqID方法

public static String createUniqID() {
        // 在Java中byte占用一个字节,char占用2个字节
        // 所以这里需要创建LEN * 2 的char数组来存放完 16字节的数据。
        char[] sb = new char[LEN * 2];
        
        // 在上文的初始化中把 IP + PID + HashCode 16进制字符串放入到FIX_STRING
        // 这里把FIX_STRING拷贝到sb中。
        System.arraycopy(FIX_STRING, 0, sb, 0, FIX_STRING.length);
        long current = System.currentTimeMillis();
        // 是否需要更新时间。
        if (current >= nextStartTime) {
            setStartTime(current);
        }
        // 计算出运行时间差值。
        int diff = (int)(current - startTime);
        if (diff < 0 && diff > -1000_000) {
            diff = 0;
        }
        // 获取到长度,这个长度作为索引。
        int pos = FIX_STRING.length;
        // 这里填充了4字节的时间差值
        UtilAll.writeInt(sb, pos, diff);
        pos += 8;
        // 这里填充了2字节的自增位。
        UtilAll.writeShort(sb, pos, COUNTER.getAndIncrement());
        // char数组转换成字符串。
        return new String(sb);
    }
  1. 获取到初始化中初始的FIX_STRING字段,此字段已经处理了本机IP + JVM进程PID + HashCode,后续的时间差值 和 自增位还没做处理,下文会对其做处理
  2. 获取到当前时间,判断是否需要更新时间(没个月月初更新)
  3. 得到时间差值赋值给diff变量,并且转换成16进制的字符表示
  4. 获取到自增值,并且转换成16进制的字符表示
  5. 最终把16进制的 char数组转换成String对象
  6. 整个分布式的ID 创建过程完毕。

总计:

只需要记住三部分

  1. 第一部分用于处理分布式的重复可能性(IP + PID + HashCode)
  2. 第二部分用于记录创建时间
  3. 第三部分用于处理机器的并发创建ID的重复可能性(原子变量解决)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【DDD】领域驱动设计总结——如何构造领域模型

文章目录 一 分离领域二 领域对象分类2.1 实体(ENTITY)2.2 值对象(VALUE OBJECT)2.3 服务(SERVICE)2.4 模块&#xff08;&#xff2d;ODULE&#xff09; 三 管理领域对象的生命周期3.1 聚合&#xff08;AGGREGATE&#xff09;3.2 工厂&#xff08;FACTORY&#xff09;3.3 存储库…

UE5富文本框学习(用途:A(名字)用刀(图片)击杀B(名字))

UE5-UMG教程-通用控件&#xff1a;多格式文本块&#xff08;RichTextBlock&#xff09;_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1Pu4y1k7Z2/?p54&spm_id_frompageDriver 结果示例&#xff1a; 1.添加富文本框 2.添加文字样式库 点添加&#xff0c;更改每行行…

PostGIS学习教程八:空间关系

PostGIS学习教程八&#xff1a;空间关系 到目前为止&#xff0c;我们只使用了测量&#xff08;ST_Area、ST_Length&#xff09;、序列化&#xff08;ST_GeomFromText&#xff09;或者反序列化&#xff08;ST_AsGML&#xff09;几何图形&#xff08;geometry&#xff09;的空间…

【OJ比赛日历】快周末了,不来一场比赛吗? #12.02-12.08 #15场

CompHub[1] 实时聚合多平台的数据类(Kaggle、天池…)和OJ类(Leetcode、牛客…&#xff09;比赛。本账号会推送最新的比赛消息&#xff0c;欢迎关注&#xff01; 以下信息仅供参考&#xff0c;以比赛官网为准 目录 2023-12-02&#xff08;周六&#xff09; #4场比赛2023-12-03…

五种多目标优化算法(MSSA、MOAHA、MOPSO、NSGA3、NSGA2)求解微电网多目标优化调度(MATLAB)

一、多目标优化算法简介 &#xff08;1&#xff09;多目标鳟海鞘算法MSSA 多目标优化算法&#xff1a;多目标鳟海鞘算法&#xff08;Multi-objective Salp Swarm Algorithm &#xff0c;MSSA&#xff09;-CSDN博客 参考文献&#xff1a; S. Mirjalili, A.H. Gandomi, S.Z. M…

C语言--每日选择题--Day31

第一题 1. 下面程序 i 的值为&#xff08;&#xff09; int main() {int i 10;int j 0;if (j 0)i; elsei--; return 0; } A&#xff1a;11 B&#xff1a;9 答案及解析 B if语句中的条件判断为赋值语句的时候&#xff0c;因为赋值语句的返回值是右操作数&#xff1b; …

系统设计面试指南之分布式任务调度

1 简介 任务是需要资源(CPU 时间、内存、存储、网络带宽等)在指定时间内完成的一段计算工作。 通过智能地将资源分配给任务以满足任务级和系统级目标的系统称为任务调度程序。 任务调度程序&#xff1a; 及时决定和分配资源给任务的过程称为任务调度。 当我们在 Facebook 发…

13-Vue基础之自定义指令与插槽的使用

个人名片&#xff1a; &#x1f60a;作者简介&#xff1a;一名大二在校生 &#x1f921; 个人主页&#xff1a;坠入暮云间x &#x1f43c;座右铭&#xff1a;懒惰受到的惩罚不仅仅是自己的失败&#xff0c;还有别人的成功。 &#x1f385;**学习目标: 坚持每一次的学习打卡 文章…

【驱动】串口驱动分析(二)-tty core

前言 tty这个名称源于电传打字节的简称&#xff0c;在linux表示各种终端&#xff0c;终端通常都跟硬件相对应。比如对应于输入设备键盘鼠标&#xff0c;输出设备显示器的控制终端和串口终端。也有对应于不存在设备的pty驱动。在如此众多的终端模型之中&#xff0c;linux是怎么…

jmeter做接口自动化测试,你可能只是个新手!

jmeter 这个工具既可以做接口的功能测试&#xff0c;也可以做自动化测试&#xff0c;还可以做性能测试&#xff0c;其主要用途就是用于性能测试。但是&#xff0c;有些公司和个人&#xff0c;就想用 jmeter 来做接口自动化测试。 你有没有想过呢&#xff1f; 下面我就给大家讲…

WPF应用开发之附件管理

在我们之前的开发框架中&#xff0c;往往都是为了方便&#xff0c;对附件的管理都会进行一些简单的封装&#xff0c;目的是为了方便快速的使用&#xff0c;并达到统一界面的效果&#xff0c;本篇随笔介绍我们基于SqlSugar开发框架的WPF应用端&#xff0c;对于附件展示和控件的一…

软考:2024年软考高级:软件工程

软考&#xff1a;2024年软考高级: 提示&#xff1a;系列被面试官问的问题&#xff0c;我自己当时不会&#xff0c;所以下来自己复盘一下&#xff0c;认真学习和总结&#xff0c;以应对未来更多的可能性 关于互联网大厂的笔试面试&#xff0c;都是需要细心准备的 &#xff08;1…

性能测试线上监控

如果你的产品出现了一个线上问题&#xff0c;你会是怎么样的反应&#xff1f; 也许会跟下面这张图一样。 哇&#xff01;有一个线上bug&#xff0c;好慌呀&#xff01;&#xff01; 咦&#xff0c;问题似乎自动解决了&#xff1f;渐渐冷静。 不对&#xff01;&#xff01;&a…

数据结构:图文详解顺序表的各种操作(新增元素,查找元素,删除元素,给指定位置元素赋值)

目录 一.顺序表的概念 二.顺序表的实现 新增元素 默认尾部新增 指定位置添加元素 查找元素 查找是否存在 查找元素对应的位置 查找指定位置对应的元素 删除元素 获取顺序表长度 清空顺序表 一.顺序表的概念 在线性数据结构中&#xff0c;我们一般分为俩类&#xf…

2023.11.29 -hmzx电商平台建设项目 -核销主题阶段总结

目录 1.准备源数据 2.准备数仓工具进行源数据同步到ods层,本项目使用Datax 3.使用Datax完成数据同步前建表时的方案选择 3.1同步方式区别: 3.2存储格式和压缩区别: 4.在hive中创建表,共31个表 5.数仓概念 和 数仓建模方案 5.1数仓的基本概念 5.2 数仓建模方案 关系建模…

接口02-Java

接口02 一、接口与继承类1、引入2、总结&#xff08;1&#xff09;接口和继承解决的问题不同。&#xff08;2&#xff09;接口比继承更加灵活。&#xff08;3&#xff09;接口在一定程度上实现代码解耦。 二、接口的多态性1、多态参数① 回顾&#xff1a;继承中的多态② 接口的…

银河麒麟v10——植物大战僵尸原版——2023教程

1、原版安装包如下&#xff1a; 阿里云盘分享https://www.alipan.com/s/Qn5DpDKs2YT 2、麒麟信息&#xff1a; 3、安装命令&#xff1a; 注意&#xff1a;最后一步&#xff0c;需要先解压tar包&#xff0c;再切到PlantsVsZombies.exe所在目录下&#xff0c;再执行启动命令&a…

Linux C/C++高级全栈开发(后端/游戏/嵌入式/高性能网络/存储/基础架构)

Linux C/C高级全栈开发是一个涉及到多个领域的综合性技术要求&#xff0c;需要对Linux系统、C/C编程语言以及各种相关的技术进行深入的理解和应用。 下面是一些涵盖的主要技术领域和技能要点&#xff1a; Linux系统基础&#xff1a;熟悉Linux操作系统的原理和常用命令&#xf…

命名管道:简单案例实现

&#x1f4df;作者主页&#xff1a;慢热的陕西人 &#x1f334;专栏链接&#xff1a;Linux &#x1f4e3;欢迎各位大佬&#x1f44d;点赞&#x1f525;关注&#x1f693;收藏&#xff0c;&#x1f349;留言 本博客主要内容讲解了什么是命名管道&#xff0c;匿名管道和命名管道的…

咨询+低代码,强强联合为制造业客户赋能

内容来自演讲&#xff1a;沈毅 | 遨睿智库 | 董事长 & 王劭禹 | 橙木智能 | 联合创始人 摘要 文章主要讲述了智库董事长沈毅创办广告公司的经历&#xff0c;以及他在管理公司过程中遇到的问题和挑战&#xff0c;最后通过与明道云以及橙木智能联合创始人王邵禹老师的合作&…