RocketMQ(五) - RemotingServer服务端 请求调用方法 源码分析
RocketMQ(五) - RemotingServer服务端 请求调用方法 源码分析
上一篇 《RocketMQ(三) - RemotingServer服务端启动 》 中 分析了 NettyRemotingServer 的启动源码,同时 也对其 继承接口 RemotingServer方法做了简单的注释说明,本篇主要详细 分析其中 三个请求方法 在NettyRemotingServer 中的实现细节。
先回顾下 三个 请求调用方法 , 分别如下:
/**
* 1. 同步调用
* @param channel 通信通道
* @param request 业务请求对象
* @param timeoutMillis 超时时间
* @return 响应结果封装
*/
RemotingCommand invokeSync(final Channel channel, final RemotingCommand request,
final long timeoutMillis) throws InterruptedException, RemotingSendRequestException,RemotingTimeoutException;
/**
* 2. 异步调用
* @param channel 通信通道
* @param request 业务请求对象
* @param timeoutMillis 超时时间
* @param invokeCallback 响应结果回调对象
*/
void invokeAsync(final Channel channel, final RemotingCommand request, final long timeoutMillis,
final InvokeCallback invokeCallback) throws InterruptedException,
RemotingTooMuchRequestException, RemotingTimeoutException, RemotingSendRequestException;
/**
* 3. 单向调用 (不关注返回结果)
* @param channel 通信通道
* @param request 业务请求对象
* @param timeoutMillis 超时时间
*/
void invokeOneway(final Channel channel, final RemotingCommand request, final long timeoutMillis)
throws InterruptedException, RemotingTooMuchRequestException, RemotingTimeoutException,RemotingSendRequestException;
注意 本篇讲述的 这三个 调用 是建立在 服务端 主动向 客户端 发送请求的 情况下。
在 NettyRemotingServer中的 三个请求调用方法 实际上都是 又一次调用了 其父类 NettyRemotingAbstract 的 对应方法, 因此 我们直接来看 NettyRemotingAbstract 类中 这三个方法的具体实现。
1.同步调用
/**
* 服务器 主动向客户端 发起请求时 使用的方法。(当前方法 同步调用)
* 什么是同步调用?
* 服务器业务线程 需要在这里 等待client 返回结果之后 整个调用才完毕
* @param channel 客户端ch
* @param request 网络请求对象 remotingCommand
* @param timeoutMillis 超时时长
*/
public RemotingCommand invokeSyncImpl(final Channel channel, final RemotingCommand request,
final long timeoutMillis)
throws InterruptedException, RemotingSendRequestException, RemotingTimeoutException {
// 获取请求ID Opaque
final int opaque = request.getOpaque();
try {
// 封装 响应结果 对象 (之后请求的返回结果会 存入该对象中)
// 参数1: 客户端ch
// 参数2: 请求ID
// 参数3: 超时时间
final ResponseFuture responseFuture = new ResponseFuture(channel, opaque, timeoutMillis, null, null);
// 加入到 映射表 内
// key: opaque value: 响应结果对象
this.responseTable.put(opaque, responseFuture);
// 获取 客户端 地址信息
final SocketAddress addr = channel.remoteAddress();
// 将数据 写入 客户端 ch, 并设置个 回调监听器
channel.writeAndFlush(request).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture f) throws Exception {
//条件成立: 请求写成功
if (f.isSuccess()) {
// 设置 请求写状态 ok
responseFuture.setSendRequestOK(true);
return;
}
// 条件成立: 请求写失败
else {
// 设置 请求写状态 false
responseFuture.setSendRequestOK(false);
}
// 写失败 才会走到这...
// 将当前请求的 responseFuture 从映射表 移除
responseTable.remove(opaque);
// 设置失败原因..
responseFuture.setCause(f.cause());
responseFuture.putResponse(null);
log.warn("send a request command to channel <" + addr + "> failed.");
}
});
// 同步调用的关键代码
// 业务线程 在这里 进入挂起状态... => countDownLatch.await()
RemotingCommand responseCommand = responseFuture.waitResponse(timeoutMillis);
// 线程执行到这,有两种情况
// 1. 正常情况, 客户端 返回数据了, IO线程 将业务线程 唤醒
// 2. 超时
if (null == responseCommand) {
// 执行进这里, 说明 超时 或 其它异常情况..
if (responseFuture.isSendRequestOK()) {
throw new RemotingTimeoutException(RemotingHelper.parseSocketAddressAddr(addr), timeoutMillis,
responseFuture.getCause());
} else {
throw new RemotingSendRequestException(RemotingHelper.parseSocketAddressAddr(addr), responseFuture.getCause());
}
}
// 正常.. 这里返回 客户端的 请求结果。
return responseCommand;
} finally {
// 从 响应结果映射表 移除该 响应结果对象
this.responseTable.remove(opaque);
}
}
2.异步调用
/**
* 服务器 主动 向客户端 发起请求时, 使用的方法 (异步)
* @param channel 客户端ch
* @param request 网络请求对象 remotingCommand
* @param timeoutMillis 超时时长
* @param invokeCallback 请求结果回调处理对象
*/
public void invokeAsyncImpl(final Channel channel, final RemotingCommand request, final long timeoutMillis,
final InvokeCallback invokeCallback)
throws InterruptedException, RemotingTooMuchRequestException, RemotingTimeoutException, RemotingSendRequestException {
// 获取 开始时间
long beginStartTime = System.currentTimeMillis();
// 请求ID
final int opaque = request.getOpaque();
// 获取信号量
boolean acquired = this.semaphoreAsync.tryAcquire(timeoutMillis, TimeUnit.MILLISECONDS);
if (acquired) {
// 执行到这, 说明抢到了信号量, 当前线程可以发起请求, 服务器请求客户端的 并发 ,并未达到上限
// once 对象 封装了 释放信号量的操作
final SemaphoreReleaseOnlyOnce once = new SemaphoreReleaseOnlyOnce(this.semaphoreAsync);
// 计算 到这一步为止, 耗费的时间
long costTime = System.currentTimeMillis() - beginStartTime;
// 条件成立: 说明 已经超时了.. 就不用 再发起RPC了
if (timeoutMillis < costTime) {
once.release();
throw new RemotingTimeoutException("invokeAsyncImpl call timeout");
}
// 参数1: 客户端ch
// 参数2: 请求ID
// 参数3: 剩余的超时时间
// 参数4: 回调处理对象
// 参数5: 信号量释放操作封装对象
final ResponseFuture responseFuture = new ResponseFuture(channel, opaque, timeoutMillis - costTime, invokeCallback, once);
// 将 responseFuture 加入 响应 映射表,key 其实是 RequestId
this.responseTable.put(opaque, responseFuture);
try {
// 1. 业务线程 将数据 交给netty, netty IO线程 接管 写 和 刷 数据的操作。
// 2. 注册 写 刷 操作的 监听器。 监听器由IO线程回调
channel.writeAndFlush(request).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture f) throws Exception {
if (f.isSuccess()) {
// 写刷 成功 ,设置 responseFutrue 发生状态为 true
responseFuture.setSendRequestOK(true);
return;
}
// 执行到这, 发送失败
requestFail(opaque);
log.warn("send a request command to channel <{}> failed.", RemotingHelper.parseChannelRemoteAddr(channel));
}
});
} catch (Exception e) {
responseFuture.release();
log.warn("send a request command to channel <" + RemotingHelper.parseChannelRemoteAddr(channel) + "> Exception", e);
throw new RemotingSendRequestException(RemotingHelper.parseChannelRemoteAddr(channel), e);
}
} else {
// 执行到这, 说明获取 信号量失败, 说明当前并发高
if (timeoutMillis <= 0) {
throw new RemotingTooMuchRequestException("invokeAsyncImpl invoke too fast");
} else {
String info =
String.format("invokeAsyncImpl tryAcquire semaphore timeout, %dms, waiting thread nums: %d semaphoreAsyncValue: %d",
timeoutMillis,
this.semaphoreAsync.getQueueLength(),
this.semaphoreAsync.availablePermits()
);
log.warn(info);
throw new RemotingTimeoutException(info);
}
}
}
3.单向调用
/**
* 服务器 主动向 客户端 发起请求时,(此方法 不关注结果 单向请求)
* @param channel 客户端ch
* @param request 网络请求对象 remotingCommand
* @param timeoutMillis 超时时长
*/
public void invokeOnewayImpl(final Channel channel, final RemotingCommand request, final long timeoutMillis)
throws InterruptedException, RemotingTooMuchRequestException, RemotingTimeoutException, RemotingSendRequestException {
// 设置 标记, 对端 检查标记 就可以知道 这个请求是一个单向请求
request.markOnewayRPC();
// 申请信号量
boolean acquired = this.semaphoreOneway.tryAcquire(timeoutMillis, TimeUnit.MILLISECONDS);
if (acquired) {
// 释放信号量逻辑的封装对象
final SemaphoreReleaseOnlyOnce once = new SemaphoreReleaseOnlyOnce(this.semaphoreOneway);
try {
// 1. 将数据交给ch, 这里数据处理发送的逻辑 由netty 线程完成
// 2. 添加 写刷数据操作的 监听器。 由Netty IO线程回调
channel.writeAndFlush(request).addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture f) throws Exception {
// 释放信号量
once.release();
if (!f.isSuccess()) {
log.warn("send a request command to channel <" + channel.remoteAddress() + "> failed.");
}
}
});
} catch (Exception e) {
once.release();
log.warn("write send a request command to channel <" + channel.remoteAddress() + "> failed.");
throw new RemotingSendRequestException(RemotingHelper.parseChannelRemoteAddr(channel), e);
}
}
// 条件成立: 说明并发高,达到了限制
else {
if (timeoutMillis <= 0) {
throw new RemotingTooMuchRequestException("invokeOnewayImpl invoke too fast");
} else {
String info = String.format(
"invokeOnewayImpl tryAcquire semaphore timeout, %dms, waiting thread nums: %d semaphoreAsyncValue: %d",
timeoutMillis,
this.semaphoreOneway.getQueueLength(),
this.semaphoreOneway.availablePermits()
);
log.warn(info);
throw new RemotingTimeoutException(info);
}
}
}
4. 总结
NettyRemotingServer 三个 请求调用的方法 很简单, 没什么好说的。 这里总结下:
方法 | 是否关注响应结果 | 是否又并发限制 |
---|---|---|
invokeSyncImpl | 关注 | 无并发限制 |
invokeAsyncImpl | 关注 | 有并发限制 |
invokeOnewayImpl | 不关注 | 有并发限制 |
其中注意的是:
- 关注结果, 实际上就是将 对象 封装成 ResponseFuture 对象
- 同步调用, 就是利用 ResponseFuture 对象中的 countDownLatch 实现的
- 并发限制,就是通过控制Semaphore 实现的
万般皆下品,唯有读书高!
【推荐】国内首个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 并发修改异常