RocketMQ源码探索之数据落盘

RocketMQ是一款分布式消息中间件,使用文件进行消息存储,保证了消息数据的持久性,通过主备节点间的数据复制实现了消息数据的可靠性。上篇文章《RocketMQ源码探索之主备同步》我们讲述了RocketMQ主备节点间的数据复制逻辑,本文笔者将跟读者说说RocketMQ的数据落盘构成及实现逻辑。

1-数据落盘

RocketMQ的数据落盘在实现上包括两部分内容:存储及后台服务。RocketMQ的消息数据存储在commitlog文件中,所有的操作都是围绕着commitlog进行,下图为RocketMQ落盘体系的实现类图,其中CommitLog、MappedFile为消息数据的内存存放区域,而GroupCommitService、FlushRealTimeService、CommitRealService则是不同落盘模式下数据从内存到文件的写入服务。

  落盘模式

落盘模式是指消息数据从内存到文件的写入方式,分为同步、异步两种。

SYNC-FLUSH

同步落盘,每次消息的写入都会触发系统层的IO操作,IO粒度较细、频度较高,数据可靠性高。

ASYNC-FLUSH

异步落盘,消息写入内存后,即可返回,数据由后台服务集中写入,IO粒度较大、频度较低,但丢失数据的可能性较高。

ASYNC-FLUSH WITH TRANSIENT POOL 

异步落盘加暂态池,该模式和ASYNC-FLUSH类似,但额外提供了临时buffer提供快速的数据写入,而不是像ASYNC-FLUSH一样直接写入FileChannel。

后端服务

在CommitLog类实现中存在如下两个服务:commit服务、flush服务,前者用于数据从transientPool到FileChannel的commit,后者用于数据从FileChannel或MappedByteBuffer到文件系统的落盘。

  • Commit服务

    在启用transientPool时,数据首先被写入transientPool的buffer中,再由后台的commitLogService将数据从transientPool写入到FileChannel中。transientPool启用有如下三个条件,全部满足时才能启用:

          1、brokerRole:非Slave

          2、刷盘模式:ASYNC_FLUSH

          3、transientStorePoolEnable:true

  • Flush服务

    将数据从FileChannel或MappedByteBuffer(map到FileChannel的buffer)中数据刷入文件系统的磁盘,成功刷盘的数据不会因为系统异常而丢失。刷盘服务有两个:GroupCommitService、FlushRealTimeService。

  • 后台服务和落盘模式对应关系

不同落盘模式和与之对应的后台服务之间的关系如下图:

上面为大家简单介绍了RocketMQ数据落盘体系的大概构成、配置,下面我们通过一张图来解读一下数据落盘的实现逻辑。

从上图可以看到,所有的数据操作最终都在MappedFile中完成。MappedFile包含如下几个关键元素:

  • 2个buffer

    writeBuffer(异步模式加暂态池模式下使用)、mappedByteBuffer

  • 3个位置

    wrotePos(writeBuffer最新写入位置)、committedPos(writeBuffer最新提交位置)、flushedPos(fileChannel最新落盘位置)

  • SYNC

    只有直接映射到FileChannel的mappedByteBuffer,所有操作都通过该buffer直接反应到commitlog上。在同步模式下wrotePos、committedPos两者相等,均表示当前写入位置,而flushedPos则是记录的上次forceout的位置,flushedPos意味着该位置前的数据已经落盘。

  • ASYNC

    异步模式下,如果没启用transientPool,则所有元素和SYNC一样。启用transientPool后,所有数据写入到writeBuffer,然后再由CommitRealTimeService将数据commit到FileChannel,最后再由FlushRealTimeService将数据从FileChannel落到commitlog。wrotePos:writeBuffer中最新写入位置。committedPos:writeBuffer中已经写入到FileChannel中的数据位置。flushedPos:FileChannel中已经forceout到commitlog中的数据位置,该位置之前的数据属于安全数据。

      

讲完了RocketMQ的数据落盘的整体逻辑,我们接下来将逐一介绍3个负责数据提交、刷盘的后台服务。

2-后台服务

GroupCommitService

服务SYNC刷盘模式,数据落盘实时性最高,但响应时间最慢。提供putRequest接口接受GroupCommitRequest,刷盘成功或失败后,负责唤醒处于等待状态SendMessageThread。

GroupCommitRequest

  • nextRequestOffset:期待刷盘偏移量,CommitLog刷盘偏移量大于该值时表示刷盘成功。

  • flushOK:刷盘结果

GroupCommitService

持续运行的后端服务,遍历所有requestRead中所有GroupCommitRequest对象,检查当前请求对应的消息是否已完成刷盘。如果刷盘成功,则唤醒等待同步结果的SendMessageThread,结束handleDiskFlush处理,否则直接调用flush发起刷盘操作。

CommitRealTimeService

同样是个后台服务,它负责完成writeBuffer中数据到FileChannel的写入(即所谓的commit)。影响CommitRealTimeService的参数有如下几个:

commitIntervalCommitLog:CommitRealTimeService等待被唤醒间隔。

commitCommitLogLeastPages:非定时提交最小提交页数,即wrotePos - commitedPos间的数据量要求大于commitCommitLogLeastPages * OS_PAGE_SIZE,默认OS_PAGE_SIZE为4K。

commitDataThoroughInterval:强制提交间隔。

整个CommitRealTimeService的核心是数据提交:

提交页限制调整

当前时间和lastCommitTime间隔大于commitDataThoroughInterval则进行强制提交,提交时会解除提交页面数下限限制,将commitDataLeastPages归零。

数据提交:调用MappedFileQueue的commit进行buffer数据提交,如果不是强制提交,这个执行过程很可能因为未提交的数据量不到commitCommitLogLeastPages*4K而被放弃。MappedFileQueue的commit返回值是通过比较提交后的offset和commitedWhere得出的,两者不相等则表示提交了部分数据,返回false。CommitRealTimeService发现提交了部分数据后会主动唤醒FlushRealTimeService进行FileChannel中的数据forceout。FlushRealTimeService被唤醒后,commitRealTimeService进入等待状态,等待间隔为commitIntervalCommitLog。问题:CommitRealTimeService在等待状态下是否可能被唤醒?被谁唤醒?答案是唤醒者就是前面所说的handleDishFlush。

FlushRealTimeService

同样是个后台服务,它负责在ASYNC模式下完成FileChannel中数据到文件系统(FS)的写入(即所谓的forceout)。影响FlushRealTimeService的参数有如下几个:

flushCommitLogTimed:是否定时Flush,定时Flush主线程会进入sleep,非定时则进入waitForRunning。

flushIntervalCommitLog:Flush间隔,如果为定时Flush,则严格保证这个间隔,无唤醒操作。

flushCommitLogLeastPages:最小刷盘页数,即flushedPos - commitedPos间的数据要求大于flush CommitLogLeastPages * OS_PAGE_SIZE,默认OS_PAGE_SIZE为4K。

flushCommitLogThoroughInterval:强制刷盘间隔,即超过该间隔刷盘不受flushCommitLogLeastPages限制。

整个FlushRealTimeService的核心是数据刷盘:

数据刷盘数据输盘操作在这里将被委托给MappedFileQueue完成。对于输盘操作超过500ms的,日志有提醒。

MappedFile的flush和flush比较类似,但又有所区别。他们都会进行待提交或刷盘数据检查,超过页限制后才能进行提交或刷盘逻辑。在commit中,对于为启用的transientPool的直接返回wrotePos,只有启用transientPool的才会执行writeBuffer到Filechannel的写入。而在flush中,未启用transientPool的,则将flushedPos-wrotePos的数据刷盘,而对于启用transientPool的,则仅将flushedPos-committedPos进行刷盘处理。

总结

至此我们已经讲完了SYNC、ASYNC模式以及这两种模式对落盘的影响。总体来说落盘数据频度越高,数据丢失概率越低,安全性则越高,但相对来说系统层的IO粒度也越细。

  • SYNC:每次消息的写入均会触发FileChannel的force out,无论消息数据大小,因而消息数据完整性、安全性最高、IO粒度最细,性能测试时能看到多个线程对IO的高开销。

  • ASYNC:无论是否采用采用transientPool,数据提交均和MessageStore的配置有关,主要可能影响配置包括commit、flush间隔及commit、flush页面数限制。前者控制提交频率,后者控制commit、flush的最小页面数,以此实现IO的大块写入,避免IO粒度过细。

在主备模式下,数据落盘对SendMessageThread的响应时间影响比较大。SendMessageThread在将消息写入内存后,需要进行handleDiskFlush及handleHA的处理,而这两块均和落盘有关。

  • handleDiskFlush

 对于SYNC,提交刷盘确认请求,直到等到刷盘结果。对于ASYNC,唤醒commit或flush服务后直接返回。

  • handleHA

如果master-slave间数据复制是同步模式,slave端确认完成后SendMessageThread的等待才会结束,而这个等待时间是和committed有关的,如果新写入数据迟迟不变为committed状态,则它不会被同步到slave端。对于非transientPool模式,wrote就是committed,即写入后即刻变成可同步复制状态,而对于transientPool则需要CommitRealTimeService将数据从writeBuffer写入到FileChannel后才会变成可同步状态,而影响CommitRealTimeService的因素包括commitCommitLogLeastPages、commitDataThoroughInterval,所以在配置选择前需要考虑好配置对SendMessageThread的等待时间影响。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值