消息保存-消息刷入磁盘

 

消息入盘的基本逻辑都在MappedFile这个类中。根据配置的不同,MappedFile为我们准备了两块不同的缓存。

  • DirectBuffer类型的writeBuffer
  • MappedByteBuffer类型的mappedByteBuffer

如果writeBuffer不为空的情况下,优先使用writeBuffer,否则数据将写入到mappedByteBuffer。数据只会写入一块buffer,不会同时写入。

首先,我们看看什么情况下会使用writeBuffer。

public boolean isTransientStorePoolEnable() {
	return transientStorePoolEnable && FlushDiskType.ASYNC_FLUSH == getFlushDiskType()
		&& BrokerRole.SLAVE != getBrokerRole();
}

由此可见,只有broker主节点,配置为异步刷盘的情况下,才有可能会使用writeBuffer。

第一步,将消息写入MappedFile的缓存区域,这步进行之后,会更新wrotePosition字段。

//选择缓冲内存
ByteBuffer byteBuffer = writeBuffer != null ? writeBuffer.slice() : this.mappedByteBuffer.slice();

...

//将消息封写入缓存内存
byteBuffer.put(this.msgStoreItemMemory.array(), 0, msgLen);

第二步,将内存缓存的数据刷入磁盘。这步分为三种情况:

  • 同步 + MappedByteBuffer
  • 异步 + MappedByteBuffer
  • 异步 + DirectBuffer

第一种情况对应的逻辑在GroupCommitService类中,核心方法为GroupCommitService::doCommit,最终调用到public int MappedFile::flush(final int flushLeastPages)。

第二种情况对应的逻辑在FlushRealTimeService类中,核心方法为FlushRealTimeService::run,最终也是调用到最终调用到public int MappedFile::flush(final int flushLeastPages)。

第三种情况对应的逻辑在CommitRealTimeService中,核心方法为CommitRealTimeService::run,最终调用到protected void MappedFile::commit0(final int commitLeastPages)。

其实第一种和第二种基本一致,唯一的区别在于,第一种是有数据就刷盘,第二种是当数据达到一定的数量(2页)才刷盘。

分别贴一下两个方法的核心代码。

/**
 * 第一种情况,参数为0,有数据就会刷盘。
 * 第二种情况,参数为页数(2),达到指定大小才会刷盘
 */
private boolean isAbleToFlush(final int flushLeastPages) {
    int flush = this.flushedPosition.get();
    int write = getReadPosition();

    if (this.isFull()) {
        return true;
    }

    if (flushLeastPages > 0) {
        return ((write / OS_PAGE_SIZE) - (flush / OS_PAGE_SIZE)) >= flushLeastPages;
    }

    return write > flush;
}

/**
 * @return The current flushed position
 */
public int MappedFile::flush(final int flushLeastPages) {
	if (this.isAbleToFlush(flushLeastPages)) {
		if (this.hold()) {
			int value = getReadPosition();

			try {
				//We only append data to fileChannel or mappedByteBuffer, never both.
                //这个writeBuffer的判空很魔幻。目前的版本,走到这里的情况下,writeBuffer永远为空。如果不为空了,这个地方应该是需要修改的,加上将writeBuffer写到fileChannel的逻辑
				if (writeBuffer != null || this.fileChannel.position() != 0) {
					this.fileChannel.force(false);
				} else {
					this.mappedByteBuffer.force();
				}
			} catch (Throwable e) {
				log.error("Error occurred when force data to disk.", e);
			}

			this.flushedPosition.set(value);
			this.release();
		} else {
			log.warn("in flush, hold failed, flush offset = " + this.flushedPosition.get());
			this.flushedPosition.set(getReadPosition());
		}
	}
	return this.getFlushedPosition();
}

这个执行完成之后,flushedPosition会更新。

protected void MappedFile::commit0(final int commitLeastPages) {
	int writePos = this.wrotePosition.get();
	int lastCommittedPosition = this.committedPosition.get();

	if (writePos - this.committedPosition.get() > 0) {
		try {
			//将DirectBuffer中内容写入文件
			ByteBuffer byteBuffer = writeBuffer.slice();
			byteBuffer.position(lastCommittedPosition);
			byteBuffer.limit(writePos);
			this.fileChannel.position(lastCommittedPosition);
			this.fileChannel.write(byteBuffer);
			//更新committedPosition字段
			this.committedPosition.set(writePos);
		} catch (Throwable e) {
			log.error("Error occurred when commit data to FileChannel.", e);
		}
	}
}

这个执行完成之后,commitedPosition会更新。

最后,附上时序图。消息写入内存缓存的时序

第一、二种情况,消息刷入磁盘的时序

第三种情况,消息刷入磁盘的时序

参考文章:

https://blog.csdn.net/yankunhaha/article/details/97232870

 

源码学习的其他方面的,有兴趣了解的可以看看:

https://blog.csdn.net/wusheng520/article/details/108798042

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值