Skip to content

消息存储机制 ​

深入理解RocketMQ的消息存储机制,掌握CommitLog、ConsumeQueue、IndexFile等存储文件的实现,理解消息存储优化、刷盘机制等核心功能。

📋 消息存储概览 ​

RocketMQ采用混合型存储结构,将消息存储和索引分离,提高存储性能。

🎯 一、CommitLog ​

1.1 CommitLog结构 ​

CommitLog是消息的主存储文件,所有消息都顺序写入CommitLog。

CommitLog文件结构:

+--------+--------+--------+--------+--------+--------+--------+--------+
|  Magic |  Body Length  |  Body CRC32  |  Queue ID  |  Queue Offset   |
+--------+--------+--------+--------+--------+--------+--------+--------+
|  Physical Offset  |  Sys Flag  |  Born Timestamp  |  Born Host       |
+--------+--------+--------+--------+--------+--------+--------+--------+
|  Store Timestamp  |  Store Host  |  Reconsume Times  |  Transaction   |
+--------+--------+--------+--------+--------+--------+--------+--------+
|  Prepared Transaction Offset  |  Body  |  Topic Length  |  Topic       |
+--------+--------+--------+--------+--------+--------+--------+--------+
|  Properties Length  |  Properties  |
+--------+--------+--------+--------+

1.2 消息写入 ​

DefaultMessageStore.putMessage:

java
public PutMessageResult putMessage(MessageExtBrokerInner msg) {
    PutMessageStatus checkStoreStatus = this.checkStoreStatus();
    if (checkStoreStatus != PutMessageStatus.PUT_OK) {
        return new PutMessageResult(checkStoreStatus, null);
    }
    
    PutMessageStatus msgCheckStatus = this.checkMessage(msg);
    if (msgCheckStatus == PutMessageStatus.MESSAGE_ILLEGAL) {
        return new PutMessageResult(msgCheckStatus, null);
    }
    
    long beginLockTimestamp = this.getSystemClock().now();
    this.putMessageLock.lock();
    try {
        long msgStoreTimestamp = this.commitLog.getMaxOffset();
        msg.setStoreTimestamp(msgStoreTimestamp);
        
        MappedFile unlockMappedFile = null;
        MappedFile mappedFile = this.commitLog.getLastMappedFile();
        
        msg.setQueueId(queueId);
        msg.setQueueOffset(queueOffset);
        
        AppendMessageResult result = mappedFile.appendMessage(msg);
        
        switch (result.getStatus()) {
            case PUT_OK:
                break;
            case END_OF_FILE:
                unlockMappedFile = mappedFile;
                mappedFile = this.commitLog.getLastMappedFile();
                if (null == mappedFile) {
                    return new PutMessageResult(PutMessageStatus.CREATE_MAPEDFILE_FAILED, result);
                }
                result = mappedFile.appendMessage(msg);
                break;
            case MESSAGE_SIZE_EXCEEDED:
            case PROPERTIES_SIZE_EXCEEDED:
                return new PutMessageResult(PutMessageStatus.MESSAGE_ILLEGAL, result);
            case UNKNOWN_ERROR:
                return new PutMessageResult(PutMessageStatus.UNKNOWN_ERROR, result);
            default:
                return new PutMessageResult(PutMessageStatus.UNKNOWN_ERROR, result);
        }
        
        PutMessageResult putMessageResult = new PutMessageResult(PutMessageStatus.PUT_OK, result);
        
        this.handleDiskFlush(result, putMessageResult, msg);
        this.handleHA(result, putMessageResult, msg);
        
        return putMessageResult;
    } finally {
        this.putMessageLock.unlock();
    }
}

🎯 二、ConsumeQueue ​

2.1 ConsumeQueue结构 ​

ConsumeQueue是消费队列索引,按Topic和QueueId组织。

ConsumeQueue条目结构:

+--------+--------+--------+--------+
|  CommitLog Offset (8 bytes)       |
+--------+--------+--------+--------+
|  Message Size (4 bytes)          |
+--------+--------+--------+--------+
|  Message Tag HashCode (8 bytes)  |
+--------+--------+--------+--------+

2.2 ConsumeQueue构建 ​

ConsumeQueue构建流程:

🎯 三、IndexFile ​

3.1 IndexFile结构 ​

IndexFile提供消息索引功能,支持按时间查询消息。

IndexFile结构:

+--------+--------+--------+--------+
|  Header (40 bytes)                |
+--------+--------+--------+--------+
|  Slot Table (500万 * 4 bytes)     |
+--------+--------+--------+--------+
|  Index Linked List (2000万 * 20 bytes) |
+--------+--------+--------+--------+

3.2 索引构建 ​

IndexService.buildIndex:

java
public void buildIndex(DispatchRequest req) {
    IndexFile indexFile = retryGetAndCreateIndexFile();
    if (indexFile != null) {
        long endPhyOffset = indexFile.getEndPhyOffset();
        DispatchRequest msg = req;
        String topic = msg.getTopic();
        String keys = msg.getKeys();
        if (msg.getCommitLogOffset() < endPhyOffset) {
            return;
        }
        
        final int tranType = MessageSysFlag.getTransactionValue(
                msg.getSysFlag());
        switch (tranType) {
            case MessageSysFlag.TRANSACTION_NOT_TYPE:
            case MessageSysFlag.TRANSACTION_COMMIT_TYPE:
                break;
            case MessageSysFlag.TRANSACTION_PREPARED_TYPE:
            case MessageSysFlag.TRANSACTION_ROLLBACK_TYPE:
                return;
        }
        
        if (req.getUniqKey() != null) {
            indexFile.putKey(topic, req.getUniqKey(), 
                    req.getCommitLogOffset(), req.getStoreTimestamp());
        }
        
        if (keys != null && keys.length() > 0) {
            String[] keyset = keys.split(MessageConst.KEY_SEPARATOR);
            for (int i = 0; i < keyset.length; i++) {
                String key = keyset[i];
                if (key.length() > 0) {
                    indexFile.putKey(topic, key, req.getCommitLogOffset(), 
                            req.getStoreTimestamp());
                }
            }
        }
    } else {
        log.error("build index error, stop building index");
    }
}

🎯 四、刷盘机制 ​

4.1 同步刷盘 ​

同步刷盘:

java
public void handleDiskFlush(AppendMessageResult result, 
        PutMessageResult putMessageResult, MessageExt messageExt) {
    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) {
                putMessageResult.setPutMessageStatus(
                        PutMessageStatus.FLUSH_DISK_TIMEOUT);
            }
        } else {
            service.wakeup();
        }
    } else {
        this.flushCommitLogService.wakeup();
    }
}

4.2 异步刷盘 ​

异步刷盘:

java
public void handleDiskFlush(AppendMessageResult result, 
        PutMessageResult putMessageResult, MessageExt messageExt) {
    this.flushCommitLogService.wakeup();
}

📖 学习资源 ​

视频教程 ​

书籍推荐 ​

  • 《RocketMQ技术内幕》 - 丁威
  • 《分布式消息中间件实践》 - 倪炜

网站资源 ​

✅ 学习检查清单 ​

  • [ ] 已理解CommitLog实现
  • [ ] 已理解ConsumeQueue实现
  • [ ] 已理解IndexFile实现
  • [ ] 已理解刷盘机制
  • [ ] 已绘制存储架构图
  • [ ] 已总结核心知识点

上一章节:RocketMQ源码深度解析概览
下一章节:消息投递流程
返回目录:源码阅读