源码分析RocketMQ之消费队列、Index索引文件存储结构与存储机制-下篇

总结

大型分布式系统犹如一个生命,系统中各个服务犹如骨骼,其中的数据犹如血液,而Kafka犹如经络,串联整个系统。这份Kafka源码笔记通过大量的设计图展示、代码分析、示例分享,把Kafka的实现脉络展示在读者面前,帮助读者更好地研读Kafka代码。

麻烦帮忙转发一下这篇文章+关注我

就这一次!拼多多内部架构师培训Kafka源码笔记(现已绝版)

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

ConsumeQueue#recover

public void recover() {

final List mappedFiles = this.mappedFileQueue.getMappedFiles(); // @1

if (!mappedFiles.isEmpty()) {

int index = mappedFiles.size() - 3;

if (index < 0) // @2

index = 0;

int mappedFileSizeLogics = this.mappedFileSize; // @3 start

MappedFile mappedFile = mappedFiles.get(index);

ByteBuffer byteBuffer = mappedFile.sliceByteBuffer();

long processOffset = mappedFile.getFileFromOffset(); // @3 end

long mappedFileOffset = 0;

long maxExtAddr = 1;

while (true) { //

for (int i = 0; i < mappedFileSizeLogics; i += CQ_STORE_UNIT_SIZE) { // 4 start

long offset = byteBuffer.getLong(); // @5 start

int size = byteBuffer.getInt();

long tagsCode = byteBuffer.getLong(); // @5 end

if (offset >= 0 && size > 0) {

mappedFileOffset = i + CQ_STORE_UNIT_SIZE;

this.maxPhysicOffset = offset;

if (isExtAddr(tagsCode)) {

maxExtAddr = tagsCode;

}

} else {

log.info("recover current consume queue file over, " + mappedFile.getFileName() + " "

  • offset + " " + size + " " + tagsCode);

break;

}

} // @4 end

if (mappedFileOffset == mappedFileSizeLogics) { // @6

index++;

if (index >= mappedFiles.size()) {

log.info("recover last consume queue file over, last maped file "

  • mappedFile.getFileName());

break;

} else {

mappedFile = mappedFiles.get(index);

byteBuffer = mappedFile.sliceByteBuffer();

processOffset = mappedFile.getFileFromOffset();

mappedFileOffset = 0;

log.info("recover next consume queue file, " + mappedFile.getFileName());

}

} else {

log.info("recover current consume queue queue over " + mappedFile.getFileName() + " "

  • (processOffset + mappedFileOffset));

break;

}

}

processOffset += mappedFileOffset; // @7

this.mappedFileQueue.setFlushedWhere(processOffset); // @8

this.mappedFileQueue.setCommittedWhere(processOffset); // @9

this.mappedFileQueue.truncateDirtyFiles(processOffset); // @10

if (isExtReadEnable()) {

this.consumeQueueExt.recover();

log.info(“Truncate consume queue extend file by max {}”, maxExtAddr);

this.consumeQueueExt.truncateByMaxAddress(maxExtAddr);

}

}

}

代码@1:获取该消息队列的所有内存映射文件。

代码@2:只从倒数第3个文件开始,这应该是一个经验值。

代码@3 :首先介绍几个局部变量。

  • mappedFileSizeLogics

consumequeue 逻辑大小。

  • mappedFile

该queue对应的内存映射文件。

  • byteBuffer

内存映射文件对应的ByteBuffer。

  • processOffset :

处理的 offset,默认从 consumequeue 中存放的第一个条目开始。

代码@4:循环验证 consumeque 包含条目的有效性(如果offset大于0并且size大于0,则表示是一个有效的条目)

代码@5:读取一个条目的内容。

  • offset :commitlog中的物理偏移量

  • size : 该条消息的消息总长度

  • tagsCode :tag hashcode

如果 offset大于0并且size大于0,则表示是一个有效的条目,设置 consumequeue 中有效的 mappedFileOffset ,继续下一个条目的验证,如果发现不正常的条目,则跳出循环。

代码@6:如果该 consumeque 文件中所有条目全部有效,则继续验证下一个文件,(index++),如果发现条目不合法,后面的文件不需要再检测。

代码@7,:processOffset 代表了当前 consuemque 有效的偏移量。

代码8,@9:设置 flushedWhere,committedWhere 为当前有效的偏移量。

代码@10:截断无效的consumeque文件。

public void truncateDirtyFiles(long offset) {

List willRemoveFiles = new ArrayList();

for (MappedFile file : this.mappedFiles) {

long fileTailOffset = file.getFileFromOffset() + this.mappedFileSize;

if (fileTailOffset > offset) { // @1

if (offset >= file.getFileFromOffset()) {

file.setWrotePosition((int) (offset % this.mappedFileSize));

file.setCommittedPosition((int) (offset % this.mappedFileSize));

file.setFlushedPosition((int) (offset % this.mappedFileSize));

} else {

file.destroy(1000); // @2

willRemoveFiles.add(file);

}

}

}

this.deleteExpiredFile(willRemoveFiles); // @3

}

该方法主要就是再次遍历所有的 MappedFile,如果无效的 offset 大于 该 consumeque,则无需处理。

如果无效的 offset 小于该文件最大的偏移量,如果 consumequeue 的 offset 大于失效的 offset,则该文件整个删除,如果否,则设置 wrotePosition,commitedPosition,flushedPoisition 的值即可。

由此可见,DefaultMessageStore#recoverConsumeQueue 主要要做的就是先移除非法的offset。

下面代码摘录自:DefaultMessageStore#recover

if (lastExitOK) {

this.commitLog.recoverNormally();

} else {

this.commitLog.recoverAbnormally();

}

lastExitOk 为true,表示abort文件不存在,表示是正常退出,如果abort文件存在,则表示异常退出,

1.2 commitlog正常恢复


CommitLog#recoverNormally commitlog正常恢复与 ConsumeQueue 的恢复差不多的逻辑,就不重复跟踪。

1.3 commitlog异常恢复


CommitLog#recoverAbnormally

public void recoverAbnormally() {

// recover by the minimum time stamp

boolean checkCRCOnRecover = this.defaultMessageStore.getMessageStoreConfig().isCheckCRCOnRecover();

final List mappedFiles = this.mappedFileQueue.getMappedFiles();

if (!mappedFiles.isEmpty()) {

// Looking beginning to recover from which file

int index = mappedFiles.size() - 1;

MappedFile mappedFile = null;

for (; index >= 0; index–) { // @1

mappedFile = mappedFiles.get(index);

if (this.isMappedFileMatchedRecover(mappedFile)) {

log.info("recover from this maped file " + mappedFile.getFileName());

break;

}

}

if (index < 0) {

index = 0;

mappedFile = mappedFiles.get(index);

}

ByteBuffer byteBuffer = mappedFile.sliceByteBuffer();

long processOffset = mappedFile.getFileFromOffset();

long mappedFileOffset = 0;

while (true) {

DispatchRequest dispatchRequest = this.checkMessageAndReturnSize(byteBuffer, checkCRCOnRecover); // @2

int size = dispatchRequest.getMsgSize();

// Normal data

if (size > 0) {

mappedFileOffset += size;

if (this.defaultMessageStore.getMessageStoreConfig().isDuplicationEnable()) {

if (dispatchRequest.getCommitLogOffset() < this.defaultMessageStore.getConfirmOffset()) {

this.defaultMessageStore.doDispatch(dispatchRequest);

}

} else {

this.defaultMessageStore.doDispatch(dispatchRequest);

}

}

// Intermediate file read error

else if (size == -1) {

log.info("recover physics file end, " + mappedFile.getFileName());

break;

}

// Come the end of the file, switch to the next file

// Since the return 0 representatives met last hole, this can

// not be included in truncate offset

else if (size == 0) {

index++;

if (index >= mappedFiles.size()) {

// The current branch under normal circumstances should

// not happen

log.info("recover physics file over, last maped file " + mappedFile.getFileName());

break;

} else {

mappedFile = mappedFiles.get(index);

byteBuffer = mappedFile.sliceByteBuffer();

processOffset = mappedFile.getFileFromOffset();

mappedFileOffset = 0;

log.info("recover next physics file, " + mappedFile.getFileName());

}

}

}

processOffset += mappedFileOffset;

this.mappedFileQueue.setFlushedWhere(processOffset);

this.mappedFileQueue.setCommittedWhere(processOffset);

this.mappedFileQueue.truncateDirtyFiles(processOffset);

// Clear ConsumeQueue redundant data

this.defaultMessageStore.truncateDirtyLogicFiles(processOffset);

}

// Commitlog case files are deleted

else {

最后

金三银四到了,送上一个小福利!

image.png

image.png

专题+大厂.jpg

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

ssageStore.truncateDirtyLogicFiles(processOffset);

}

// Commitlog case files are deleted

else {

最后

金三银四到了,送上一个小福利!

[外链图片转存中…(img-l0Q3hGCs-1715691933249)]

[外链图片转存中…(img-MZ3lfG3z-1715691933250)]

[外链图片转存中…(img-k7qLWiDX-1715691933250)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

  • 9
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值