Rocketmq 消息存储 & 事务

RocketMQ 存储概要设计

目前的 MQ 中间件从存储模型来分,分为需要持久化和不需要持久化的两种模型,大多数的是支持持久化存储的,比如 ActiveMQ,RabbitMQ Kafka,RocketMQ,ZeroMQ 不支持持久化存储,而业务系统也大多需要 MQ 有持久存储的能力,这样可以大大增加系统的高可用性。

持久化能力从存储方式和效率来看,文件系统高于 KV 存储, KV 存储又高于关系型数据库,直接操作文件系统肯定是最快的,但如果从可靠性的角度出发,直接操作文件系统是最低的,而关系型数据库的可靠性是最高的。

RocketMQ 主要存储的文件包括 Commitlog 文件、ConsumeQueue 文件、IndexFile。RocketMQ 将所有主题的消息存储在同一文件,确保消息发送时顺序写文件,尽最大的能力确保消息发送的高性能与高吞吐量。但由于一般的消息中间件是基于消息主题的订阅机制,这样便给按照消息主题检索消息带来了极大的不便。为了提高消息消费的效率,RocketMQ 引入了 ConsumeQueue 消息队列文件,每个消息主题包含多个消息消费队列,每个消息队列有一个消息 IndexFile 索引文件,其主要设计理念就是为了加速消息的检索性能,可以根据消息的属性快速从 Commitlog 文件中检索消息。整体如下:

     

  1. CommitLog :消息存储文件,所有消息主题的消息都存储在 CommitLog 文件中
  2. ConsumeQueue :消息消费队列,消息到达 CommitLog 文件后,将异步转发到消息消费队列,供消息消费者消费
  3. IndexFile :消息索引文件,主要存储消息 Key 与 Offset 的对应关系

消息存储结构

CommitLog

CommitLog 以物理文件的方式存放,每台 Broker 上的 CommitLog 被本机器所有 ConsumeQueue 共享,文件地址:$ {user.home}\store\${ commitlog}\${ fileName}。在 CommitLog 中,一个消息的存储长度是不固定的,RocketMQ 采取一些机制,尽量向 CommitLog 中顺序写,但是随机读。commitlog 文件默认大小为 1G ,可通过在 broker 配置文件中设置 mapedFileSizeCommitLog 属性来改变默认大小。

CommitLog windows存储位置:

   

Linux 存储位置:/root/store/commitlog 

Commitlog 文件存储的逻辑视图如下,每条消息的前面 4 个字节存储该条消息的总长度,但是一个消息的存储长度是不固定的。

   

ConsumeQueue

ConsumeQueue 是消息的逻辑队列,类似数据库的索引文件,存储的是指向物理存储的地址。每个 Topic 下的每个 Message Queue 都有一个对应的ConsumeQueue 文件, 文件地址在$ {$storeRoot} \consumequeue\$ {topicName} \$ { queueld} \$ {fileName}。

   

ConsumeQueue 中存储的是消息条目,为了加速 ConsumeQueue 消息条目的检索速度与节省磁盘空间,每一个 ConsumeQueue 条目不会存储消息的全量信息,消息条目如下:

   

ConsumeQueue 即为 Commitlog 文件的索引文件,其构建机制是当消息到达 Commitlog 文件后由专门的线程产生消息转发任务,从而构建消息消费队列文件(ConsumeQueue )与下文提到的索引文件。

这种存储机制的优点:

  1. CommitLog 顺序写,可以大大提高写入效率。(实际上,磁盘有时候会比你想象的快很多,有时候也比你想象的慢很多,关键在如何使用,使用得当,磁盘的速度完全可以匹配上网络的数据传输速度。目前的高性能磁盘, 顺序写速度可以达到 600MB/s ,超过了一般网卡的传输速度,这是磁盘比想象的快的地方,但是磁盘随机写的速度只有大概 100KB/s,和顺序写的性能相差 6000 倍!)
  2. 虽然是随机读,但是利用操作系统的 pagecache 机制,可以批量地从磁盘读取,作为 cache 存到内存中,加速后续的读取速度
  3. 为了保证完全的顺序写,需要 ConsumeQueue 这个中间结构 ,因为 ConsumeQueue 里只存偏移量信息,所以尺寸是有限的,在实际情况中,大部分的 ConsumeQueue 能够被全部读入内存,所以这个中间结构的操作速度很快,可以认为是内存读取的速度。此外为了保证 CommitLog 和 ConsumeQueue 的一致性, CommitLog 里存储了 Consume Queues 、Message Key、 Tag 等所有数据,即使 ConsumeQueue 丢失,也可以通过 commitLog 完全恢复出来

IndexFile

index 存的是索引文件,这个文件用来加快消息查询的速度。消息消费队列 RocketMQ 专门为消息订阅构建的索引文件,提高根据主题与消息检索消息的速度,使用 Hash 索引机制,具体是 Hash 槽与 Hash 冲突的链表结构。

   

Config

config 文件夹中存储着 Topic 和 Consumer 等相关信息。主题和消费者群组相关的信息就存在在此。

topics.json:topic 配置属性

subscriptionGroup.json:消息消费组配置信息。

delayOffset.json:延时消息队列拉取进度。

consumerOffset.json:集群消费模式消息消费进度。

consumerFilter.json :主题消息过滤信息。

   

其他

abort :如果存在 abort 文件说明 Broker 非正常闭,该文件默认启动时创建,正常退出之前删除

checkpoint :文件检测点,存储 commitlog 文件最后一次刷盘时间戳、 consumequeue 最后一次刷盘时间、 index 索引文件最后一次刷盘时间戳。

内存映射

内存映射文件,是由一个文件到一块内存的映射。文件的数据就是这块区域内存中对应的数据,读写文件中的数据,直接对这块区域的地址操作就可以,减少了内存复制的环节。所以说,内存映射文件比起文件 I/O 操作,效率要高,而且文件越大,体现出来的差距越大。

RocketMQ 通过使用内存映射文件来提高 IO 访问性能,无论是 CommitLog,ConsumeQueue 还是 IndexFile ,单个文件都被设计为固定长度,如果一个文件写满以后再创建一个新文件,文件名就为该文件第一条消息对应的全局物理偏移量。

文件刷盘机制

RocketMQ 存储与读写是基于 JDK NIO 的内存映射机制,具体使用 MappedByteBuffer(基于 MappedByteBuffer 操作大文件的方式,其读写性能极高),最终将消息存储到磁盘上,这样既能保证断电后恢复,又可以让存储的消息超出内存的限制,RocketMQ 为了提高性能,会尽可能地保证磁盘的顺序写。

消息存储时首先将消息追加到内存,再根据配置的不同的刷盘策略,在不同时间进行刷写磁盘。通过在 broker 配置文件中配置 flushDiskType 来设定刷盘方式,有两种刷盘方式,可选值为 ASYNC_FLUSH (异步刷盘), SYNC_FLUSH 同步刷盘) 默认为异步

   

异步刷盘方式

 在返回写成功状态时,消息可能只是被写入了内存的 pageCache ,写操作的返回快,吞吐量大;当内存里的消息积累到一定程度时,RocketMQ 会使用一个单独的线程按照某个设定的频统一触发写磁盘动作,快速写入。

同步刷盘方式

 在返回写成功状态时,消息已经被写人磁盘,具体流程是,消息写入内存的 pageCache 后,将同步调用 MappedByteBuffer.force() 方法,立刻通知刷盘线程刷盘,然后等待刷盘完成,刷盘线程执行完成后唤醒等待的线程,返回消息写成功的状态

总结

 实际应用中要结合业务场景,合理设置刷盘方式,尤其是同步刷盘的方式,由于频繁的触发磁盘写动作,会明显降低性能。通常情况下,应该把 Rocket 置成异步刷盘方式。

过期文件删除

由于 RocketMQ 操作 CommitLog,ConsumeQueue 文件是基于内存映射机制并在启动的时候会加载 commitlog,ConsumeQueue 目录下的所有文件,为了避免内存与磁盘的浪费,不可能将消息永久存储在消息服务器上,所以需要引入一种机制来删除己过期的文件。

删除过程分别执行清理消息存储文件( Commitlog )与消息消费 队列文件( ConsumeQueue 文件),消息消费队列文件与消息存储文件( Commitlog )共用一套过期文件处理机制。

RocketMQ 清除过期文件的方法是:如果非当前正在写的文件,在一定时间间隔内没有再次被更新,则认为是过期文件,可以被删除,RocketMQ 不会关注这个文件上的消息是否全部被消费。默认每个文件的过期时间为 42 小时(不同版本的默认值不同,这里以 4.4.0 为例),通过在 Broker 配置文件中设置 fileReservedTime 来改变过期时间,单位为小时。

触发文件清除操作的是一个定时任务,而且只有定时任务,文件过期删除定时任务的周期由该删除决定,默认每 10s 执行一次。

过期判断

文件删除主要是由这个配置属性:fileReservedTime:文件保留时间。也就是从最后一次更新时间到现在,如果超过了该时间,则认为是过期文件,可以删除。

另外还有其他两个配置参数:

deletePhysicFilesInterval:删除物理文件的时间间隔(默认是 100ms),在一次定时任务触发时,可能会有多个物理文件超过过期时间可被删除, 因此删除一个文件后需要间隔 deletePhysicFilesInterval 这个时间再删除另外一个文件,由于删除文件是一个非常耗费 IO 的操作,会引起消息插入消费的延迟(相比于正常情况下),所以不建议直接删除所有过期文件。

destroyMapedFileIntervalForcibly:在删除文件时,如果该文件还被线程引用,此时会阻止此次删除操作,同时将该文件标记不可用并且纪录当前时间戳,destroyMapedFileIntervalForcibly 表示文件在第一次删除拒绝后文件可保存的最大时间,在此时间内一直会被拒绝删除,当超过这个时间时,会将引用每次减少 1000,直到引用小于等于 0 为止,即可删除该文件.

删除条件

  1. 通过指定 deleteWhen 设置每天的固定时间删除过期文件,默认为凌晨 4 点
  2. 磁盘空间是否充足,如果磁盘空间不充足(DiskSpaceCleanForciblyRatio。磁盘空间强制删除文件水位。默认是 85),会触发过期文件删除操作。

另外还有 RocketMQ 的磁盘配置参数: 

  1. 物理使用率大于 diskSpaceWarningLevelRatio(默认 90%可通过参数设置),则会阻止新消息的插入
  2. 物理磁盘使用率小于 diskMaxUsedSpaceRatio(默认 75%),表示磁盘使用正常。

RocketMQ 中的事务消息

事务消息实现思想

RocketMQ 事务消息,是指发送消息事件和其他事件需要同时成功或同失败。比如银行转账,A 银行的某账户要转一万元到 B 银行的某账户。A 银行发送“B 银行账户增加一万元” 这个消息,要和“从 A 银行账户扣除一万元”这个操作同时成功或者同时失败。

RocketMQ 采用两阶段提交的方式实现事务消息,TransactionMQProducer 处理上面情况的流程是,先发一个“准备从 B 银行账户增加一万元”的消息,发送成功后做从 A 银行账户扣除一万元的操作,根据操作结果是否成功,确定之前的“准备从 B 银行账户增加一万元”的消息是做 commit 还是 rollback,RocketMQ 实现的具体流程如下:

   

1)发送方向 RocketMQ 发送“待确认”(Prepare)消息。

2 ) RocketMQ  将收到的“待确认”(一般写入一个 HalfTopic 主题<RMQ_SYS_TRANS_HALF_TOPIC>)消息持化成功后,向发送方回复消息已经发送成功,此时第一阶段消息发送完成。

发送方开始执行本地事件逻辑.

  1.  发送方根据事件执行结果向 RocketMQ 发送二次确认( Commit 还是 Rollback)消息,RocketMQ 收到 Commit 则将第一阶段消息标记为可投递(这些消息才会进入生产时发送实际的主题 RealTopic),订阅方将能够收到该消息;收到 Rollback 状态则删除第一阶段的消息,订阅方接收不到该消息。
  2. 如果出现异常情况,步骤 3 提交的二次确认最终未到达 RocketMQ,服务器在经过固定时间段后将对“待确认”消息、发起回查请求。
  3. 发送方收到消息回查请求后(如果发送一阶段消息的 Producer 不能工作,回查请求将被发送到和 Producer 在同一个 Group 里的其他 Producer ),通过检查对应消息的本地事件执行结果返回 Commit or Roolback 状态。

两阶段提交

提交半事务是一个阶段,提交全事务和事务回查是另外一个阶段,所以称之为两阶段提交。

事务状态回查机制

RocketMQ 通过 TransactionalMessageCheckService 线程定时去检测 RMQ_SYS_ TRANS_ HALF_TOPIC 主题中的消息,回查消息的事务状态

TransactionalMessageCheckService 的检测频率默认为 1 分钟,可通过在 broker.conf 文件中设置 transactionChecklnterval 来改变默认值,单位为毫秒。

代码实现(略)

LocalTransactionState 枚举类,

COMMIT_MESSAGE 提交消息,即 broker 确认了这条消息的正确性之后执行提交,标记这条消息可被消费,这样的话 consumer 就可以正常消费这条消息了;

ROLLBACK_MESSAGE 回滚消息,意思是当我们的本地主事务发生异常的时候,回滚本地事务的同时,同样需要一种方法通知到 rocketMq 不要继续发送消息了,当 broker 收到这个命令时候就会标记消息为 rollBack 的状态,consumer 就不能收到了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。
在分布式事务中使用RocketMQ时,可能会遇到一些坑。其中一个主要的坑是如何保证分布式事务的正确执行。有几种常用的分布式事务解决方案,包括XA方案(两阶段提交方案)、TCC方案(try、confirm、cancel)、SAGA方案、可靠消息最终一致性方案和最大努力通知方案。 在RocketMQ中,主要采用了可靠消息最终一致性方案来实现分布式事务。这个方案的主要思路是,在发送消息时,将消息事务绑定,然后将消息存储在Broker节点上,等到事务提交成功后再真正发送消息。如果事务提交失败,就会回滚消息,保证消息的一致性。这个方案相对来说较为简单,但是需要保证消息的可靠性和幂等性。 当然,在使用RocketMQ时,也需要考虑具体的业务需求、时间、成本以及开发团队的实力。分布式还有很多其他的坑,具体要根据情况来决定是否使用分布式架构。 希望以上信息对您有所帮助。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [这三年被分布式坑惨了,曝光十大坑](https://blog.csdn.net/jackson0714/article/details/108775573)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [RocketMQ事务消息学习及刨坑过程](https://blog.csdn.net/huangying2124/article/details/102634761)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值