Broker 在收到Producer发送过来的消息后,会存入CommitLog对应的内存映射区中,见CommitLog类的putMessage方法。该方法执行OK后,会判断存储配置中刷盘模式:同步or异步?继而进行对应的操作。 
ServiceThread –> FlushCommitLogService 
–> GroupCommitService 
–> FlushRealTimeService

// Synchronization flush, 默认是异步刷盘
if (FlushDiskType.SYNC_FLUSH == this.defaultMessageStore.getMessageStoreConfig().getFlushDiskType()) {
GroupCommitService service = (GroupCommitService) this.flushCommitLogService;
if (msg.isWaitStoreMsgOK()) {
request = new GroupCommitRequest(result.getWroteOffset() + result.getWroteBytes());
//
service.putRequest(request);
boolean flushOK =
request.waitForFlush(this.defaultMessageStore.getMessageStoreConfig()
.getSyncFlushTimeout());
if (!flushOK) {
log.error("do groupcommit, wait for flush failed, topic: " + msg.getTopic() + " tags: "
+ msg.getTags() + " client address: " + msg.getBornHostString());
putMessageResult.setPutMessageStatus(PutMessageStatus.FLUSH_DISK_TIMEOUT);
}
}
else {
service.wakeup();
}
}
// Asynchronous flush
else {
this.flushCommitLogService.wakeup();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

可以看到,对于同步刷盘而言,会构造一个GroupCommitRequest对象,表明从哪里写,写多少字节。然后等待刷盘工作的完成。对于异步刷盘而言,只是notify()异步刷盘任务这个Runnable,对于何时执行真正写磁盘操作,要看线程调度了。

同步刷盘逻辑:从上面可以看到,给GroupCommitService Runnable 传递了一个GroupCommitRequest对象,触发的逻辑是会唤醒这个刷盘线程:

public void putRequest(final GroupCommitRequest request) {
synchronized (this) {
this.requestsWrite.add(request);
if (!this.hasNotified) {
this.hasNotified = true;
this.notify();
}
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

接下来,waitForFlush()会一直等到执行刷盘操作的完成。

public boolean waitForFlush(long timeout) {
try {
// 当刷盘完成后会调用 countDownLatch.countDown()
boolean result = this.countDownLatch.await(timeout, TimeUnit.MILLISECONDS);
return result || this.flushOK;
}
catch (InterruptedException e) {
e.printStackTrace();
return false;
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

那么是如何保证同步等待这个过程的完成呢?CountDownLatch,闭锁这个同步工具可以保证线程达到某种状态后才会继续下去,所以线程总是会运行的,执行刷盘操作。

 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 flush
boolean flushOK = false;
for (int i = 0; (i < 2) && !flushOK; i++) {
flushOK = (CommitLog.this.mapedFileQueue.getCommittedWhere() >= req.getNextOffset()); if (!flushOK) {
CommitLog.this.mapedFileQueue.commit(0);
}
} req.wakeupCustomer(flushOK);
} long storeTimestamp = CommitLog.this.mapedFileQueue.getStoreTimestamp();
if (storeTimestamp > 0) {
CommitLog.this.defaultMessageStore.getStoreCheckpoint().setPhysicMsgTimestamp(
storeTimestamp);
}
//注意这里清空了,所以保证写时为空
this.requestsRead.clear();
}
else {
// Because of individual messages is set to not sync flush, it
// will come to this process
CommitLog.this.mapedFileQueue.commit(0);
}
} public void run() {
while (!this.isStoped()) {
try { // 等待时机唤醒,然后执行flush操作
this.waitForRunning(0);
this.doCommit();
}
catch (Exception e) {//.. }
}
// 下面是线程正常终止,处理逻辑
// Under normal circumstances shutdown, wait for the arrival of the
// request, and then flush
try {
Thread.sleep(10);
}
catch (InterruptedException e) {
CommitLog.log.warn("GroupCommitService Exception, ", e);
} synchronized (this) {
this.swapRequests();
} this.doCommit();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56

刷盘完成后,调用wakeupCustomer(),改变闭锁状态,刷盘完成。

 public void wakeupCustomer(final boolean flushOK) {
this.flushOK = flushOK;
this.countDownLatch.countDown();
}
  • 1
  • 2
  • 3
  • 4

*异步刷盘的逻辑:从上面可以看到,对于异步刷盘,只是唤醒了该实时刷盘线程。假以时日,定会运行。异步刷盘又可以设置为定时或者实时,默认是实时。

public void run() {
while (!this.isStoped()) {
// 是否定时方式刷盘,默认是实时刷盘real time
boolean flushCommitLogTimed =
CommitLog.this.defaultMessageStore.getMessageStoreConfig().isFlushCommitLogTimed();
// CommitLog刷盘间隔时间 default 1s
int interval =
CommitLog.this.defaultMessageStore.getMessageStoreConfig()
.getFlushIntervalCommitLog();
// 刷CommitLog,至少刷几个PAGE default 4
int flushPhysicQueueLeastPages =
CommitLog.this.defaultMessageStore.getMessageStoreConfig()
.getFlushCommitLogLeastPages();
// 刷CommitLog,彻底刷盘间隔时间 default 10s
int flushPhysicQueueThoroughInterval =
CommitLog.this.defaultMessageStore.getMessageStoreConfig()
.getFlushCommitLogThoroughInterval();
try {
if (flushCommitLogTimed) {
Thread.sleep(interval);
}
else {// 实时刷,等待消息写入mapped area的通知
this.waitForRunning(interval);
}
// 进行刷盘
CommitLog.this.mapedFileQueue.commit(flushPhysicQueueLeastPages);
long storeTimestamp = CommitLog.this.mapedFileQueue.getStoreTimestamp();
if (storeTimestamp > 0) {
CommitLog.this.defaultMessageStore.getStoreCheckpoint().setPhysicMsgTimestamp(
storeTimestamp);
}
}
catch (Exception e) {//.... }
} // Normal shutdown, to ensure that all the flush before exit
//如果线程是正常终止 就要保证所有mapped area中数据写到磁盘 所以参数是0
boolean result = false;
for (int i = 0; i < RetryTimesOver && !result; i++) {
result = CommitLog.this.mapedFileQueue.commit(0);
CommitLog.log.info(this.getServiceName() + " service shutdown, retry " + (i + 1) + " times "
+ (result ? "OK" : "Not OK"));
}
CommitLog.log.info(this.getServiceName() + " service end");
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

committedWhere(long类型)变量记录写到映射区的数据字节数,据此取模可以定位到具体的一个Commitlog文件,然后写入(具体后面),写入完成后更新状态变量committedWhere

 public boolean commit(final int flushLeastPages) {
boolean result = true;
MapedFile mapedFile = this.findMapedFileByOffset(this.committedWhere, true);
if (mapedFile != null) {
long tmpTimeStamp = mapedFile.getStoreTimestamp();
//
int offset = mapedFile.commit(flushLeastPages);
long where = mapedFile.getFileFromOffset() + offset;
result = (where == this.committedWhere);
// 更新 Commit Log写到了哪里
this.committedWhere = where;
if (0 == flushLeastPages) {
this.storeTimestamp = tmpTimeStamp;
}
} return result;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

判断是否符合刷盘条件

 // 是否符合刷盘条件:映射文件满 or 数据满足指定的least page
private boolean isAbleToFlush(final int flushLeastPages) {
int flush = this.committedPosition.get();
int write = this.wrotePostion.get(); // 如果当前文件已经写满,应该立刻刷盘
if (this.isFull()) {
return true;
} // 只有未刷盘数据满足指定page数目才刷盘
if (flushLeastPages > 0) {
return ((write / OS_PAGE_SIZE) - (flush / OS_PAGE_SIZE)) >= flushLeastPages;
} // flushLeastPages 有数据就flush
return write > flush;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

实际的Flush映射内存区中的数据到物理设备中

 public int commit(final int flushLeastPages) {
if (this.isAbleToFlush(flushLeastPages)) {
if (this.hold()) {
int value = this.wrotePostion.get();
this.mappedByteBuffer.force();//写入存储设备
this.committedPosition.set(value);
this.release();
}
else {
//..
}
}
return this.getCommittedPosition();
}

mq刷盘方式的更多相关文章

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

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

  2. rocketmq刷盘过程

     本文基于rocketmq4.0版本,结合CommitlLog的刷盘过程,对消息队列的刷盘过程源码进行分析,进而对RocketMQ的刷盘原理和过程进行了解.   rocketmq 4.0版本中刷盘类型 ...

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

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

  4. 【mq读书笔记】mq索引文件刷盘

    索引文件的刷盘并不是采取定时刷盘机制,而是每更新一次索引文件就会将上一次的改动刷写到磁盘. 同步刷盘: GroupCommitRequest将被提交到GroupCommitService线程,Grou ...

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

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

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

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

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

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

  8. Centos系统真机安装,U盘方式

    下载Centos系统镜像,建议选择Minimal ISO.下载地址:https://www.centos.org/download/ 下载Fedora Media Writer,用来将系统镜像写到U盘 ...

  9. LeetCode 到底怎么刷?GitHub 上多位大厂程序员亲测的高效刷题方式

    作者:HelloGitHub-小鱼干 在众多的诸如阿里.腾讯等大厂之中,最看中面试者刷题技能的大概要数有"链表厂"之称的字节跳动了.作为一个新晋大厂,字节跳动以高薪.技术大佬云集吸 ...

随机推荐

  1. redis主从,哨兵(windows版)

    一.下载 由于redis官方并不支持windows操作系统,所以官网上是下不到的,需要到gitlab上下载,下载地址如下: https://github.com/MicrosoftArchive/re ...

  2. nginx防止DDOS攻击

    防御DDOS是一个系统工程,攻击花样多,防御的成本高瓶颈多,防御起来即被动又无奈.DDOS的特点是分布式,针对带宽和服务攻击,也就是四层流量攻击和七层应用攻击,相应的防御瓶颈四层在带宽,七层的多在架构 ...

  3. Effective STL 学习笔记14: Use reserve to avoid unnecessary reallocations.

    vector 和 string 容器在动态插入一个新的对象时,如果容器内空间不够,该容器会: 重新分配空间 通常的做法是分配当前 Capacity 大小两倍的空间. 将旧空间中的所有元素拷贝进新的空间 ...

  4. JavaScript闭包与变量的经典问题

    许多人第一次接触闭包大概都是从高程里这段代码开始的: function createFunctions() { var result = new Array(); for(var i=0; i< ...

  5. Zookeeper服务器集群的搭建与操作

    ZooKeeper 作用:Zookeeper 可以用来保证数据在zk集群之间的数据的事务性一致(原子操作). 介绍:Zookeeper 是 Google 的 Chubby一个开源的实现,是 Hadoo ...

  6. 码云Android项目构建注意事项(转载)

    1.ant项目 build.xml必须位于项目根目录. 2.maven项目 pom.xml必须位于项目根目录. 3.gradle项目 由于gradle的配置灵活,我们做了一些规范,并且增加了一下机制来 ...

  7. Adobe PS CS6安装详解

    Adobe PS CS6安装破解详解 注:电脑上是否拥有虚拟光驱,若是没有,推荐2345好压:官网http://haozip.2345.com/下载地址:http://dl.2345.com/haoz ...

  8. read file into shell vars

    test.ksh value=$(<rosstest.txt)echo $value

  9. 机器学习之路:python k均值聚类 KMeans 手写数字

    python3 学习使用api 使用了网上的数据集,我把他下载到了本地 可以到我的git中下载数据集: https://github.com/linyi0604/MachineLearning 代码: ...

  10. mysql 通过cmd 在命令行创建数据库

    一.连接MYSQL 格式: mysql -h主机地址 -u用户名 -p用户密码 1. 连接到本机上的MYSQL. 首先打开DOS窗口,然后进入目录mysql\bin,再键入命令mysql -u roo ...