本文基于rocketmq4.0版本,结合CommitlLog的刷盘过程,对消息队列的刷盘过程源码进行分析,进而对RocketMQ的刷盘原理和过程进行了解。
 
rocketmq 4.0版本中刷盘类型和以前的版本一样有两种:
  1. public enum FlushDiskType {
  2. // 同步刷盘
  3. SYNC_FLUSH,
  4. // 异步刷盘
  5. ASYNC_FLUSH
  6. }
 
刷盘方式有三种:
线程服务 场景 写消息性能
CommitRealTimeService 异步刷盘 && 开启内存字节缓冲区 第一
FlushRealTimeService 异步刷盘  第二
GroupCommitService 同步刷盘 第三

其中CommitRealTimeService是老一些版本中没有的,它为开启内存字节缓存的刷盘服务。

介绍各个线程工作之前,先需要重点了解一下waitForRunning方法,因为在三个刷盘服务线程中都频繁使用该方法:

  1. protected void waitForRunning(long interval) {
  2. if (hasNotified.compareAndSet(true, false)) {
  3. this.onWaitEnd();
  4. return;
  5. }
  6. //entry to wait
  7. waitPoint.reset();
  8.  
  9. try {
  10. waitPoint.await(interval, TimeUnit.MILLISECONDS);
  11. } catch (InterruptedException e) {
  12. log.error("Interrupted", e);
  13. } finally {
  14. hasNotified.set(false);
  15. this.onWaitEnd();
  16. }
  17. }
这里要注意一下 waitPoint 这个共享变量,它是CountDownLatch2类型,,没有细看CountDownLatch2的原理,猜测它和CountDownLatch类似,根据CountDownLatch的使用原理,大致可以猜测waitPoint的作用。
 
回顾一下CountDownLatch相关知识:
CountDownLatch能够使一个线程等待其他线程完成各自的工作后再执行自己的任务,CountDownLatch是通过一个计数器来实现的,计数器的初始值为需要等待的线程的数量。每当一个线程完成了自己的任务后,计数器的值就会减1。当计数器值到达0时,它表示所有的线程已经完成了任务,然后在闭锁上等待的线程就可以恢复执行任务。
 
因此,可以猜测waitForRunning的业务逻辑大致为:
(1). 通过闭锁没执行依次waitPoint.countDown(),当计数器值到达0时,结束阻塞状态,开始执行等待线程的任务;
(2). 等待一定时间之后,结束阻塞状态,开始执行等待线程的任务。
 
在rocketmq4.0版本中,调用了waitPoint.countDown()的地方有三处:
  1. shutdown()
  2. stop()
  3. wakeup()

这里我们关心的是wakeup()方法,调用wakeup方法的几处如下

其中与commitLog刷盘相关的有:

service.wakeup()、flushCommitLogService.wakeup()、commitLogService.wakeup(),其中service.wakeup()的service是GroupCommitService类型。
 
由此引入了本文所要讲述的FlushRealTimeService、CommitRealTimeService以及GroupCommitService三个线程刷盘服务。
 
 
GroupCommitService
broker启动后,会启动许多服务线程,包括刷盘服务线程,如果刷盘服务线程类型是SYNC_FLUSH (同步刷盘类型:对写入的数据同步刷盘,只在broker==master时使用),则开启GroupCommitService服务,该服务线程启动后每隔10毫秒或该线程调用了wakeup()方法后停止阻塞,执行doCommit()方法。doCommit里面执行具体的刷盘逻辑业务。GroupCommitService服务线程体如下:

  1. public void run() {
  2. CommitLog.log.info(this.getServiceName() + " service started");
  3.  
  4. while (!this.isStopped()) {
  5. try {
  6. this.waitForRunning(10);
  7. this.doCommit();
  8. } catch (Exception e) {
  9. CommitLog.log.warn(this.getServiceName() + " service has exception. ", e);
  10. }
  11. }
  12.  
  13. // Under normal circumstances shutdown, wait for the arrival of the
  14. // request, and then flush
  15. try {
  16. Thread.sleep(10);
  17. } catch (InterruptedException e) {
  18. CommitLog.log.warn("GroupCommitService Exception, ", e);
  19. }
  20.  
  21. synchronized (this) {
  22. this.swapRequests();
  23. }
  24.  
  25. this.doCommit();
  26.  
  27. CommitLog.log.info(this.getServiceName() + " service end");
  28. }
 
当broker类型为master时,每写入一条消息成功写入mapedFile文件后,调用handleDiskFlush方法,如果该消息满足messageExt.isWaitStoreMsgOK(),则将这一条成功写入的消息生成GroupCommitRequest对象,将该对像放入GroupCommitService的requestsWrite列表中(List<GroupCommitRequest>),等待刷盘线程调用doCommit,对列表中的消息进行刷盘,doCommit中每对一个request处理完成后,会调用wakeupCustomer。等待时间5s后或者request的countDownLatch记数为0时,则将这条消息是否已经刷盘成功进行汇报,如果没有刷盘成功,则再日志中记录错误,并将putMessageResult设置为FLUSH_DISK_TIMEOUT。代码如下:

  1. if (FlushDiskType.SYNC_FLUSH == this.defaultMessageStore.getMessageStoreConfig().getFlushDiskType()) {
  2. final GroupCommitService service = (GroupCommitService) this.flushCommitLogService;
  3. if (messageExt.isWaitStoreMsgOK()) {
  4. GroupCommitRequest request = new GroupCommitRequest(result.getWroteOffset() + result.getWroteBytes());
  5. service.putRequest(request);
  6. boolean flushOK = request.waitForFlush(this.defaultMessageStore.getMessageStoreConfig().getSyncFlushTimeout());
  7. if (!flushOK) {
  8. log.error("do groupcommit, wait for flush failed, topic: " + messageExt.getTopic() + " tags: " + messageExt.getTags()
  9. + " client address: " + messageExt.getBornHostString());
  10. putMessageResult.setPutMessageStatus(PutMessageStatus.FLUSH_DISK_TIMEOUT);
  11. }
  12. } else {
  13. service.wakeup();
  14. }
  15. }

这条消息是否已经刷盘成功进行汇报的逻辑 -- waitForFlush方法:

  1. public static class GroupCommitRequest {
  2. private final long nextOffset;
  3. private final CountDownLatch countDownLatch = new CountDownLatch(1);
  4. private volatile boolean flushOK = false;
  5.  
  6. public GroupCommitRequest(long nextOffset) {
  7. this.nextOffset = nextOffset;
  8. }
  9.  
  10. public long getNextOffset() {
  11. return nextOffset;
  12. }
  13.  
  14. public void wakeupCustomer(final boolean flushOK) {
  15. this.flushOK = flushOK;
  16. this.countDownLatch.countDown();
  17. }
  18.  
  19. public boolean waitForFlush(long timeout) {
  20. try {
  21. // 阻塞当前工作线程,等待时间5s后或者countDownLatch记数为0时,停止阻塞,执行下一条语句
  22. this.countDownLatch.await(timeout, TimeUnit.MILLISECONDS);
  23. return this.flushOK;
  24. } catch (InterruptedException e) {
  25. log.error("Interrupted", e);
  26. return false;
  27. }
  28. }
  29. }
对GroupCommitRequest类中的两个方法的说明:
在waitForFlush方法阻塞的时候,doCommit方法对写入requestsWrite列表中(List<GroupCommitRequest>)所有GroupCommitRequest对象依次进行了 wakeupCustomer方法调用,wakeupCustomer调用后,countDownLatch 闭锁记数减一,等待时间5s后或者countDownLatch记数为0时,返回调用wakeupCustomer的GroupCommitRequest对应的消息的刷盘结果。
 
GroupCommitService的doCommit方法
说明一下:分析doCommit方法之前,先提及一下swapRequests这个方法,之前提过,GroupCommitService服务线程该每隔10毫秒或调用了该线程的wakeup()方法后执行doCommit()方法,具体地要涉及到waitForRunning方法,waitForRunning方法中onWaitEnd的作用在这里就可以提及一下了,它的作用就是将requestsWrite 转换为requestsRead ,这个与消息存储过程中处理dispatchRequest是类似的。
  1. private void swapRequests() {
  2. List<GroupCommitRequest> tmp = this.requestsWrite;
  3. this.requestsWrite = this.requestsRead;
  4. this.requestsRead = tmp;
  5. }
 doCommit代码:
  1. private void doCommit() {
  2. //
  3. synchronized (this.requestsRead) {
  4. if (!this.requestsRead.isEmpty()) {
  5. for (GroupCommitRequest req : this.requestsRead) {
  6. // There may be a message in the next file, so a maximum of
  7. // two times the flush
  8. boolean flushOK = false;
  9. for (int i = 0; i < 2 && !flushOK; i++) {
  10. flushOK = CommitLog.this.mappedFileQueue.getFlushedWhere() >= req.getNextOffset();
  11.  
  12. if (!flushOK) {
  13. CommitLog.this.mappedFileQueue.flush(0);
  14. }
  15. }
  16.  
  17. req.wakeupCustomer(flushOK);
  18. }
  19.  
  20. long storeTimestamp = CommitLog.this.mappedFileQueue.getStoreTimestamp();
  21. if (storeTimestamp > 0) {
  22. CommitLog.this.defaultMessageStore.getStoreCheckpoint().setPhysicMsgTimestamp(storeTimestamp);
  23. }
  24.  
  25. this.requestsRead.clear();
  26. } else {
  27. // Because of individual messages is set to not sync flush, it
  28. // will come to this process
  29. CommitLog.this.mappedFileQueue.flush(0);
  30. }
  31. }
  32. }
 
其中,具体的刷盘核心代码:CommitLog.this.mappedFileQueue.flush(0); 接下来看其他两个刷盘服务线程,对CommitLog.this.mappedFileQueue.flush(0)下文将具体讲解。
 
 

FlushRealTimeService
// 刷新策略(默认是实时刷盘)
flushCommitLogTimed
// 刷盘时间间隔(默认0.5s)
interval = flushIntervalCommitLog
// 每次刷盘至少需要多少个page(默认是4个)
flushPhysicQueueLeastPages
// 彻底刷盘间隔时间(默认10s)
flushPhysicQueueThoroughInterval
 
大致逻辑:
-- 如果 当前时间 >(最后一次刷盘时间 + 彻底刷盘间隔时间(10s)),则将最新一次刷盘时间更新为当前时间
 
-- 如果是实时刷盘,每隔一定时间间隔,该线程休眠500毫秒
如果不是实时刷盘,则调用waitForRunning,即每隔500毫秒或该刷盘服务线程调用了wakeup()方法之后结束阻塞。
 
-- 调用 CommitLog.this.mappedFileQueue.flush(flushPhysicQueueLeastPages);
 
 
CommitRealTimeService
CommitRealTimeService 比较特殊,它会包含提交和异步刷盘逻辑,专门为开启内存字节缓冲区的刷盘服务。
// 提交到FileChannel的时间间隔,只在TransientStorePool 打开的情况下使用,默认0.2s
interva l= commitIntervalCommitLog
//每次提交到File至少需要多少个page(默认是4个)
commitDataLeastPages = commitCommitLogLeastPages
/ 提交完成间隔时间(默认0.2s)
commitDataThoroughInterval
 
大致逻辑:
如果 当前时间 >(最后一次提交时间 + 提交完成间隔时间),更新lastCommitTimestamp之后,执行提交的核心逻辑:
boolean result = CommitLog.this.mappedFileQueue.commit(commitDataLeastPages);
如果result == false 则意味着有新的数据 committed,此时需要wakeup刷盘线程,即:
flushCommitLogService.wakeup(); 进行异步刷盘处理。
 
可知道,刷盘的下一层核心逻辑:
mappedFileQueue.flush
mappedFileQueue.commit
 
  flush
  1. public boolean flush(final int flushLeastPages) {
  2. boolean result = true;
  3. MappedFile mappedFile = this.findMappedFileByOffset(this.flushedWhere, false);
  4. if (mappedFile != null) {
  5. long tmpTimeStamp = mappedFile.getStoreTimestamp();
  6. int offset = mappedFile.flush(flushLeastPages);
  7. long where = mappedFile.getFileFromOffset() + offset;
  8. result = where == this.flushedWhere;
  9. this.flushedWhere = where;
  10. if (0 == flushLeastPages) {
  11. this.storeTimestamp = tmpTimeStamp;
  12. }
  13. }
  14.  
  15. return result;
  16. }

  commit

  1. public boolean commit(final int commitLeastPages) {
  2. boolean result = true;
  3. MappedFile mappedFile = this.findMappedFileByOffset(this.committedWhere, false);
  4. if (mappedFile != null) {
  5. int offset = mappedFile.commit(commitLeastPages);
  6. long where = mappedFile.getFileFromOffset() + offset;
  7. result = where == this.committedWhere;
  8. this.committedWhere = where;
  9. }
  10.  
  11. return result;
  12. }
 
从上代码可以看出,刷盘过程与MappedFile有很大关系,通过findMappedFileByOffset方法找到要刷盘的MappedFile,然后MappedFile中采用数据刷盘技术将数据刷入到磁盘

MappedFile的刷盘方式有两种:
1. 写入内存字节缓冲区(writeBuffer) ----> 从内存字节缓冲区(write buffer)提交(commit)到文件通道(fileChannel) ----> 文件通道(fileChannel)flush到磁盘
2.写入映射文件字节缓冲区(mappedByteBuffer) ----> 映射文件字节缓冲区(mappedByteBuffer)flush
 
(MappedFile的刷盘方式待具体分析,待补充...)
 
 

rocketmq刷盘过程的更多相关文章

  1. RocketMQ消息丢失解决方案:同步刷盘+手动提交

    前言 之前我们一起了解了使用RocketMQ事务消息解决生产者发送消息时消息丢失的问题,但使用了事务消息后消息就一定不会丢失了吗,肯定是不能保证的. 因为虽然我们解决了生产者发送消息时候的消息丢失问题 ...

  2. RocketMQ中Broker的刷盘源码分析

    上一篇博客的最后简单提了下CommitLog的刷盘  [RocketMQ中Broker的消息存储源码分析] (这篇博客和上一篇有很大的联系) Broker的CommitLog刷盘会启动一个线程,不停地 ...

  3. 【RocketMQ】消息的刷盘机制

    刷盘策略 CommitLog的asyncPutMessage方法中可以看到在写入消息之后,调用了submitFlushRequest方法执行刷盘策略: public class CommitLog { ...

  4. HTC A510C电信手机刷机过程

    HTC A510C电信手机刷机过程记录 Writed by Peter Hu(2014.6.7) ON WIN7_64 刷机需要的步骤: 1)  将S-ON加密保护式去掉,改成S-OFF模式,这样才能 ...

  5. mq刷盘方式

    Broker 在收到Producer发送过来的消息后,会存入CommitLog对应的内存映射区中,见CommitLog类的putMessage方法.该方法执行OK后,会判断存储配置中刷盘模式:同步or ...

  6. Rocket重试机制,消息模式,刷盘方式

    一.Consumer 批量消费(推模式) 可以通过 consumer.setConsumeMessageBatchMaxSize(10);//每次拉取10条 这里需要分为2种情况 Consumer端先 ...

  7. MySQL InnoDB 日志管理机制中的MTR和日志刷盘

    1.MTR(mini-transaction) 在MySQL的 InnoDB日志管理机制中,有一个很重要的概念就是MTR.MTR是InnoDB存储擎中一个很重要的用来保证物理写的完整性和持久性的机制. ...

  8. 【软件安装与环境配置】TX2刷机过程

    前言 使用TX2板子之前需要进行刷机,一般都是按照官网教程的步骤刷机,无奈买不起的宝宝只有TX2核心板,其他外设自己搭建,所以只能重新制作镜像,使用该镜像进行刷机. 系统需求 1.Host Platf ...

  9. 从CM刷机过程和原理分析Android系统结构

    前面101篇文章都是分析Android系统源代码,似乎不够接地气. 假设能让Android系统源代码在真实设备上跑跑看效果,那该多好.这不就是传说中的刷ROM吗?刷ROM这个话题是老罗曾经一直避免谈的 ...

随机推荐

  1. SpringMVC传递数据的流线图

    流程含义解释:(1)HTTP请求到达web服务器,web服务器将其封装成一个httpServletRequest对象(2)springMVC框架截获这个httpServletRequest对象(3)s ...

  2. Android Activity活动状态及生存周期

    1.活动状态 每个活动在其生命周期中最多可能会有4中状态. (1)运行状态 当一个活动位于返回栈的栈顶时,此时活动就处于运行状态.系统不会回收处于运行状态的活动. (2)暂停状态 当一个活动不再处于栈 ...

  3. Snippet取表字段说明和详细信息

    IF OBJECT_ID (N'dbo.GetDetails', N'IF') IS NOT NULL DROP FUNCTION dbo.GetDetails; GO create function ...

  4. vss和vs2008组合搭建源代码管理器

    用源代码管理项目,是为了方便开发和管理组内项目,一个组做的是同一套项目,彼此知道各个模块的进度和开发情况,这也是开发项目所需要的.今天整理了VSS的安装.创建.连接及添加项目等操作. 一.安装VSS( ...

  5. Top Android App使用的组件

    唱吧_462 smack:de.measite.smack:??? ???:org.apache:??? smack:org.jivesoftware.smack:XMPP客户端类库 dnsjava: ...

  6. AngularJS.js: 杂项

    ylbtech-AngularJS.js: 杂项 AngularJS诞生于2009年,由Misko Hevery 等人创建,后为Google所收购.是一款优秀的前端JS框架,已经被用于Google的多 ...

  7. Django缓存,信号,序列化

    缓存 1.缓存的简介 在动态网站中,用户所有的请求,服务器都会去数据库中进行相应的增,删,查,改,渲染模板,执行业务逻辑,最后生成用户看到的页面. 当一个网站的用户访问量很大的时候,每一次的的后台操作 ...

  8. kubernetes 学习 pod相关

    1  pod的状态: Pending, Running, Succeeded, Failed, Unknown 2  pod重启策略: Always(自动重启,是默认的) .  OnFailure(容 ...

  9. 如何用xMind打开.mmap文件

    首先右键单击mmap格式文档,选择“属性” > “更改了打开方式” > 选择使用Xmind软件打开.然后双击mmap格式文档,就可以用Xmind打开了!

  10. RefWorks

    RefWorks公司简介/RefWorks 编辑 RefWorks是美国剑桥信息集团的子公司,是ProQuest 的姊妹公司.该公司于2001年由参考文献管理领域的一些专家组建而成,并致力于为学术机构 ...