RocketMQ(六) - 服务端请求与响应的处理 源码分析
RocketMQ(六) - 服务端请求与响应的处理 源码分析
上一篇 《RocketMQ(五) - RemotingServer服务端 请求方法 源码分析》讲述了 RocketMQ 中 Netty服务端作为 请求发送方 的 三个 调用方法。
本篇主要分析 服务端 对 客户端发来的 请求 与 响应 的处理方法。
RocketMQ 的 Netty服务端 在启动方法中,向 NioSocketChannel 的 pipeline中 添加了 很多 handler处理器, 而其中 的 NettyServerHandler 专门是用来处理 客户端发来的 请求与响应的。
@ChannelHandler.Sharable
class NettyServerHandler extends SimpleChannelInboundHandler<RemotingCommand> {
/**
* 处理客户端发来消息的方法
* ctx ctx对象
* msg 远程对象 (请求对象 / 响应对象) 该对象在之前的Encodehandler中已经被解析好了
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, RemotingCommand msg) throws Exception {
// 核心处理消息接受方法
// 这里会调用到父类 NettyRemotingAbstract的 processMessageReceived方法
processMessageReceived(ctx, msg);
}
}
1. 消息处理
下面直接来看 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;
case RESPONSE_COMMAND: // 客户端响应给服务器的数据 走这里
processResponseCommand(ctx, cmd);
break;
default:
break;
}
}
}
上述代码 很清晰, 根据 消息的类型 来选择 对应的 处理方法:
- REQUEST_COMMAND (客户端的请求) :
processRequestCommand(ctx, cmd);
- RESPONSE_COMMAND(客户端的响应):
processResponseCommand(ctx, cmd);
接下来 分别分析 这两个方法。
2. 请求的处理
// 处理客户端发来的请求
public void processRequestCommand(final ChannelHandlerContext ctx, final RemotingCommand cmd) {
// 根据 业务代码 从处理器映射表中 获取合适的 处理器和线程池资源
final Pair<NettyRequestProcessor, ExecutorService> matched = this.processorTable.get(cmd.getCode());
// 若没有找到 code对应的pair, 则使用 默认的defaultRequestProcessor
final Pair<NettyRequestProcessor, ExecutorService> pair = null == matched ? this.defaultRequestProcessor : matched;
// 获取请求Id
final int opaque = cmd.getOpaque();
if (pair != null) {
// 封装一个 runnable 核心逻辑入口... 业务线程..
Runnable run = new Runnable() {
@Override
public void run() {
try {
// RPC Hook before 逻辑...
doBeforeRpcHooks(RemotingHelper.parseChannelRemoteAddr(ctx.channel()), cmd);
// callback 封装响应客户端的逻辑.
final RemotingResponseCallback callback = new RemotingResponseCallback() {
@Override
public void callback(RemotingCommand response) {
// 执行RPC HOOK 的 after 逻辑...
doAfterRpcHooks(RemotingHelper.parseChannelRemoteAddr(ctx.channel()), cmd, response);
// 条件成立: 说明 客户端发来的请求 是需要响应的
if (!cmd.isOnewayRPC()) {
if (response != null) {
// 设置 请求id (客户端根据 该opaque的值 ,在responseFutrueTable 中找到 responseFuture 并完成结果交互)
response.setOpaque(opaque);
// 设置 当前请求为响应类型
response.markResponseType();
try {
// 将数据 交给 Netty IO 线程, 完成数据写和刷
ctx.writeAndFlush(response);
} catch (Throwable e) {
log.error("process request over, but response failed", e);
log.error(cmd.toString());
log.error(response.toString());
}
} else {
}
}
}
};
// nameSrv 使用的是 DefaultRequestProcessor 是 AsyncNettyRequestProcessor的子类
// 因此 该条件会成立
if (pair.getObject1() instanceof AsyncNettyRequestProcessor) {
// 获取 处理器
AsyncNettyRequestProcessor processor = (AsyncNettyRequestProcessor)pair.getObject1();
// 参数1: ctx
// 参数2: cmd 客户端请求对象 remotingCommand
// 参数3: callback 封装响应客户端的回调逻辑.
processor.asyncProcessRequest(ctx, cmd, callback);
} else {
NettyRequestProcessor processor = pair.getObject1();
RemotingCommand response = processor.processRequest(ctx, cmd);
callback.callback(response);
}
} catch (Throwable e) {
log.error("process request exception", e);
log.error(cmd.toString());
if (!cmd.isOnewayRPC()) {
final RemotingCommand response = RemotingCommand.createResponseCommand(RemotingSysResponseCode.SYSTEM_ERROR,
RemotingHelper.exceptionSimpleDesc(e));
response.setOpaque(opaque);
ctx.writeAndFlush(response);
}
}
}
};
// 拒绝策略处理
if (pair.getObject1().rejectRequest()) {
final RemotingCommand response = RemotingCommand.createResponseCommand(RemotingSysResponseCode.SYSTEM_BUSY,
"[REJECTREQUEST]system busy, start flow control for a while");
response.setOpaque(opaque);
ctx.writeAndFlush(response);
return;
}
// 正常走这里 ...
try {
// 将 (runnable 、ch 、 请求cmd) 封装成 RequestTask 对象
final RequestTask requestTask = new RequestTask(run, ctx.channel(), cmd);
// 注意: 这里 IO线程 会将任务提交到 业务线程中了
// pair.getObject2() => 获取处理器对应的线程池
// submit(requestTask) => 将requestTask提交到线程池中, task执行时会执行 runnable的run()方法
pair.getObject2().submit(requestTask);
} catch (RejectedExecutionException e) {
//... 省略
}
} else {
String error = " request type " + cmd.getCode() + " not supported";
final RemotingCommand response =
RemotingCommand.createResponseCommand(RemotingSysResponseCode.REQUEST_CODE_NOT_SUPPORTED, error);
response.setOpaque(opaque);
ctx.writeAndFlush(response);
log.error(RemotingHelper.parseChannelRemoteAddr(ctx.channel()) + error);
}
}
上述代码虽然很长,但是逻辑非常清晰,这里做个小总结:
- 根据 requestCode 从 processorTable 中 获取 Pair , 若找不到则使用 默认的 Pair:
(processor: DefaultReqeustProcessor, executor: RemotingExecutor)
-
封装 核心业务任务 Runnable:
-
封装 请求结果回调 对象 RemotingResponseCallback
-
核心方法: 调用 processor(DefaultReqeustProcessor) 的 asyncProcessRequest 方法
-
将 核心业务任务Runnable 提交给 RemotingExecutor 线程池执行
其中具体的 DefaultReqeustProcessor 是如何 处理请求的,会在下一篇文章中详细分析。
3. 响应的处理
当服务端 向 客户端发送请求后, 客户端会返回响应消息, 则通过该方法来处理器。
public void processResponseCommand(ChannelHandlerContext ctx, RemotingCommand cmd) {
final int opaque = cmd.getOpaque();
final ResponseFuture responseFuture = responseTable.get(opaque);
if (responseFuture != null) {
// 设置客户端 cmd
responseFuture.setResponseCommand(cmd);
// 将 responseFuture 从映射表 中 移除
responseTable.remove(opaque);
if (responseFuture.getInvokeCallback() != null) {
//异步设置future结果
// 回调 对象处理结果
executeInvokeCallback(responseFuture);
} else {
// 同步设置future 结果, 这一步会调用 countDownLatch.countDown(); 将 同步调用的业务线程唤醒
responseFuture.putResponse(cmd);
// 释放信号量
responseFuture.release();
}
} else {
log.warn("receive response, but not matched any request, " + RemotingHelper.parseChannelRemoteAddr(ctx.channel()));
log.warn(cmd.toString());
}
}
该代码 没什么好说的, 主要结合 上一篇《RocketMQ(五) - RemotingServer服务端 请求方法 源码分析》的 调用方法来分析。 其中的功能主要如下:
- 从 responseTable 中 获取并移除对应的 responseFuture对象。
- 对同步调用的响应处理:
- 设置响应结果
countDownLatch.countDown()
唤醒同步调用方法中阻塞的线程
- 对异步调用的响应处理:
- 设置 响应结果
- 释放信号量
- 对单向调用的响应结果: 释放信号量
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
2021-02-22 ConcurrentModificationException 并发修改异常