前言
本篇文章主要介绍Broker接收到网络请求,是如何判断它是消息发送请求,Broker是如何接收发送请求的,下面Broker接收消息请求的流程图,通过这个流程图先有一个整体的印象,后面源码分析可以对照流程图观看。
消息接收源码分析
消息处理器注册
在Broker启动过程中BrokerController#initialize
里有一步是将请求处理器注册到NettyServerHandler,这其中就包括接收消息的请求处理器,可以看到接收消息请求处理器是SendMessageProcessor
// org.apache.rocketmq.broker.BrokerController#registerProcessor
public void registerProcessor() {
SendMessageProcessor sendProcessor = new SendMessageProcessor(this);
// ...
this.remotingServer.registerProcessor(RequestCode.SEND_MESSAGE, sendProcessor, this.sendMessageExecutor);
this.remotingServer.registerProcessor(RequestCode.SEND_MESSAGE_V2, sendProcessor, this.sendMessageExecutor);
// ... 省略其他处理器注册代码
}
注册processor的源码如下,将processor和线程池对象绑定,创建一个pair对象,放置到NettyRemotingServer中key为requestCode,value为Pair的map属性中
// org.apache.rocketmq.remoting.netty.NettyRemotingServer
// 缓存map
protected final HashMap<Integer/* requestCode */, Pair<NettyRequestProcessor, ExecutorService>> processorTable = new HashMap<Integer, Pair<NettyRequestProcessor, ExecutorService>>(64);
public void registerProcessor(int requestCode, NettyRequestProcessor processor, ExecutorService executor) {
// ...
// 构建processor和线程池的Pair
Pair<NettyRequestProcessor, ExecutorService> pair = new Pair<NettyRequestProcessor, ExecutorService>(processor, executorThis);
// 将processor和线程池Pair设置到key为RequestCode,value是pair的map中
this.processorTable.put(requestCode, pair);
}
消息接收源码分析
在broker启动过程源码分析的文章里介绍了BrokerController中用于服务端网络通信的类是NettyRemotingServer,Broker接收Producer消息的入口方法可以从NettyRemotingServer#start
找到,在start方法通过Netty构建了ServerBootstrap
,本文我们不分析RocketMQ是如何封装Netty的,我们直接找到start方法中处理请求的方法
// org.apache.rocketmq.remoting.netty.NettyRemotingServer#start
public void start() {
// ...
ServerBootstrap childHandler =
this.serverBootstrap.group(this.eventLoopGroupBoss, this.eventLoopGroupSelector)
// ...
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline()
.addLast(defaultEventExecutorGroup,
encoder,
new NettyDecoder(),
new IdleStateHandler(0, 0, nettyServerConfig.getServerChannelMaxIdleTimeSeconds()),
connectionManageHandler,
// 服务端请求处理Handler
serverHandler
);
}
});
// ...
}
在RocketMQ中处理服务端请求的类是NettyServerHandler,它是NettyRemotingServer中的内部类,broker接收到消息后首先会调用processMessageReceived。
RemotingCommand是经过反序列化得到的请求命令对象,它主要包括消息长度、序列化类型、请求头和请求体几个组成部分
// org.apache.rocketmq.remoting.netty.NettyRemotingServer.NettyServerHandler
class NettyServerHandler extends SimpleChannelInboundHandler<RemotingCommand> {
protected void channelRead0(ChannelHandlerContext ctx, RemotingCommand msg) throws Exception {
// 处理消息接收
processMessageReceived(ctx, msg);
}
}
根据命令的类型,Producer发送的消息属于请求命令,因此又将请求转发到processRequestCommand
// org.apache.rocketmq.remoting.netty.NettyRemotingAbstract#processMessageReceived
public void processMessageReceived(ChannelHandlerContext ctx, RemotingCommand msg) throws Exception {
final RemotingCommand cmd = msg;
if (cmd != null) {
switch (cmd.getType()) {
case REQUEST_COMMAND:
// 处理请求命令
processRequestCommand(ctx, cmd);
break;
// ...
}
}
}
processRequestCommand方法比较长,我们如果直接看会比较晕,因此我们拆分成两个部分,先忽略创建Runnable的代码逻辑,processRequestCommand中代码逻辑如下
- 根据请求requestCode从processorTable获取请求请求处理类和处理线程池构成的Pair,接收消息的请求处理器是SendMessageProcessor,处理消息的线程池是BrokerController.sendMessageExecutor
- 创建请求处理Runnable对象
- SendMessageProcessor判断是否拒绝本次请求,如果满足拒绝条件,则系统繁忙(
SYSTEM_BUSY
)给客户端 - 如果pair是空,找不到请求的processor,则返回不支持的请求编码给客户端(
REQUEST_CODE_NOT_SUPPORTED
) - 创建RequestTask,封装了runnable,channel,remotingCommand
- 将封装的RequestTask提交给sendMessageExecutor处理
// org.apache.rocketmq.remoting.netty.NettyRemotingAbstract#processRequestCommand
public void processRequestCommand(final ChannelHandlerContext ctx, final RemotingCommand cmd) {
// 1. 根据请求编码获取ExecutorService,接收消息的是SendMessageProcessor
final Pair<NettyRequestProcessor/*请求处理Processor*/, ExecutorService/*处理线程池*/> matched = this.processorTable.get(cmd.getCode());
// 如果是null,则采用默认的processor
final Pair<NettyRequestProcessor, ExecutorService> pair = null == matched ? this.defaultRequestProcessor : matched;
if (pair != null) {
// 2. 创建Runnable对象
Runnable run = new Runnable() {/*... 暂时忽略runnable里面的代码逻辑*/};
// 3. SendMessageProcessor判断是否拒绝本次请求,如果满足拒绝条件,则系统繁忙(`SYSTEM_BUSY`)给客户端
if (pair.getObject1().rejectRequest()) {
final RemotingCommand response = RemotingCommand.createResponseCommand(RemotingSysResponseCode.SYSTEM_BUSY,
"[REJECTREQUEST]system busy, start flow control for a while");
ctx.writeAndFlush(response);
return;
}
try {
// 5. 创建RequestTask,封装了runnable,channel,remotingCommand
final RequestTask requestTask = new RequestTask(run, ctx.channel(), cmd);
// 6. 将封装的RequestTask提交给sendMessageExecutor处理
pair.getObject2()/*executorService*/.submit(requestTask);
} catch (RejectedExecutionException e) {
// ... 忽略拒绝代码
}
} else {
// 4. 如果pair是空,找不到请求的processor,则返回不支持的请求编码给客户端(`REQUEST_CODE_NOT_SUPPORTED`)
// 构建请求命令
final RemotingCommand response =
RemotingCommand.createResponseCommand(RemotingSysResponseCode.REQUEST_CODE_NOT_SUPPORTED, error);
// 返回处理结果
ctx.writeAndFlush(response);
}
}
RequestTask源码分析
RequestTask对象实现了Runnable接口,它包括三个属性
- Runnable对象
线程池执行的Runnable对象
- Channel
与Producer通信的Channel
- RemotingCommand
public class RequestTask implements Runnable {
// runnable对象
private final Runnable runnable;
private final long createTimestamp = System.currentTimeMillis();
// netty通信channel
private final Channel channel;
// 请求command
private final RemotingCommand request;
private boolean stopRun = false;
// 省略部分代码
@Override
public void run() {
// 调用runnable方法
if (!this.stopRun)
this.runnable.run();
}
public void returnResponse(int code, String remark) {
// 创建ResponseCommand
final RemotingCommand response = RemotingCommand.createResponseCommand(code, remark);
response.setOpaque(request.getOpaque());
// 返回response
this.channel.writeAndFlush(response);
}
}
发送消息线程池处理Runnable分析
RequestTask提交给线程池后,线程池会执行Runnable中的方法,Runnable中的方法如下,整体的逻辑不是很复杂,主要包括下面几个步骤
- 执行RPC请求钩子
- 创建回调对象,回调对象中的回调方法,首先会执行RPC请求结束钩子,如果不是oneway请求,则会将RPC调用的结果返回给客户端
- 从Pair中获取请求处理器
- 将请求上下文(ChannelHandlerContext),请求命令(RquestCommand)和回调对象(RemotingResponseCallback)传给处理器,处理消息请求
- 如果Runnable执行异常,则向客户端返回系统异常(SYSTEM_ERROR)
// org.apache.rocketmq.remoting.netty.NettyRemotingAbstract#processRequestCommand
Runnable run = new Runnable() {
@Override
public void run() {
try {
// 获取远程服务器地址
String remoteAddr = RemotingHelper.parseChannelRemoteAddr(ctx.channel());
// 1. 执行RPC请求钩子
doBeforeRpcHooks(remoteAddr, cmd);
// 2. 创建回调对象
final RemotingResponseCallback callback = new RemotingResponseCallback() {
@Override
public void callback(RemotingCommand response) {
// 执行RPC结束请求钩子
doAfterRpcHooks(remoteAddr, cmd, response);
// 如果不是oneway请求,则将请求结果返回给客户端
if (!cmd.isOnewayRPC()) {
// ...
ctx.writeAndFlush(response);
// ...
}
}
};
if (pair.getObject1() instanceof AsyncNettyRequestProcessor) {
// 3.获取请求处理processor
AsyncNettyRequestProcessor processor = (AsyncNettyRequestProcessor)pair.getObject1();
// 4. 异步处理请求
processor.asyncProcessRequest(ctx, cmd, callback);
} else {
// ...
}
} catch (Throwable e) {
// 5. 如果异常,返回异常结果
if (!cmd.isOnewayRPC()) {
// ...
ctx.writeAndFlush(response);
}
}
}
};
SendMessageProcessor处理消息请求
SendMessageProcessor处理消息请求其实是调用了asyncProcessRequest方法,返回一个CompletableFuture,并利用CompletableFuture的特性,将回调对象传给CompletableFuture,在消息请求处理完成后,在回调方法内部返回请求结果
// SendMessageProcessor#asyncProcessRequest
public void asyncProcessRequest(ChannelHandlerContext ctx, RemotingCommand request, RemotingResponseCallback responseCallback) throws Exception {
asyncProcessRequest(ctx, request)/*CompletableFuture*/.thenAcceptAsync(responseCallback::callback, this.brokerController.getPutMessageFutureExecutor());
}
异步处理请求的方法主要包括4个步骤
- 解析发送消息请求的header
- 构建发送消息上下文信息
- 调用消息处理前钩子函数
- 异步处理消息请求,如果是批量消息,则调用批量消息处理方法,如果是单条消息,则调用单条消息处理方法
// SendMessageProcessor#asyncProcessRequest
public CompletableFuture<RemotingCommand> asyncProcessRequest(ChannelHandlerContext ctx,
RemotingCommand request) throws RemotingCommandException {
// ...
switch (request.getCode()) {
// 消息重回队列
case RequestCode.CONSUMER_SEND_MSG_BACK:
return this.asyncConsumerSendMsgBack(ctx, request);
default:
// 1. 解析发送消息请求header
SendMessageRequestHeader requestHeader = parseRequestHeader(request);
if (requestHeader == null) {
return CompletableFuture.completedFuture(null);
}
// 2. 构建发送消息上下文信息
mqtraceContext = buildMsgContext(ctx, requestHeader);
// 3. 调用消息处理前钩子函数
this.executeSendMessageHookBefore(ctx, request, mqtraceContext);
// 4. 异步处理消息请求
if (requestHeader.isBatch()) {
// 处理批量请求
return this.asyncSendBatchMessage(ctx, request, mqtraceContext, requestHeader);
} else {
// 处理单条请求
return this.asyncSendMessage(ctx, request, mqtraceContext, requestHeader);
}
}
}
接下来在处理单条消息时,主要可以分为下面三个步骤
- 构建MessageExtBrokerInner对象,并设置属性值
- 调用DefaultMessageStore#asyncPutMessage方法保存消息
- 处理保存结果,并返回给客户端
// SendMessageProcessor#asyncSendMessage
private CompletableFuture<RemotingCommand> asyncSendMessage(ChannelHandlerContext ctx, RemotingCommand request,
SendMessageContext mqtraceContext,
SendMessageRequestHeader requestHeader) {
// 1. 构建消息ExtBrokerInner对象,并设置属性
MessageExtBrokerInner msgInner = new MessageExtBrokerInner();
msgInner.setTopic(requestHeader.getTopic());
msgInner.setQueueId(queueIdInt);
//...
// 消息产生时间
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();
// ...
// 是否是PREPARED事务消息
if (Boolean.parseBoolean(transFlag)) {
// ...
} else {
// 2. 消息落盘
putMessageResult = this.brokerController.getMessageStore()/*DefaultMessageStore*/.asyncPutMessage(msgInner);
}
// 3. 生成普通消息结果
return handlePutMessageResultFuture(putMessageResult, response, request, msgInner, responseHeader, mqtraceContext, ctx, queueIdInt);
}
总结
本次我们介绍了Broker从网络接收消息发送请求到DefaultMessageStore处理消息的整个过程,在这其中有一段很巧妙的代码是将请求处理的Processor和执行线程池构建成一个Pair,缓存在RemotingServer中,根据不同的RequestCode找到不同的Processor处理对应请求。本文没有展开说明消息保存的具体过程,这部分我们单独介绍。