mq刷盘方式
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刷盘方式的更多相关文章
- Rocket重试机制,消息模式,刷盘方式
		一.Consumer 批量消费(推模式) 可以通过 consumer.setConsumeMessageBatchMaxSize(10);//每次拉取10条 这里需要分为2种情况 Consumer端先 ... 
- rocketmq刷盘过程
		本文基于rocketmq4.0版本,结合CommitlLog的刷盘过程,对消息队列的刷盘过程源码进行分析,进而对RocketMQ的刷盘原理和过程进行了解. rocketmq 4.0版本中刷盘类型 ... 
- RocketMQ中Broker的刷盘源码分析
		上一篇博客的最后简单提了下CommitLog的刷盘 [RocketMQ中Broker的消息存储源码分析] (这篇博客和上一篇有很大的联系) Broker的CommitLog刷盘会启动一个线程,不停地 ... 
- 【mq读书笔记】mq索引文件刷盘
		索引文件的刷盘并不是采取定时刷盘机制,而是每更新一次索引文件就会将上一次的改动刷写到磁盘. 同步刷盘: GroupCommitRequest将被提交到GroupCommitService线程,Grou ... 
- RocketMQ消息丢失解决方案:同步刷盘+手动提交
		前言 之前我们一起了解了使用RocketMQ事务消息解决生产者发送消息时消息丢失的问题,但使用了事务消息后消息就一定不会丢失了吗,肯定是不能保证的. 因为虽然我们解决了生产者发送消息时候的消息丢失问题 ... 
- 【RocketMQ】消息的刷盘机制
		刷盘策略 CommitLog的asyncPutMessage方法中可以看到在写入消息之后,调用了submitFlushRequest方法执行刷盘策略: public class CommitLog { ... 
- MySQL InnoDB 日志管理机制中的MTR和日志刷盘
		1.MTR(mini-transaction) 在MySQL的 InnoDB日志管理机制中,有一个很重要的概念就是MTR.MTR是InnoDB存储擎中一个很重要的用来保证物理写的完整性和持久性的机制. ... 
- Centos系统真机安装,U盘方式
		下载Centos系统镜像,建议选择Minimal ISO.下载地址:https://www.centos.org/download/ 下载Fedora Media Writer,用来将系统镜像写到U盘 ... 
- LeetCode 到底怎么刷?GitHub 上多位大厂程序员亲测的高效刷题方式
		作者:HelloGitHub-小鱼干 在众多的诸如阿里.腾讯等大厂之中,最看中面试者刷题技能的大概要数有"链表厂"之称的字节跳动了.作为一个新晋大厂,字节跳动以高薪.技术大佬云集吸 ... 
随机推荐
- MySQL学习笔记:upper、lower、ucase、lacase——字符串函数
			在MySQL中,通过利用upper.lower.ucase.lacase几个函数对字符串进行大小写转换. upper(str)——根据当前字符集映射返回字符串str,并将所有字符更改为大写.默认值是l ... 
- EFK收集Kubernetes应用日志
			本节内容: EFK介绍 安装配置EFK 配置efk-rbac.yaml文件 配置 es-controller.yaml 配置 es-service.yaml 配置 fluentd-es-ds.yaml ... 
- Java学习(异常类)
			一.什么是异常: 异常就是在运行时产生的问题.通常用Exception描述. 在java中,把异常封装成了一个类,当出现问题时,就会创建异常类对象并抛出异常相关的信息(如详细信息,名称以及异常所处的位 ... 
- day5模块学习--yaml文件处理
			yaml文件处理(http://pyyaml.org/wiki/PyYAMLDocumentation) 摘要: 本文讲的是yaml在python上的使用教程详解, YAML是一种容易人类阅读 ... 
- 【Java网络编程】基于 UDP 的聊天通信
			使用 udp 协议,写一个基于命令行的聊天软件:客户端跟服务端分别在命令行启动之后,客户端和服务器端可以互相发送数据. 代码实现如下: 一.创建线程 sendThread 和 receiveThrea ... 
- linux kernel.shmall shemax shemin解释
			Linux X86-64操作系统,Oracle 10g数据库,由8G加到16G,把kernel.shmmax参数改到17179869184(16G)后,发现只要修改sga_max_size和s ... 
- 007 Ajax中的购物车
			1.大纲设计 2.shopingCartItem.java package beans; public class shopingCartItem { private String bookname; ... 
- jquery 查询IP归属地
			<script src="http://c.csdnimg.cn/public/common/libs/jquery/jquery-1.9.1.min.js" type=&q ... 
- Python网络编程之socket应用
			1 引言 本篇主要对Python下网络编程中用到的socket模块进行初步总结.首先从网络基础理论出发,介绍了TCP协议和UDP协议:然后总结了socket中的常用函数:最后通过实际代码展示基本函数的 ... 
- JFinal 3.3 入门学习 -- Hello JFinal World.
			资源准备 jar包下载:http://www.jfinal.com/download/?file=jfinal-3.3-all.zip 下载完成后需要用到的jar包: 将 jfinal-3.3-bin ... 
