7. Broker-文件刷盘机制

本文深入探讨RocketMQ的消息存储机制,重点分析了同步刷盘和异步刷盘的工作原理,以及如何通过Broker配置实现不同刷盘策略,确保消息的可靠性和系统性能。

        rocketmq的消息存储是先写入内存,再根据不同的刷盘策略进行刷盘,rocketmq支持两种刷盘模式:

同步刷盘:消息写入后等待刷盘成功返回

异步刷盘:消息写入内存后直接返回,由后台线程去定时刷盘

broker配置文件中设置flushDiskType字段,可选择ASYNC_FLUSH(异步刷盘)和 SYNC_FLUSH(同步刷盘)

前面"Broker-消息存储流程"中,CommitLog.putMessage方法的最后会调用CommitLog.handleDiskFlush方法进行刷盘。

    public void handleDiskFlush(AppendMessageResult result, PutMessageResult putMessageResult, 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());
                service.putRequest(request);
                boolean flushOK = request.waitForFlush(this.defaultMessageStore.getMessageStoreConfig().getSyncFlushTimeout());
                if (!flushOK) {
                    log.error("do groupcommit, wait for flush failed, topic: " + messageExt.getTopic() + " tags: " + messageExt.getTags()
                        + " client address: " + messageExt.getBornHostString());
                    putMessageResult.setPutMessageStatus(PutMessageStatus.FLUSH_DISK_TIMEOUT);
                }
            } else {
                service.wakeup();
            }
        }
        // Asynchronous flush
        else {
            if (!this.defaultMessageStore.getMessageStoreConfig().isTransientStorePoolEnable()) {
                flushCommitLogService.wakeup();
            } else {
                commitLogService.wakeup();
            }
        }
    }

该方法中,首先判断当前刷盘模式是同步刷盘还是异步刷盘。

对于同步刷盘,构造一个GroupCommitRequest对象,并提交到flushCommitLogService中,线程阻塞等待刷盘完成。

对于异步刷盘,直接激活刷盘线程并返回,线程并不阻塞。

看一下GroupCommitRequest类图

nextOffset:刷盘点偏移量

countDownLatch:刷盘控制器

flushOk:刷盘结果

public boolean waitForFlush(long timeout) {
            try {
                this.countDownLatch.await(timeout, TimeUnit.MILLISECONDS);
                return this.flushOK;
            } catch (InterruptedException e) {
                log.error("Interrupted", e);
                return false;
            }
        }

提交请求后在countDownLatch上等待,默认超时时间5s,flushCommitLogService线程处理完刷盘后,调用GroupCommitRequest.wakeupCustomer方法通知等待线程

public void wakeupCustomer(final boolean flushOK) {
            this.flushOK = flushOK;
            this.countDownLatch.countDown();
        }

看下刷盘线程处理逻辑,默认的刷盘线程是GroupCommitService

requestsWrite:写请求缓冲池

requestsRead:处理的缓冲池

主循环不断读取requestsRead处理,然后将两个缓冲池交换,处理另一个缓冲池。

private void swapRequests() {
            List<GroupCommitRequest> tmp = this.requestsWrite;
            this.requestsWrite = this.requestsRead;
            this.requestsRead = tmp;
        }

 请求一直往requestsWrite缓冲池插入。

public synchronized void putRequest(final GroupCommitRequest request) {
            //将请求加入requestsWrite缓冲池
            synchronized (this.requestsWrite) {
                this.requestsWrite.add(request);
            }
            //如果线程处于等待状态将其唤醒
            if (hasNotified.compareAndSet(false, true)) {
                waitPoint.countDown(); // notify
            }
        }

线程不断循环处理刷盘请求,每次处理完其中一个缓冲池,如果另一个缓冲池没有数据会sleep10毫秒

public void run() {
            CommitLog.log.info(this.getServiceName() + " service started");

            while (!this.isStopped()) {
                try {
                    this.waitForRunning(10);
                    this.doCommit();
                } catch (Exception e) {
                    CommitLog.log.warn(this.getServiceName() + " service has exception. ", e);
                }
            }
。。。
}

刷盘逻辑在doCommit中处理,最终调用mappedFileQueue.flush进行刷盘。


        private void doCommit() {
            synchronized (this.requestsRead) {
                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.mappedFileQueue.getFlushedWhere() >= req.getNextOffset();

                            if (!flushOK) {
                                CommitLog.this.mappedFileQueue.flush(0);
                            }
                        }

                        req.wakeupCustomer(flushOK);
                    }

                    long storeTimestamp = CommitLog.this.mappedFileQueue.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.mappedFileQueue.flush(0);
                }
            }
        }

 

对于异步刷盘直接激活刷盘线程

// Asynchronous flush
        else {
            if (!this.defaultMessageStore.getMessageStoreConfig().isTransientStorePoolEnable()) {
                flushCommitLogService.wakeup();
            } else {
                commitLogService.wakeup();
            }
        }

 

 

 

 

### RocketMQ 2M-2S 同步复制 异步刷盘 配置与工作原理 #### 工作原理概述 在RocketMQ的2主2从架构下,同步复制意味着当生产者发送的消息被主Broker接收后,该消息会被立即转发给对应的从Broker。只有当所有指定的副本都确认接收到这条消息后,才会向生产者返回成功响应。这种机制确保了即使某个Broker发生故障,其他存活的Brokers仍然持有完整的数据备份。 对于异步刷盘而言,在接收到新消息并将其存储到内存队列之后,并不会立刻将这些变更持久化至物理磁盘上;而是通过后台线程定期批量处理的方式完成实际的数据落地操作。这有助于提高系统的整体性能,但也带来了潜在的风险——如果服务器突然断电,则可能会造成部分未及时保存的信息丢失[^1]。 #### 设置方法详解 为了实现上述特性,需要对`broker.conf`文件做出相应调整: ##### Broker A (Master) ```properties # 定义当前实例的角色为主节点 brokerRole=ASYNC_MASTER # 开启自动创建主题功能,默认开启即可 autoCreateTopicEnable=true # 自动创建订阅组开关同样保持默认状态 autoCreateSubscriptionGroup=true # 消息提交日志路径配置项可根据实际情况修改 storePathCommitLog=/home/work/store/commitlog # 记录消费进度的日志存放位置设定 storePathConsumeQueue=/home/work/store/consumequeue ``` ##### Broker B (Slave) ```properties # 明确指出此实例作为备选节点运行 brokerRole=SLAVE # 关联其对应的主要服务端地址 masterAddress=<MASTER_BROKER_IP>:<PORT> # 存储层面上依旧沿用之前的参数定义方式 storePathCommitLog=/home/work/store/slave_commitlog storePathConsumeQueue=/home/work/store/slave_consumequeue ``` 值得注意的是,在启用同步复制的同时选择了异步刷盘模式时,虽然能够获得较好的吞吐量表现,但由于缺乏即时性的硬盘写入保障,因此存在一定的安全隐患。建议根据具体应用场景权衡利弊后再做决定[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值