【RocketMQ | 源码分析】Broker接收消息过程都做了什么

前言

本篇文章主要介绍Broker接收到网络请求,是如何判断它是消息发送请求,Broker是如何接收发送请求的,下面Broker接收消息请求的流程图,通过这个流程图先有一个整体的印象,后面源码分析可以对照流程图观看。

image-20230406231339127

消息接收源码分析

消息处理器注册

在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中代码逻辑如下

  1. 根据请求requestCode从processorTable获取请求请求处理类和处理线程池构成的Pair,接收消息的请求处理器是SendMessageProcessor,处理消息的线程池是BrokerController.sendMessageExecutor
  2. 创建请求处理Runnable对象
  3. SendMessageProcessor判断是否拒绝本次请求,如果满足拒绝条件,则系统繁忙(SYSTEM_BUSY)给客户端
  4. 如果pair是空,找不到请求的processor,则返回不支持的请求编码给客户端(REQUEST_CODE_NOT_SUPPORTED)
  5. 创建RequestTask,封装了runnable,channel,remotingCommand
  6. 将封装的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中的方法如下,整体的逻辑不是很复杂,主要包括下面几个步骤

  1. 执行RPC请求钩子
  2. 创建回调对象,回调对象中的回调方法,首先会执行RPC请求结束钩子,如果不是oneway请求,则会将RPC调用的结果返回给客户端
  3. 从Pair中获取请求处理器
  4. 将请求上下文(ChannelHandlerContext),请求命令(RquestCommand)和回调对象(RemotingResponseCallback)传给处理器,处理消息请求
  5. 如果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个步骤

  1. 解析发送消息请求的header
  2. 构建发送消息上下文信息
  3. 调用消息处理前钩子函数
  4. 异步处理消息请求,如果是批量消息,则调用批量消息处理方法,如果是单条消息,则调用单条消息处理方法
// 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);
            }
    }
}

接下来在处理单条消息时,主要可以分为下面三个步骤

  1. 构建MessageExtBrokerInner对象,并设置属性值
  2. 调用DefaultMessageStore#asyncPutMessage方法保存消息
  3. 处理保存结果,并返回给客户端
// 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处理对应请求。本文没有展开说明消息保存的具体过程,这部分我们单独介绍。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值