ConsumeQueue for source code analysis

Keywords: Java kafka RocketMQ

ConsumeQueue for source code analysis

When sending a message, the data is displayed in the ConsumeQueue

Five messages are sent continuously. The messages are of variable length. First, all information is put into the Commitlog. Each message needs to be locked when it is put into the Commitlog to ensure sequential writing.

After the Commitlog is written successfully. The data is resynchronized to the consumequeue.

And the data is distributed one by one. This is a typical polling.

Queue Offset represents the number of messages in a queue

Logic Offset is Queue Offset*20, because the message length in each ConsumeQueue is 20

Physical Offset, which is the offset of each message in the Commitlog.

This design is very clever:

When searching for a message, you can directly calculate the global location of the index according to the message sequence number of the queue (for example, if the sequence number is 2, you know that the offset is 20), then directly read the index, and then find the message according to the global location of the message recorded in the index. The two operations are to find the index and the file where the message is located, These two searches are similar and can be abstracted into:

Because the length of each index file or message file is fixed, an orderly file array from small to large is maintained for each group of files. When searching for a file, you can directly obtain the serial number of the file in the array through calculation:

Sequence number of the file in the array = (global location - file name of the first file) / fixed file size

The time complexity of obtaining data in the array through sequence number is 0 (1), and the time complexity of secondary file search is: 0 (1) + 0 (1) = 0 (1). Therefore, the time complexity of finding data during consumption is also O(1).

Entry: reputmessageservice.doreput (independent thread)

DefaultMessageStore. start()

// org.apache.rocketmq.store.DefaultMessageStore#start
log.info("[SetReputOffset] maxPhysicalPosInLogicQueue={} clMinOffset={} clMaxOffset={} clConfirmedOffset={}",
         maxPhysicalPosInLogicQueue, this.commitLog.getMinOffset(), this.commitLog.getMaxOffset(),
         this.commitLog.getConfirmOffset());
this.reputMessageService.setReputFromOffset(maxPhysicalPosInLogicQueue);
this.reputMessageService.start(); // here

ReputMessageService.run()

// org.apache.rocketmq.store.DefaultMessageStore.ReputMessageService#run
public void run() {
    DefaultMessageStore.log.info(this.getServiceName() + " service started");
    while (!this.isStopped()) {
        try {
            Thread.sleep(1);
            this.doReput(); // here
        } catch (Exception e) {
            DefaultMessageStore.log.warn(this.getServiceName() + " service has exception. ", e);
        }
    }
    DefaultMessageStore.log.info(this.getServiceName() + " service end");
}
// org.apache.rocketmq.store.DefaultMessageStore.ReputMessageService#doReput
private void doReput() {
    if (this.reputFromOffset < DefaultMessageStore.this.commitLog.getMinOffset()) {
        this.reputFromOffset = DefaultMessageStore.this.commitLog.getMinOffset();
    }
    for (boolean doNext = true; this.isCommitLogAvailable() && doNext; ) {
        if (DefaultMessageStore.this.getMessageStoreConfig().isDuplicationEnable()
            && this.reputFromOffset >= DefaultMessageStore.this.getConfirmOffset()) {
            break;
        }
        //It mainly builds ConsumerQueue and Index
        //reputFromOffset: progress of building ConsumerQueue and Index
        SelectMappedBufferResult result = DefaultMessageStore.this.commitLog.getData(reputFromOffset);
        if (result != null) {
            ...
                DefaultMessageStore.this.doDispatch(dispatchRequest); // here
            ...
        }
    }
}
// org.apache.rocketmq.store.DefaultMessageStore.CommitLogDispatcherBuildConsumeQueue
class CommitLogDispatcherBuildConsumeQueue implements CommitLogDispatcher {

    @Override
    public void dispatch(DispatchRequest request) {
        final int tranType = MessageSysFlag.getTransactionValue(request.getSysFlag());
        switch (tranType) {
            case MessageSysFlag.TRANSACTION_NOT_TYPE:
            case MessageSysFlag.TRANSACTION_COMMIT_TYPE:
                // Build a logical queue for the consumer
                DefaultMessageStore.this.putMessagePositionInfo(request); // here
                break;
            case MessageSysFlag.TRANSACTION_PREPARED_TYPE:
            case MessageSysFlag.TRANSACTION_ROLLBACK_TYPE:
                break;
        }
    }
}
// org.apache.rocketmq.store.DefaultMessageStore#putMessagePositionInfo
public void putMessagePositionInfo(DispatchRequest dispatchRequest) {
    ConsumeQueue cq = this.findConsumeQueue(dispatchRequest.getTopic(), dispatchRequest.getQueueId());
    cq.putMessagePositionInfoWrapper(dispatchRequest);
}

public void putMessagePositionInfoWrapper(DispatchRequest request) {
...
    // Build a logical queue for the consumer
    boolean result = this.putMessagePositionInfo(
    request.getCommitLogOffset(), request.getMsgSize(), 
    tagsCode, request.getConsumeQueueOffset());
}

// org.apache.rocketmq.store.ConsumeQueue#putMessagePositionInfo
private boolean putMessagePositionInfo(final long offset, final int size, final long tagsCode,
                                       final long cqOffset) {
    ...
	MappedFile mappedFile = this.mappedFileQueue.getLastMappedFile(expectLogicOffset); // here
    if (mappedFile != null) {
        return mappedFile.appendMessage(this.byteBufferIndex.array()); // here
    }
}
// org.apache.rocketmq.store.MappedFile#appendMessage(byte[])
public boolean appendMessage(final byte[] data) {
    int currentPos = this.wrotePosition.get();
    if ((currentPos + data.length) <= this.fileSize) {
        try {
            this.fileChannel.position(currentPos);
            this.fileChannel.write(ByteBuffer.wrap(data));
        } catch (Throwable e) {
            log.error("Error occurred when append message to mappedFile.", e);
        }
        this.wrotePosition.addAndGet(data.length);
        return true;
    }
    return false;
}

Asynchronous brush disk

// org.apache.rocketmq.store.DefaultMessageStore#DefaultMessageStore
public DefaultMessageStore(final MessageStoreConfig messageStoreConfig, 
                            final BrokerStatsManager brokerStatsManager,
                            final MessageArrivingListener messageArrivingListener,
                            final BrokerConfig brokerConfig) throws IOException {
     this.messageArrivingListener = messageArrivingListener;
     this.brokerConfig = brokerConfig;
     this.messageStoreConfig = messageStoreConfig;
     this.brokerStatsManager = brokerStatsManager;
     this.allocateMappedFileService = new AllocateMappedFileService(this);
     if (messageStoreConfig.isEnableDLegerCommitLog()) {
         this.commitLog = new DLedgerCommitLog(this);
     } else {
         this.commitLog = new CommitLog(this);
     }
     this.consumeQueueTable = new ConcurrentHashMap<>(32);

     this.flushConsumeQueueService = new FlushConsumeQueueService(); // here
 }
// 
private void doFlush(int retryTimes) {
...
    if (0 == flushConsumeQueueLeastPages) {
        if (logicsMsgTimestamp > 0) {
            DefaultMessageStore.this.getStoreCheckpoint().setLogicsMsgTimestamp(logicsMsgTimestamp);
        }
        DefaultMessageStore.this.getStoreCheckpoint().flush(); // here
    }
}

Posted by adnan1983 on Mon, 29 Nov 2021 11:06:44 -0800