CommitLog

生产者向Broker发送的消息,会以顺序写的方式,写入CommitLog文件,CommitLog文件的根目录由配置参数storePathRootDir决定,默认每一个CommitLog的文件大小为1G,如果文件写满会新建一个CommitLog文件,以该文件中第一条消息的偏移量为文件名,小于20位用0补齐:



比如第一个文件中第一条消息的偏移量为0,那么第一个文件的名称为00000000000000000000,当这个文件存满之后,需要重新建立一个CommitLog文件,一个文件大小为1G,

1GB = 102410241024 = 1073741824 Bytes,所以下一个文件就会被命名为00000000001073741824。

数据格式

CommitLog中存储的每条消息的数据格式如下:

  • 消息总长度,占4个字节;
  • 魔数,占4个字节;
  • 消息体CRC校验和,占4个字节;
  • 队列ID,占4个字节;
  • 标识,占4个字节;
  • 队列的偏移量,占8个字节;
  • 消息在文件的物理偏移量,占8个字节;
  • 系统标识,占4个字节;
  • 发送消息的时间戳,占8个字节;
  • 发送消息的主机地址,占8个字节;
  • 存储时间戳,占8个字节;
  • 存储消息的主机地址,占8个字节;
  • 消息的重试次数,占4个字节;
  • 事务相关偏移量,占8个字节;
  • 消息内容的长度,占4个字节;
  • 消息内容,由于消息内容不固定,所以长度不固定;
  • 主题名称的长度,占1个字节;
  • 主题名称内容,长度不固定;
  • 消息属性长度,占2个字节;
  • 消息属性内容,长度不固定;

RocketMQ一般会保存一个物理偏移量offSet,从CommitLog中获取消息内容。

ConsumeQueue

RocketMQ在消息存储的时候将消息顺序写入CommitLog文件,如果想根据Topic对消息进行查找,需要扫描所有CommitLog文件,这种方式性能低下,所以RocketMQ又设计了ConsumeQueue存储消息的逻辑偏移量,offset逻辑偏移量从0开始编号,进行递增,消息写入CommitLog以后,会构建对应的 ConsumeQueue文件。

在RocketMQ的存储文件目录下,有一个consumequeue文件夹,里面按Topic分组,每个Topic一个文件夹,Topic文件夹内是该Topic的所有消息队列,以消息队列ID命名文件夹,每个消息队列都有自己对应的ConsumeQueue文件:

ConsumeQueue中存储的每条数据大小是固定的,总共20个字节,数据格式如下:

  • 消息在CommitLog文件的偏移量,占用8个字节;
  • 消息大小,占用4个字节;
  • 消息Tag的hashcode值,用于tag过滤,占用8个字节;

消费进度

消费者在拉取消息进行消费的时候,就是通过这个ConsumeQueue实现的,消费者在向Broker发送消息拉取请求之前,需要知道应该从哪条消息开始消费,对于广播模式,消息的消费进度保存在消费者端本地,对于集群模式,消息的消费进度保存在Broker中,所以拉取某个消息队列的消息之前,会向Broker发送请求,获取该消息队列的消费进度,消费进度在RocketMQ的存储目录中有一个对应的文件,叫consumerOffset.json,里面的offsetTable中保存了每个消息队列的消费进度,这个消费进度值对应的就是ConsumeQueue中的逻辑偏移量,它由定时任务定时进行持久化:

{
"offsetTable":{
"TestTopic@TestTopicGroup":{ // 主题名称@消费者组名称
0:0, // 每个消息队列对应的消费进度,Key中的0表示队列0,value中的0表示消息在ConsumeQueue中的逻辑偏移量
1:1,
2:1,
3:0
}
}
}

拿到消息队列对应的消费进度时,就可以根据这个值从Broker拉取消息,Broker收到请求后,会根据这个值从ConsumeQueue中获取此条消息在CommitLog中的物理偏移量,根据物理偏移量再从CommitLog中获取消息内容返回给消费者。

总结

当消息写入CommitLog之后会构建对应的ConsumeQueue文件,每个消息队列MessageQueue都会有一个对应的ConsumeQueue文件,ConsumeQueue文件中的offset记录的是消息的逻辑索引,从0开始编号进行递增,比如存入了3条消息,那么对应的offset分别为0、1、2,消费者在消费的时候拿到的消费进度就是这个offset,然后根据offset从ConsumeQueue文件中获取数据,里面记录了消息在CommitLog文件中的物理偏移量,之后就可以从CommitLog中获取消息内容。

消费者消费完毕之后,会保存这个消费进度,对于集群模式,消费进度会保存在Borker端,Broker会定时将消费进度进行持久化,如果消费者刚启动的时候,会向Broker发起请求获取之前记录的消费进度。

IndexFile

为了便于消息查找,RocketMQ还设计了IndexFile,支持根据Key对消息进行查找,在发送消息的时候可以设置一个唯一Keys值,用于标识这条消息,之后就可以根据这个Keys值对消息进行查找。

Keys: 服务器会根据 keys 创建哈希索引,设置后,可以在 Console 系统根据 Topic、Keys 来查询消息,由于是哈希索引,请尽可能保证 key 唯一,例如订单号,商品 Id 等。

   Message msg = new Message(topic, RandomUtils.getStringByUUID().getBytes());
// 订单Id
String orderId = "20034568923546";
msg.setKeys(orderId);

IndexFile文件结构

每个indexFile文件的大小是固定的,一个IndexFile文件大约可以保存2000W个消息的索引,IndexFile的文件结构如下:

IndexHeader

index header记录indexFile文件的整体信息,占40个字节,有以下信息:

  • beginTimestamp:当前indexFile文件中第一条消息的存储时间;
  • endTimestamp:当前indexFile文件中最后一条消息存储时间;
  • beginPhyoffset:当前indexFile文件中第一条消息在Commitlog中的偏移量;
  • endPhyoffset:当前indexFile文件中最后一条消息在commitlog中的偏移量;
  • hashSlotCount:已经使用的hash槽的个数;
  • indexCount:索引项中记录的所有消息索引总数;

hash slot

RocketMQ在每个IndexFile文件中划分了500W个hash槽,在向文件中添加消息索引的时候,会取出消息的Keys(实际会使用Topic + "#" + key进行拼装做为IndexFile文件的Key)计算hash值,然后对hash槽总数取余,来判断应该放到哪个hash槽。

index item

索引项中记录每个Key的索引信息,有以下部分组成:

  • keyHash:消息的key计算出来的的hashcode值,
  • phyOffset:消息在CommitLog中的物理偏移量;
  • timeDiff:消息的存储时间减去IndexHeader中的beginTimestamp(当前indexFile文件中第一条消息的存储时间);
  • preIndexNo:当哈希冲突的时候,用于指向上一个索引,可以看做当哈希冲突的时候,使用一个链表将该哈希槽下的所有元素串起来,使用头插法增加新的元素;

消息索引添加

举个例子,比如现在有一条消息,它的Key值1,假设哈希槽的个数为10,这里对哈希计算简化,直接用1对哈希槽个数取余,得到值为0,那么这条消息将落入哈希槽0的位置,然后会在索引项区域建立该消息的索引信息:

如果新增一条消息2,它的Key值为2,用2对哈希槽个数取余,依旧得到哈希槽0,此时产生哈希冲突,将哈希槽0处存储的值改为消息2的索引项,并将消息2索引项中的preIndexNo指向消息1的索引项,形成一个链表:

参考

孤翁-进阶篇 RocketMQ 原理之key查询

迟钝先生-RocketMQ的Index File

【RocketMQ】RocketMQ存储结构设计的更多相关文章

  1. Kafka与RocketMq文件存储机制对比

    一个商业化消息队列的性能好坏,其文件存储机制设计是衡量一个消息队列服务技术水平和最关键指标之一. 开头问题 kafka文件结构和rocketMQ文件结构是什么样子?特点是什么? 一.目录结构 Kafk ...

  2. 终于弄明白了 RocketMQ 的存储模型

    RocketMQ 优异的性能表现,必然绕不开其优秀的存储模型 . 这篇文章,笔者按照自己的理解 , 尝试分析 RocketMQ 的存储模型,希望对大家有所启发. 1 整体概览 首先温习下 Rocket ...

  3. 一文详解RocketMQ的存储模型

    摘要:RocketMQ 优异的性能表现,必然绕不开其优秀的存储模型. 本文分享自华为云社区<终于弄明白了 RocketMQ 的存储模型>,作者:勇哥java实战分享. RocketMQ 优 ...

  4. Kafka和RocketMQ底层存储之那些你不知道的事

    大家好,我是yes. 我们都知道 RocketMQ 和 Kafka 消息都是存在磁盘中的,那为什么消息存磁盘读写还可以这么快?有没有做了什么优化?都是存磁盘它们两者的实现之间有什么区别么?各自有什么优 ...

  5. RocketMQ(十):数据存储模型设计与实现

    消息中间件,说是一个通信组件也没有错,因为它的本职工作是做消息的传递.然而要做到高效的消息传递,很重要的一点是数据结构,数据结构设计的好坏,一定程度上决定了该消息组件的性能以及能力上限. 1. 消息中 ...

  6. 源码分析 RocketMQ DLedger 多副本存储实现

    目录 1.DLedger 存储相关类图 1.1 DLedgerStore 1.2 DLedgerMemoryStore 1.3 DLedgerMmapFileStore 2.DLedger 存储 对标 ...

  7. kafka 和 rocketMQ 的数据存储

    kafka 版本:1.1.1 一个分区对应一个文件夹,数据以 segment 文件存储,segment 默认 1G. 分区文件夹: segment 文件: segment 的命名规则是怎样的? kaf ...

  8. RocketMQ存储机制与确认重传机制

    引子 消息队列之前就听说过,但一直没有学习和接触,直到最近的工作流引擎项目用到,需要了解学习一下.本文主要从一个初学者的角度针对RocketMQ的存储机制和确认重传机制做一个浅显的总结. 存储机制 我 ...

  9. RocketMQ(六):nameserver队列存储定位解析

    在rocketmq中,nameserver充当了一个配置管理者的角色,看起来好似不太重要.然而它是一个不或缺的角色,没有了它的存在,各个broker就是一盘散沙,各自为战. 所以,实际上,在rocke ...

  10. 🏆【Alibaba中间件技术系列】「RocketMQ技术专题」系统服务底层原理以及高性能存储设计分析

    设计背景 消息中间件的本身定义来考虑,应该尽量减少对于外部第三方中间件的依赖.一般来说依赖的外部系统越多,也会使得本身的设计越复杂,采用文件系统作为消息存储的方式. RocketMQ存储机制 消息中间 ...

随机推荐

  1. 解决QRCODE生成的二维码微信长按不识别问题

    问题描述 QRcode 生成二维码,展示到页面,微信打开,长按二维码没反应.而直接放二维码图片上去可以识别. 问题原因 手机兼容问题qrcode在页面生成二维码时,会生成一个canvas标签和一个im ...

  2. SQL Sever 各版本的适用环境

    很多用visual studio做开发的朋友经常会用到sqlserver数据库,但是往往在选择的时候就不知道该使用哪个版本了,今天就给大家分享一下sqlserver各个版本之间的区别,以及各个版本之间 ...

  3. Unity iOS Guideline 1.3 - Safety - Kids Category 被拒

    解决办法: 不使用unity 的分析SDK //关闭unity信息收集服务 UnityEngine.Analytics.Analytics.enabled = false; UnityEngine.A ...

  4. .NET 8 Preview 6发布,支持新的了Blazor呈现方案 和 VS Code .NET MAUI 扩展

    2023年7月11日 .NET 8 Preview 6,.NET 团队在官方博客发布了系列文章: Announcing .NET 8 Preview 6[1] ASP.NET Core updates ...

  5. 效率回归,工具库之美「GitHub 热点速览」

    刚开源就变成新星的 igl,不仅获得了 2k+ star,也能提高你开发游戏的效率,摆平一切和图形有关的问题.如果这个没有那么惊艳的话,还有 The-Art-of-Linear-Algebra,重燃了 ...

  6. MAUI Blazor如何隐藏滚动条

    MAUI Blazor如何隐藏滚动条 Windows 在Windows上是最简单的,改css就可以了,把下面这段添加到app.css中 ::-webkit-scrollbar { display: n ...

  7. Django学习笔记:第三章D的路由和视图

    1.网站的入口--路由和视图 URL是网站Web服务的入口.用户在浏览器输入URL发出请求后,django会根据路由系统,运行对应的视图函数,然后返回信息到浏览器中. 1.1 认识路由 创建项目时,会 ...

  8. Openjob 1.0.5 发布,新增 Agent

    什么是 Openjob? Openjob 基于Akka架构的新一代分布式任务调度框架.支持多种定时任务.延时任务.工作流设计,采用无中心化架构,底层使用一致性分片算法,支持无限水平扩容. 完善的任务日 ...

  9. 编码技巧 --- 使用dynamic简化反射

    引言 dynamic 是 Framework 4.0 就出现特性,它的出现让 C# 具有了弱语言类型的特性.编译器在编译的时候不再对类型进行检查,默认 dynamic 对象支持开发者想要的任何特性. ...

  10. 查看Nginx是否启动

    查看Nginx进程 ps -ef | grep nginx 输出如下: root 1036 1 0 Jul15 ? 00:00:00 nginx: master process /www/server ...