前言
关于mq的概念、优缺点不再多说,主要谈谈rocketmq的通信和存储,看其源码可知remoting模块就是其通信模块了,也就是netty+protocol,不多说,broker模块就是其服务模块了,负责存储消息响应producer和consumer的请求,了解rocketmq的都知道其主要分3个组件或者说4个组件:Producer、Consumer、Broker、Namesrv,既然说存储,那就谈谈Broker。
码说
用过rocketmq原生api的都了解其编程模型,大体上就是搞个Producer、设置上namesrvAddr、发送消息就完事了,对于Producer不在这里多说,比较简单,大体就是和namesrv通信选择一个broker,发送消息,也就是相对broker来说就是个客户端(注意对于存储服务来说的),那么broker是如何接收消息、如何存储消息才是我们关心的,因为消息中间件基本功能就是对一方提供写服务,对另一方提供读服务,那只要保证2个条件,基本功能也就具备了,条件就是通信和存储。
那就从通信说起,简单看看BrokerStartup就知道rocketmq是由netty来进行底层通信的,那就先看看Producer发送过来的消息Broker是如何接收处理的,先看看整体流程。
可以看到netty接收到请求后进行解码,将消息封装成任务交给线程池异步执行,实际由NettyRequestProcessor去处理请求,接下来就是由MessageStore去存储,整体大致就是如此。当然这看起来太粗糙了,通信通信模型还没有说协议,那就看看RemotingCommand,这个对象就是协议的定义了;那么如何存储呢,这个问题还得再细致说说,这里只谈到了由MessageStore去存储,还没谈谈它如何来存储的。
public CompletableFuture<PutMessageResult> asyncPutMessage(MessageExtBrokerInner msg) {
//省略
CompletableFuture<PutMessageResult> putResultFuture = this.commitLog.asyncPutMessage(msg);
//省略
return putResultFuture;
}
省略部分代码后就是其存储逻辑了,就是让CommitLog去存储,接下来看看CommitLog如何存储。
public CompletableFuture<PutMessageResult> asyncPutMessage(final MessageExtBrokerInner msg) {
MappedFile mappedFile = this.mappedFileQueue.getLastMappedFile();
try {
if (null == mappedFile || mappedFile.isFull()) {
mappedFile = this.mappedFileQueue.getLastMappedFile(0);
}
result = mappedFile.appendMessage(msg, this.appendMessageCallback);
}
CompletableFuture<PutMessageStatus> flushResultFuture = submitFlushRequest(result, msg);
CompletableFuture<PutMessageStatus> replicaResultFuture = submitReplicaRequest(result, msg);
}
可见CommitLog就是从其队列中取尾部的MappedFile,把消息追加到MappedFile,然后进行刷盘,继续看看如何刷盘。这个刷盘说起来话可就长了,还是长话短说吧。
public CompletableFuture<PutMessageStatus> submitFlushRequest(AppendMessageResult result, MessageExt messageExt) {
// Synchronization flush
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(),
this.defaultMessageStore.getMessageStoreConfig().getSyncFlushTimeout());
service.putRequest(request);
return request.future();
} else {
service.wakeup();
return CompletableFuture.completedFuture(PutMessageStatus.PUT_OK);
}
}
// Asynchronous flush
else {
if (!this.defaultMessageStore.getMessageStoreConfig().isTransientStorePoolEnable()) {
flushCommitLogService.wakeup();
} else {
commitLogService.wakeup();
}
return CompletableFuture.completedFuture(PutMessageStatus.PUT_OK);
}
}
这么一看就是同步异步刷新的问题,咱就先说先异步也就是commitLogService.wakeUp,说到这得看看这个组件了FlushCommitLogService。
这一看明白了,就是搞个线程去跑任务呗,那看看FlushRealTimeService咋跑的。
public void run() {
while (!this.isStopped()) {
try {
CommitLog.this.mappedFileQueue.flush(flushPhysicQueueLeastPages);
} catch (Throwable e) {
}
}
}
就是循环刷盘呗,当然省略了一部分代码,刷盘是有条件的,这些不是主要的,主要的是mappedFileQueue.flush。
public boolean flush(final int flushLeastPages) {
boolean result = true;
MappedFile mappedFile = this.findMappedFileByOffset(this.flushedWhere, this.flushedWhere == 0);
if (mappedFile != null) {
int offset = mappedFile.flush(flushLeastPages);
}
return result;
}
就是让MappedFile去刷盘。
public int 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.
if (writeBuffer != null || this.fileChannel.position() != 0) {
this.fileChannel.force(false);
} else {
this.mappedByteBuffer.force();
}
} catch (Throwable e) {
}
} else {
}
}
return this.getFlushedPosition();
}
到这差不多了,就是FileChannel或者MappedByteBuffer去刷盘了。
后语
回顾下我们都干了些什么,首先想看看Producer发过来的消息Broker是如何处理的(通信、存储),为了搞清楚通信,我们发现了NettyServerHandler这个组件,它拿着消息解码成RemotingCommand并且最终封装成RequestTast提交给BrokerFixedThreadPoolExecutor去执行,接下来看了是如何存储的,发现是MessageStore负责存储,要存储的东西有点多,CommitLog、ConsumeQueue、IndexFile,搞了单独线程去处理刷盘,最终通过FileChannel和MappedByteBuffer去进行刷盘。最后看下broker,主要就是netty通信,然后nio顺序存储。