【RocketMQ】源码详解:Broker端消息刷盘流程
消息刷盘
同步入口:org.apache.rocketmq.store.CommitLog.GroupCommitService
异步入口:org.apache.rocketmq.store.CommitLog.FlushRealTimeService
刷盘有同步和异步两种,在实例化Commitlog的时候,会根据配置创建不同的服务
public CommitLog(final DefaultMessageStore defaultMessageStore) {this.mappedFileQueue = new MappedFileQueue(defaultMessageStore.getMessageStoreConfig().getStorePathCommitLog(),defaultMessageStore.getMessageStoreConfig().getMappedFileSizeCommitLog(), defaultMessageStore.getAllocateMappedFileService());this.defaultMessageStore = defaultMessageStore;if (FlushDiskType.SYNC_FLUSH == defaultMessageStore.getMessageStoreConfig().getFlushDiskType()) {// 如果是同步刷盘,则初始化GroupCommitService服务this.flushCommitLogService = new GroupCommitService();} else {// 如果是异步刷盘,则初始化GroupCommitService服务this.flushCommitLogService = new FlushRealTimeService();}// 异步转存数据服务:将堆外内存的数据提交到fileChannelthis.commitLogService = new CommitRealTimeService();this.appendMessageCallback = new DefaultAppendMessageCallback(defaultMessageStore.getMessageStoreConfig().getMaxMessageSize());putMessageThreadLocal = new ThreadLocal<PutMessageThreadLocal>() {@Overrideprotected PutMessageThreadLocal initialValue() {return new PutMessageThreadLocal(defaultMessageStore.getMessageStoreConfig().getMaxMessageSize());}};this.putMessageLock = defaultMessageStore.getMessageStoreConfig().isUseReentrantLockWhenPutMessage() ? new PutMessageReentrantLock() : new PutMessageSpinLock();}
提交刷盘
org.apache.rocketmq.store.CommitLog#submitFlushRequest
public CompletableFuture<PutMessageStatus> submitFlushRequest(AppendMessageResult result, MessageExt messageExt) {// Synchronization flush// 同步刷盘if (FlushDiskType.SYNC_FLUSH == this.defaultMessageStore.getMessageStoreConfig().getFlushDiskType()) {final GroupCommitService service = (GroupCommitService) this.flushCommitLogService;if (messageExt.isWaitStoreMsgOK()) {// 同步等待,即等待刷盘结果GroupCommitRequest request = new GroupCommitRequest(result.getWroteOffset() + result.getWroteBytes(),this.defaultMessageStore.getMessageStoreConfig().getSyncFlushTimeout());service.putRequest(request);return request.future();} else {// 同步刷盘但是不需要等待刷盘结果,那么唤醒同步刷盘线程,随后直接返回PUT_OKservice.wakeup();return CompletableFuture.completedFuture(PutMessageStatus.PUT_OK);}}// Asynchronous flush// 异步刷盘else {if (!this.defaultMessageStore.getMessageStoreConfig().isTransientStorePoolEnable()) {// 如果没有启动了堆外缓存,那么唤醒异步刷盘服务 FlushRealTimeServiceflushCommitLogService.wakeup();} else {// 如果启动了堆外缓存,那么唤醒异步转存服务 CommitRealTimeServicecommitLogService.wakeup();}return CompletableFuture.completedFuture(PutMessageStatus.PUT_OK);}
}
同步刷盘
org.apache.rocketmq.store.CommitLog.GroupCommitService
同步刷盘服务,如果没有开启同步等待,则将消息全部刷入磁盘。
同步刷盘服务中有一对读写队列,每当有刷盘请求进来,会写入到写队列,然后交换读写队列,对读队列进行请求处理,执行刷盘。
如果开启同步等待,则在提交刷盘时,提交一个request到写队列,然后唤醒刷盘服务,在刷盘服务中,会先进行读写交换,然后对读队列的请求进行刷盘处理。
在刷盘方法中,会根据传入的最小刷盘页数进行刷盘,为0则是全部刷盘。不为0,则需要判断目前需要刷盘的数据大小是否达到了传入的页数数量,若是则刷盘,若不是则不刷盘。
最后通过this.mappedByteBuffer.force();方法强制刷盘并更新刷盘点位等信息
/*** GroupCommit Service* 同步刷盘服务*/
class GroupCommitService extends FlushCommitLogService {private volatile LinkedList<GroupCommitRequest> requestsWrite = new LinkedList<GroupCommitRequest>();private volatile LinkedList<GroupCommitRequest> requestsRead = new LinkedList<GroupCommitRequest>();private final PutMessageSpinLock lock = new PutMessageSpinLock();/*** 加锁存入写队列* @param request*/public synchronized void putRequest(final GroupCommitRequest request) {lock.lock();try {this.requestsWrite.add(request);} finally {lock.unlock();}this.wakeup();}private void swapRequests() {// 刷盘时交换读写队列后,写队列为空,读队列供刷盘消费// 消费完成后,读队列置为空,在下一次交换时,写队列又为空// 如此循环lock.lock();try {LinkedList<GroupCommitRequest> tmp = this.requestsWrite;this.requestsWrite = this.requestsRead;this.requestsRead = tmp;} finally {lock.unlock();}}private void doCommit() {// 调用此方法之前,都交换了读写队列if (!this.requestsRead.isEmpty()) {for (GroupCommitRequest req : this.requestsRead) {// There may be a message in the next file, so a maximum of// two times the flushboolean flushOK = CommitLog.this.mappedFileQueue.getFlushedWhere() >= req.getNextOffset();// 这里可能会刷盘两次的原因是,可能第一次刷完发现文件满了,就没有达到req传入的刷盘点,则需要再刷盘一次for (int i = 0; i < 2 && !flushOK; i++) {CommitLog.this.mappedFileQueue.flush(0);flushOK = CommitLog.this.mappedFileQueue.getFlushedWhere() >= req.getNextOffset();}req.wakeupCustomer(flushOK ? PutMessageStatus.PUT_OK : PutMessageStatus.FLUSH_DISK_TIMEOUT);}long storeTimestamp = CommitLog.this.mappedFileQueue.getStoreTimestamp();if (storeTimestamp > 0) {CommitLog.this.defaultMessageStore.getStoreCheckpoint().setPhysicMsgTimestamp(storeTimestamp);}// requestsRead重新创建一个空的队列,// 当下一次交换队列的时候(doCommit之前会交换队列),requestsWrite又会成为一个空队列,以备向其中写入消息this.requestsRead = new LinkedList<>();} else {// Because of individual messages is set to not sync flush, it// will come to this process// 队列中没有元素是因为某些消息的设置是同步刷盘但是不等待// 因此这里直接调用mappedFileQueue.flush(0)方法进行一次同步刷盘即可,无需唤醒线程等操作。// 0表示最少刷0页,也就是全部刷入CommitLog.this.mappedFileQueue.flush(0);}}public void run() {CommitLog.log.info(this.getServiceName() + " service started");// 如果没有停止, 就一直循环while (!this.isStopped()) {try {// 若被wakeup, 则交换读写队列, 否则等待10msthis.waitForRunning(10);this.doCommit();} catch (Exception e) {CommitLog.log.warn(this.getServiceName() + " service has exception. ", e);}}// Under normal circumstances shutdown, wait for the arrival of the request, and then flushtry {Thread.sleep(10);} catch (InterruptedException e) {CommitLog.log.warn("GroupCommitService Exception, ", e);}synchronized (this) {this.swapRequests();}this.doCommit();CommitLog.log.info(this.getServiceName() + " service end");}
异步刷盘
org.apache.rocketmq.store.CommitLog.FlushRealTimeService
异步刷盘是在一个循环中,不断的执行刷盘逻辑
默认不是定时刷盘,若是定时刷盘,则固定隔一段时间(默认500ms)刷盘一次
若不是定时刷盘,则会被wakeup()方法唤醒,执行刷盘
执行刷盘前会判断配置的最小刷盘页数(默认4页即16K),若未达到此数,则不执行刷盘
但当距离上一次刷盘超过配置时间(默认10s),即使未达到最小刷盘数,也会执行刷盘
/*** 异步刷盘*/
class FlushRealTimeService extends FlushCommitLogService {private long lastFlushTimestamp = 0;private long printTimes = 0;public void run() {CommitLog.log.info(this.getServiceName() + " service started");while (!this.isStopped()) {// 是否是定时刷盘,默认是false,即不开启boolean flushCommitLogTimed = CommitLog.this.defaultMessageStore.getMessageStoreConfig().isFlushCommitLogTimed();// 获取刷盘间隔时间,默认500ms,可通过flushIntervalCommitLog配置int interval = CommitLog.this.defaultMessageStore.getMessageStoreConfig().getFlushIntervalCommitLog();// 获取刷盘的最少页数,默认4,即16k,可通过flushCommitLogLeastPages配置int flushPhysicQueueLeastPages = CommitLog.this.defaultMessageStore.getMessageStoreConfig().getFlushCommitLogLeastPages();// 最大刷盘间隔时间,默认10s,即到了10s后,刷盘页数小于4也会刷盘int flushPhysicQueueThoroughInterval =CommitLog.this.defaultMessageStore.getMessageStoreConfig().getFlushCommitLogThoroughInterval();boolean printFlushProgress = false;// Print flush progresslong currentTimeMillis = System.currentTimeMillis();//如果当前时间距离上次刷盘时间大于等于10s,那么必定刷盘if (currentTimeMillis >= (this.lastFlushTimestamp + flushPhysicQueueThoroughInterval)) {//更新刷盘时间戳为当前时间this.lastFlushTimestamp = currentTimeMillis;//最少刷盘页数为0,即不管页数是否超过4,都会刷盘flushPhysicQueueLeastPages = 0;printFlushProgress = (printTimes++ % 10) == 0;}try {if (flushCommitLogTimed) {//如果定时刷盘,那么当前线程睡眠指定的间隔时间Thread.sleep(interval);} else {// 如果不是定时刷盘,那么调用waitForRunning方法,线程最多睡眠500ms// 可以被中途的wakeup方法唤醒进而直接尝试进行刷盘this.waitForRunning(interval);}if (printFlushProgress) {this.printFlushProgress();}// 开始刷盘long begin = System.currentTimeMillis();// 刷入指定的页,内部如果没有达到对应页数则不会刷盘CommitLog.this.mappedFileQueue.flush(flushPhysicQueueLeastPages);long storeTimestamp = CommitLog.this.mappedFileQueue.getStoreTimestamp();if (storeTimestamp > 0) {CommitLog.this.defaultMessageStore.getStoreCheckpoint().setPhysicMsgTimestamp(storeTimestamp);}// 刷盘耗时long past = System.currentTimeMillis() - begin;if (past > 500) {log.info("Flush data to disk costs {} ms", past);}} catch (Throwable e) {CommitLog.log.warn(this.getServiceName() + " service has exception. ", e);this.printFlushProgress();}}// Normal shutdown, to ensure that all the flush before exit/** 停止时逻辑* 在正常情况下服务关闭时,一次性执行10次刷盘操作*/boolean result = false;for (int i = 0; i < RETRY_TIMES_OVER && !result; i++) {result = CommitLog.this.mappedFileQueue.flush(0);CommitLog.log.info(this.getServiceName() + " service shutdown, retry " + (i + 1) + " times " + (result ? "OK" : "Not OK"));}this.printFlushProgress();CommitLog.log.info(this.getServiceName() + " service end");}
刷盘方法
org.apache.rocketmq.store.MappedFileQueue#flush
org.apache.rocketmq.store.MappedFile#flush
public boolean flush(final int flushLeastPages) {boolean result = true;// flushedWhere: 刷盘位置// 根据commitlog全局的刷盘点找到文件MappedFile mappedFile = this.findMappedFileByOffset(this.flushedWhere, this.flushedWhere == 0);if (mappedFile != null) {long tmpTimeStamp = mappedFile.getStoreTimestamp();// 调用此文件的刷盘,返回此次的刷盘位置int offset = mappedFile.flush(flushLeastPages);long where = mappedFile.getFileFromOffset() + offset;// 刷盘结果result = where == this.flushedWhere;// 更新刷盘物理位置this.flushedWhere = where;if (0 == flushLeastPages) {this.storeTimestamp = tmpTimeStamp;}}return result;
}
public int flush(final int flushLeastPages) {// 判断能否刷盘, 是否达到最小刷盘页数flushLeastPagesif (this.isAbleToFlush(flushLeastPages)) {//增加对该MappedFile的引用次数if (this.hold()) {// 获取写入位置int value = getReadPosition();try {//We only append data to fileChannel or mappedByteBuffer, never both.if (writeBuffer != null || this.fileChannel.position() != 0) {// 如果使用了堆外内存,那么通过fileChannel强制刷盘,这是异步堆外内存走的逻辑this.fileChannel.force(false);} else {// 如果没有使用堆外内存,那么通过mappedByteBuffer强制刷盘,这是同步或者异步刷盘走的逻辑this.mappedByteBuffer.force();}} catch (Throwable e) {log.error("Error occurred when force data to disk.", e);}// 将刷盘位置设置为写入位置this.flushedPosition.set(value);// 减少对该MappedFile的引用次数,表示使用结束,在netty中的ByteBuf就有类似的设计,如果引用为0则内存会被回收this.release();} else {log.warn("in flush, hold failed, flush offset = " + this.flushedPosition.get());this.flushedPosition.set(getReadPosition());}}// 获取最新的刷盘位置,也就是刚刚刷到的地方return this.getFlushedPosition();
}
相关文章:

【RocketMQ】源码详解:Broker端消息刷盘流程
消息刷盘 同步入口:org.apache.rocketmq.store.CommitLog.GroupCommitService 异步入口:org.apache.rocketmq.store.CommitLog.FlushRealTimeService 刷盘有同步和异步两种,在实例化Commitlog的时候,会根据配置创建不同的服务 p…...

编码器SIQ-02FVS3驱动
一.简介 此编码器可以是功能非常强大,可以检测左右转动,和按键按下,所以说这一个编码器可以抵三个按键,而且体积非常小,使用起来比三个按键要高大尚,而且驱动也简单。唯一不足的点就是价格有点小贵6-8元才…...

【2021.9.7】记一次exe手动添加shellcode
【2021.9.7】记一次exe手动添加shellcode 文章目录【2021.9.7】记一次exe手动添加shellcode0.大致思路1.获取MessageBox的真实地址VA2.通过OD在代码段添加shellcode3.dump出数据,设置程序OEP4.测试dump出来的exe5.方法总结测试的exe和添加了shellcode的exe:链接&…...

常用训练tricks,提升你模型的鲁棒性
目录一、对抗训练FGM(Fast Gradient Method): ICLR2017代码实现二、权值平均1.指数移动平均(Exponential Moving Average,EMA)为什么EMA会有效?代码实现2. 随机权值平均(Stochastic Weight Averaging,SWA&a…...

具有精密内部基准的 DACx0502 简介及驱动应用示例
DACx0502 说明 16 位 DAC80502、14 位 DAC70502 和 12 位DAC60502 (DACx0502) 数模转换器 (DAC) 均为具有电压输出的高精度、低功耗器件。 DACx0502 线性度小于 1LSB。凭借高精度和微型封装特性,DACx0502 非常适合以下 应用: 增益和失调电压校准、电流…...

C语言函数:字符串函数及模拟实现strncpy()、strncat()、strncmp()
C语言函数:字符串函数及模拟实现strncpy()、strncat()、strncmp() 在了解strncpy、strncat()、前,需要先了解strcpy()、strncat(): C语言函数:字符串函数及模拟实现strlen() 、strcpy()、 strcat()_srhqwe的博客-CSDN博客 strncp…...

学术论文插图要求简介
1. 类型 位图和矢量图是两种不同的图像类型,它们在存储和处理图像时使用不同的方法。以下是它们之间的详细区别: 图像构成方式:位图使用像素(或图像的最小单元)来构建图像,每个像素都有自己的颜色和亮度值。…...

【独家】华为OD机试 - 斗地主 2(C 语言解题)
最近更新的博客 华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南)华为od机试,独家整理 已参加机试人员的实战技巧文章目录 最近更新的博客使用说明本期…...

力扣-计算特殊奖金
大家好,我是空空star,本篇带大家了解一道简单的力扣sql练习题。 文章目录前言一、题目:1873. 计算特殊奖金二、解题1.正确示范①提交SQL运行结果2.正确示范②提交SQL运行结果3.正确示范③提交SQL运行结果4.正确示范④提交SQL运行结果5.其他总…...

华为校招机试真题目录
专栏介绍 本专栏将逐步收集历年华为校招算法真题 专栏权益 每篇博客都包含: 算法考点解析(文字+画图)算法源码(支持 Java / JS / Python)每晚9:00 ~ 11:00 在线答疑 真题目录 时间题目考点 or 实现2022.11.27...

EdgeYOLO学习笔记
EdgeYOLO学习笔记 EdgeYOLO: An Edge-Real-Time Object Detector Abstract 本文基于最先进的YOLO框架,提出了一种高效、低复杂度、无锚的目标检测器,该检测器可以在边缘计算平台上实时实现。为了有效抑制训练过程中的过拟合,我们开发了一种…...

【分布式】什么是分布式锁?正文揭晓
分布式锁的概念 分布式锁其实可以理解为:控制分布式系统有序的去对共享资源进行操作,通过互斥来保持一致性。 举个例子:假设共享的资源就是一个房子,里面有各种书,分布式系统就是要进屋看书的人, 分布式锁…...

超详细JDK1.8所有版本下载地址
JDK1.8即为JDK8,JDK8是目前是最成熟最稳定的版本,本文将详细介绍JDK1.8历史版本的下载方式。 在此附上JDK1.8安装与配置教程 超详细JDK1.8安装与配置 一、JDK官网 首先打开oracle官网,官网首页地址为 JDK官网首页地址 点击Products 点击…...

论文解析[11] CAT: Cross Attention in Vision Transformer
发表时间:2021 论文地址:https://arxiv.org/abs/2106.05786v1 文章目录摘要3 方法3.1 总体结构3.1.1 Inner-Patch Self-Attention Block3.1.2 Cross-Patch Self-Attention Block3.1.3 Cross Attention based Transformer结论摘要 使用图像patch来替换tr…...

嵌入式和Python(一):python环境搭建的详细步骤
目录 ● 安装python ① 更新软件列表 ② 安装编译python需要用到的环境 ③ 下载python源码 ④ 解压源码包 ⑤ 配置 ⑥ 编译 ⑦ 安装 ● 建立软连接 说明 ① 删除原来的软连接 ② 在/usr/bin/目录创建软连接python,定向/usr/local/bin/python3.9 ③ 检查…...

嵌入式学习笔记——STM32硬件基础知识
STM32开发硬件知识前言单片机参数主频位数STM32最小系统电源电路晶振电路复位电路BOOT选择电路调试接口电路其他电路本文重点本文参考博客链接前言 上一篇中我们重点是讲了一下怎么搭建开发环境以及怎么下载烧录的过程,这都是解决的电脑端的开发环境问题࿰…...

Mybatis插件开发及执行原理
mybatis源码下载 https://github.com/mybatis/mybatis-3,本文分析源码版本3.4.5 mybatis启动大致流程 在看这篇文章前,建议查看我另一篇文章,以了解框架启动的流程和框架中一些重要对象:https://blog.csdn.net/Aqu415/article/…...

vue父子组件通信,兄弟组件通信
目录 一、父子组件通信 1、子组件通过 props 获取父组件变量和父组件调用子组件中的方法(这两个都是父传子的思想) a:子组件通过 props 获取父组件变量 b:父组件调用子组件中的方法 2、父组件通过ref获取子组件变量和子组件调用父组件的方法(这两个都是子传父的…...

大数据技术之Hadoop集群配置
作者简介:大家好我是小唐同学(๑><๑),好久不见,为梦想而努力的小唐又回来了,让我们一起加油!!! 个人主页:小唐同学(๑><๑)的博客主页 目前…...

MicroBlaze系列教程(7):AXI_SPI的使用(M25P16)
文章目录 AXI_SPI简介MicroBlaze硬件配置常用函数使用示例波形实测参考资料工程下载本文是Xilinx MicroBlaze系列教程的第7篇文章。 AXI_SPI简介 Xilinx AXI-SPI IP共有两个:一个是标准的AXI_SPI,即4线制SPI,CS、SCLK、MOSI和MISO,另一个是AXI_Quad SPI,支持配置成标准SP…...

使用Python通过拉马努金公式快速求π
使用Python通过拉马努金公式快速求π 一、前言 π是一个数学常数,定义为:圆的周长与直径的比值。 π是一个无理数,也是一个超越数,它的小数部分无限不循环。 π可以用来精确计算圆周长、圆面积、球体积等几何形状的关键值。 有关…...

第六章 使用系统类提供国家语言支持 - 创建自定义语言环境
文章目录第六章 使用系统类提供国家语言支持 - 创建自定义语言环境创建自定义语言环境第六章 使用系统类提供国家语言支持 - 创建自定义语言环境 创建自定义语言环境 此示例将提供一个模板,用于使用自定义表创建自定义语言环境。自定义表将在 EBCDIC(美…...

「题解」解决二进制数中1的个数
🐶博主主页:ᰔᩚ. 一怀明月ꦿ ❤️🔥专栏系列:线性代数,C初学者入门训练 🔥座右铭:“不要等到什么都没有了,才下定决心去做” 🚀🚀🚀大家觉不错…...

泛型详解.
1 泛型的引入 问题:我们之前实现过的顺序表,只能保存 int 类型的元素,如果现在需要保存 指向 Person 类型对象的引用的顺序表,请问应该如何解决?如果又需要保存指向 Book 对象类型的引用呢? 之前写的顺序表…...

Vue 3.0 响应性 深入响应性原理 【Vue3 从零开始】
现在是时候深入了!Vue 最独特的特性之一,是其非侵入性的响应性系统。数据模型是被代理的 JavaScript 对象。而当你修改它们时,视图会进行更新。这让状态管理非常简单直观,不过理解其工作原理同样重要,这样你可以避开一…...

升级 vue3 常见问题总汇
Ⅰ、前言 虽然 vue3 是没有删除 vue2 的 选项式 API , 但是我们升级vue3 还是需要修改很多问题的下面来看看我们升级常见的一些问题 👇 文章目录Ⅰ、前言Ⅱ、解决兼容问题1、路由的创建方式2、路由的方法变化3、升级 vuex 到 4.x4、作用域 插槽语法修改…...

汽车 Automotive > T-BOX GNSS高精定位测试相关知识
参考:https://en.wikipedia.org/wiki/Global_Positioning_SystemGPS和GNSS的关系GPS(Global Positioning System),全球定位系统是美国军民两用的导航定位卫星系统,GPS包含双频信号,频点L1、L2和L5GNSS&…...

大数据面试核心101问【大厂超级喜欢这些题】
之前出过《史上最全的大数据开发八股文》这篇文章,同学们都觉得还不错,但是有些同学觉得那个背起来还是有些吃力,于是我再次回顾了自己之前面试所有大厂的一些面试题以及牛客上面的一些面经,然后总结了频率问的最高的101问&#x…...

代码随想录算法训练营第四十八天 | leetcode 121. 买卖股票的最佳时机,122.买卖股票的最佳时机II
代码随想录算法训练营第四十八天 | leetcode 121. 买卖股票的最佳时机,122.买卖股票的最佳时机II121. 买卖股票的最佳时机122.买卖股票的最佳时机II121. 买卖股票的最佳时机 题目: 给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支…...

RAD 11.3 delphi和C++改进后新增、废弃及优化的功能
RAD 11.3 delphi和C改进后新增和废弃的功能 目录 RAD 11.3 delphi和C改进后新增和废弃的功能 一、版本RAD 11.3 delphi和C改进后新增功能 1、官方视频位置: 2、官方文档的链接位置: 二、版本RAD 11.3 delphi和C改进后废弃的功能 2.1、编译器不再使…...