前言
在RocketMQ读写消息时,用的是Java NIO类库中的类,关于这些类的介绍和使用,可以先看我的前一篇文章Java传统IO和NIO有什么区别。接下来就从源码来分析当broker接收到消息时是如何处理的。
一、消息处理入口
消息处理入口:BrokerController.registerProcessor()
,Broker为每一种请求的Code都注册了一个处理类,其中用于处理接收消息的类为SendMessageProcessor
。
/**
* SendMessageProcessor
*/
SendMessageProcessor sendProcessor = new SendMessageProcessor(this);
sendProcessor.registerSendMessageHook(sendMessageHookList);
sendProcessor.registerConsumeMessageHook(consumeMessageHookList);
this.remotingServer.registerProcessor(RequestCode.SEND_MESSAGE, sendProcessor, this.sendMessageExecutor);
this.remotingServer.registerProcessor(RequestCode.SEND_MESSAGE_V2, sendProcessor, this.sendMessageExecutor);
this.remotingServer.registerProcessor(RequestCode.SEND_BATCH_MESSAGE, sendProcessor, this.sendMessageExecutor);
this.remotingServer.registerProcessor(RequestCode.CONSUMER_SEND_MSG_BACK, sendProcessor, this.sendMessageExecutor);
this.fastRemotingServer.registerProcessor(RequestCode.SEND_MESSAGE, sendProcessor, this.sendMessageExecutor);
this.fastRemotingServer.registerProcessor(RequestCode.SEND_MESSAGE_V2, sendProcessor, this.sendMessageExecutor);
this.fastRemotingServer.registerProcessor(RequestCode.SEND_BATCH_MESSAGE, sendProcessor, this.sendMessageExecutor);
this.fastRemotingServer.registerProcessor(RequestCode.CONSUMER_SEND_MSG_BACK, sendProcessor, this.sendMessageExecutor);
当消息到达时,会调用SendMessageProcessor.processRequest()
public RemotingCommand processRequest(ChannelHandlerContext ctx,
RemotingCommand request) throws RemotingCommandException {
SendMessageContext mqtraceContext;
switch (request.getCode()) {
case RequestCode.CONSUMER_SEND_MSG_BACK:
return this.consumerSendMsgBack(ctx, request);
default:// 默认走这里
SendMessageRequestHeader requestHeader = parseRequestHeader(request);
if (requestHeader == null) {
return null;
}
mqtraceContext = buildMsgContext(ctx, requestHeader);
this.executeSendMessageHookBefore(ctx, request, mqtraceContext);
RemotingCommand response;
if (requestHeader.isBatch()) {
// 处理批消息
response = this.sendBatchMessage(ctx, request, mqtraceContext, requestHeader);
} else {
// 处理普通消息
response = this.sendMessage(ctx, request, mqtraceContext, requestHeader);
}
this.executeSendMessageHookAfter(response, mqtraceContext);
return response;
}
}
接下来就来看看处理普通消息的过程,即sendMessage()
方法。大致可以分为两步:
- 根据接收到的消息,封装成一个
MessageExtBrokerInner
对象 - 调用
MessageStore.putMessage()
方法
private RemotingCommand sendMessage(final ChannelHandlerContext ctx,
final RemotingCommand request,
final SendMessageContext sendMessageContext,
final SendMessageRequestHeader requestHeader) throws RemotingCommandException {
// 部分代码省略
final byte[] body = request.getBody();
int queueIdInt = requestHeader.getQueueId();
TopicConfig topicConfig = this.brokerController.getTopicConfigManager().selectTopicConfig(requestHeader.getTopic());
if (queueIdInt < 0) {
queueIdInt = Math.abs(this.random.nextInt() % 99999999) % topicConfig.getWriteQueueNums();
}
// 第一步:从请求中取出消息的相关内容,并封装成一个MessageExtBrokerInner对象。
// 后续在broker内的消息处理都会使用这个MessageExtBrokerInner对象
MessageExtBrokerInner msgInner = new MessageExtBrokerInner();
msgInner.setTopic(requestHeader.getTopic());
msgInner.setQueueId(queueIdInt);
if (!handleRetryAndDLQ(requestHeader, response, request, msgInner, topicConfig)) {
return response;
}
msgInner.setBody(body);
msgInner.setFlag(requestHeader.getFlag());
MessageAccessor.setProperties(msgInner, MessageDecoder.string2messageProperties(requestHeader.getProperties()));
msgInner.setBornTimestamp(requestHeader.getBornTimestamp());
msgInner.setBornHost(ctx.channel().remoteAddress());
msgInner.setStoreHost(this.getStoreHost());
msgInner.setReconsumeTimes(requestHeader.getReconsumeTimes() == null ? 0 : requestHeader.getReconsumeTimes());
String clusterName = this.brokerController.getBrokerConfig().getBrokerClusterName();
MessageAccessor.putProperty(msgInner, MessageConst.PROPERTY_CLUSTER, clusterName);
msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties()));
PutMessageResult putMessageResult = null;
Map<String, String> oriProps = MessageDecoder.string2messageProperties(requestHeader.getProperties());
String traFlag = oriProps.get(MessageConst.PROPERTY_TRANSACTION_PREPARED);
if (traFlag != null && Boolean.parseBoolean(traFlag)) {
if (this.brokerController.getBrokerConfig().isRejectTransactionMessage()) {
response.setCode(ResponseCode.NO_PERMISSION);
response.setRemark(
"the broker[" + this.brokerController.getBrokerConfig().getBrokerIP1()
+ "] sending transaction message is forbidden");
return response;
}
putMessageResult = this.brokerController.getTransactionalMessageService().prepareMessage(msgInner);
} else {
// 第二步:普通消息的处理,执行MessageStore的putMessage方法
// 传入的参数就是上面构建的MessageExtBrokerInner对象
putMessageResult = this.brokerController.getMessageStore().putMessage(msgInner);
}
return handlePutMessageResult(putMessageResult, response, request, msgInner, responseHeader, sendMessageContext, ctx, queueIdInt);
}
接下来重点就是这个MessageStore.putMessage()
方法啦,再看这个方法之前,我们先来了解一下接下来会涉及到的各种结构,对后面理解代码会很有帮助。
二、CommitLog结构
结合上面这两张图,首先,每个broker都有一个MessageStore
,MessageStore
是一个接口,它的实现类为DefaultMessageStore
。DefaultMessageStore
中有一个成员变量CommitLog
,而CommitLog
中又有一个MappedFileQueue
,MappedFileQueue
中保存了一个MappedFile
的List,其中每一个MappedFile
都对应了磁盘上的一个文件,这些文件才是真正存储消息的地方。
可以这么理解,从我们用户的角度来看,所有消息都好像被保存到了一个很大的文件CommitLog
上,而实际上RocketMQ在实现的时候把这个CommitLog
拆成了一个个小文件MappedFile
,而这些MappedFile
都被一个MappedFileQueue
所管理。
上面第二张图中CommitLog中那些数字代表什么?
在RocketMQ中,每个MappedFile默认大小都是1G,每个MappedFile的文件名都是一个20位的数字,如“00000000001073741824”,如果把左边的0都去掉,即"1073741824",这个数字就是当前MappedFile中第一个byte在整个CommitLog中的位置,也就是偏移量。假设给定一个CommitLog偏移量为1073741830,那么我们很容易就可以知道,这个数据一定是在第二个MappedFile中,并且在第二个MappedFile中的相对偏移量为1073741830 - 1073741824 = 6
当一个MappedFile
写满以后,就会创建一个新的MappedFile
然后往这个新的MappedFile
写。
好了,接下来我们再回到上面的那个代码中的第二步,也就是创建了MessageExtBrokerInner
对象以后调用的MessageStore.putMessage()
方法。这里大家其实应该能猜到后面要干什么了吧,因为最终目的是要往MappedFile
里面写数据, 那MessageStore.putMessage()
肯定得先调用CommitLog
的某个put
方法,那CommitLog
肯定会去MappedFileQueue
找一个MappedFile
,然后调用MappedFile
的某个put
方法。下面看实际代码
三、CommitLog处理消息写入主流程
代码DefaultMessageStore.putMessage()
public PutMessageResult putMessage(MessageExtBrokerInner msg) {
// 此处省略了broker状态和消息校验的代码
long beginTime = this.getSystemClock().now();
// 果然调用了commitLog的putMessage方法,接下来看这个方法
PutMessageResult result = this.commitLog.putMessage(msg);
long elapsedTime = this.getSystemClock().now() - beginTime;
if (elapsedTime > 500) {
log.warn("putMessage not in lock elapsed time(ms)={}, bodyLength={}", elapsedTime, msg.getBody().length);
}
this.storeStatsService.setPutMessageEntireTimeMax(elapsedTime);
if (null == result || !result.isOk()) {
this.storeStatsService.getPutMessageFailedTimes().incrementAndGet();
}
return result;
}
代码:CommitLog.putMessage()
public PutMessageResult putMessage(final MessageExtBrokerInner msg) {
// Set the storage time
msg.setStoreTimestamp(System.currentTimeMillis());
// Set the message body BODY CRC (consider the most appropriate setting
// on the client)
msg.setBodyCRC(UtilAll.crc32(msg.getBody()));
// Back to Results
AppendMessageResult result = null;
// 一个数据统计的服务,不用管
StoreStatsService storeStatsService = this.defaultMessageStore.getStoreStatsService();
String topic = msg.getTopic();
int queueId = msg.getQueueId();
final int tranType = MessageSysFlag.getTransactionValue(msg.getSysFlag());
// 处理事务消息,先不管
if (tranType == MessageSysFlag.TRANSACTION_NOT_TYPE
|| tranType == MessageSysFlag.TRANSACTION_COMMIT_TYPE) {
// Delay Delivery