rocketmq:4.3
消息写入概要写入流出,先写入CommitLog,然后会有异步线程写入ConsumeQueue和IndexFile文件。这个写入是顺序写。
下面来看下各个文件里面的索引/offset都是什么含义。
先看现象,容易理解。首先配置mapedFileSizeCommitLog(即commitLog的文件大小)为10K,然后创建topic:commitLogTopic,然后发送长度为900byte的字符串9次。commitLog和consumequeue的结构如下。rocketmq在创建mappedfile的时候会进行预分配。所以还会有一个以10240结尾的文件,虽然占了10K空间,但是里面是空的。
[-bash-3.2$ ls -lh commitlog/
total 24
-rw-r--r-- 1 xiao staff 10K 3 29 10:00 00000000000000000000
-rw-r--r-- 1 xiao staff 10K 3 29 10:00 00000000000000010240
然后观察consumequeue的文件,也是已经分配好了5.7M的空间
[-bash-3.2$ ls -lh consumequeue/commitLogTopic/0/
total 11720
-rw-r--r-- 1 xiao staff 5.7M 3 29 10:00 00000000000000000000
读取一下consumequeue的内容。第一列是消息数量,第二列是tag的hashcode(我们没设置tag),第三列是消息的长度,第四列是消息的offset(物理的offset)。观察最后一个消息的offset是8464,是说第9条消息在第一个commitLog(即00000000000000000000里面的开始写入的位置),但是实际上消息已经写到了(8464+1058)这个位置,注意是写到了。
1: 0 1058 0
2: 0 1058 1058
3: 0 1058 2116
4: 0 1058 3174
5: 0 1058 4232
6: 0 1058 5290
7: 0 1058 6348
8: 0 1058 7406
9: 0 1058 8464
那么第一个commitLog剩余可写的容量是10240-9522(8464+1058)=718,即不足以存储下一条消息(我们发送的消息)。
接下来我们发送第10条消息,发现00000000000000010240被写入了一条消息,(同时又生成了第三个commitLog文件:00000000000000020480)我们再次读一下consumequeue,第10条消息的内容如下:
10: 0 1058 10240
即每个commitLog里面第一条消息的offset跟当前文件名称的offset一致。既然第10条消息写如了第2个commitLog文件,那第一个commitLog后面的字节变成了什么?我们把第一个commitLog读到byte[]里面,然后倒叙输出,发现后718个字节是0,即被0补齐了。
OK,那接下来咱们看下代码里面的一些逻辑。
首先,消息的各种配置的类是:
org.apache.rocketmq.store.config.MessageStoreConfig
里面定义的CommitLog默认大小1G,然后进入CommitLog类的构造方法,分配了mappedFileQueue,commitLogService等等变量,我们这里关注mappedFileQueue
public CommitLog(final DefaultMessageStore defaultMessageStore) {
this.mappedFileQueue = new MappedFileQueue(defaultMessageStore.getMessageStoreConfig().getStorePathCommitLog(),
defaultMessageStore.getMessageStoreConfig().getMapedFileSizeCommitLog(), defaultMessageStore.getAllocateMappedFileService());
this.defaultMessageStore = defaultMessageStore;
然后何时会创建MappedFile呢?
方法在AllocateMappedFileService.mmapOperation()方法里面,它是从一个queue取出AllocateReq,然后进行构造。
private boolean mmapOperation() {
boolean isSuccess = false;
AllocateRequest req = null;
try {
req = this.requestQueue.take();
AllocateRequest expectedRequest = this.requestTable.get(req.getFilePath());
if (null == expectedRequest) {
log.warn("this mmap request expired, maybe cause timeout " + req.getFilePath() + " "
+ req.getFileSize());
return true;
}
if (expectedRequest != req) {
log.warn("never expected here, maybe cause timeout " + req.getFilePath() + " "
+ req.getFileSize() + ", req:" + req + ", expectedRequest:" + expectedRequest);
return true;
}
if (req.getMappedFile() == null) {
long beginTime = System.currentTimeMillis();
MappedFile mappedFile;
if (messageStore.getMessageStoreConfig().isTransientStorePoolEnable()) {
try {
mappedFile = ServiceLoader.load(MappedFile.class).iterator().next();
// 这里创建
mappedFile.init(req.getFilePath(), req.getFileSize(), messageStore.getTransientStorePool());
} catch (RuntimeException e) {
log.warn("Use default implementation.");
mappedFile = new MappedFile(req.getFilePath(), req.getFileSize(), messageStore.getTransientStorePool());
}
} else {
// 或者这里创建
mappedFile = new MappedFile(req.getFilePath(), req.getFileSize());
}
// 省略其他部分
}
那接下来的问题就是这个queue里面的参数是在什么地方放进去的呢?我们搜一下这个requestQueue,那么发现是这个类里面的putRequestAndReturnMappedFile方法。代码不贴了,具体有是谁调用了它,通过IDEA的find usage即可找到。
分析完MappedFile的创建过程,我们开始看下消息写入的流程。
直接进入CommitLog查看的putMessage方法,然后进入这行代码里面继续查看
result = mappedFile.appendMessage(msg, this.appendMessageCallback);
然后进入到MappedFile.appendMessageInner方法,
public AppendMessageResult appendMessagesInner(final MessageExt messageExt, final AppendMessageCallback cb) {
assert messageExt != null;
assert cb != null;
// 已写入的字节数
int currentPos = this.wrotePosition.get();
if (currentPos < this.fileSize) {
ByteBuffer byteBuffer = writeBuffer != null ? writeBuffer.slice() : this.mappedByteBuffer.slice();
byteBuffer.position(currentPos);
AppendMessageResult result = null;
if (messageExt instanceof MessageExtBrokerInner) {
// 进入这里查看逻辑,this.getFileFromOffset()即文件的名字,this.fileSize - currentPos表示当前文件剩余可写入的空间
result = cb.doAppend(this.getFileFromOffset(), byteBuffer, this.fileSize - currentPos, (MessageExtBrokerInner) messageExt);
} else if (messageExt instanceof MessageExtBatch) {
result = cb.doAppend(this.getFileFromOffset(), byteBuffer, this.fileSize - currentPos, (MessageExtBatch) messageExt);
} else {
return new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR);
}
// 消息写入后,更新已写入字节数
this.wrotePosition.addAndGet(result.getWroteBytes());
this.storeTimestamp = result.getStoreTimestamp();
return result;
}
log.error("MappedFile.appendMessage return null, wrotePosition: {} fileSize: {}", currentPos, this.fileSize);
return new AppendMessageResult(AppendMessageStatus.UNKNOWN_ERROR);
}
注意观察标记类注释的那行,再次跳回到CommitLog类
public AppendMessageResult doAppend(final long fileFromOffset, final ByteBuffer byteBuffer, final int maxBlank,
final MessageExtBrokerInner msgInner) {
// STORETIMESTAMP + STOREHOSTADDRESS + OFFSET <br>
// PHY OFFSET
long wroteOffset = fileFromOffset + byteBuffer.position();
这里看到wroteOffset即是文件的偏移量+当前已写入字节数的位置
后续继续更新。。。