RPC-非阻塞通信下的同步API实现原理,以Dubbo为例
Netty在Java NIO领域基本算是独占鳌头,涉及到高性能网络通信,基本都会以Netty为底层通信框架,Dubbo 也不例外。以下将以Dubbo实现为例介绍其是如何在NIO非阻塞通信基础上实现同步通信的。
Dubbo为一种RPC通信框架,提供进程间的通信,在使用dubbo协议+Netty作为传输层时,提供三种API调用方式:
- 同步接口
- 异步带回调接口
- 异步不带回调接口
同步接口适用在大部分环境,通信方式简单、可靠,客户端发起调用,等待服务端处理,调用结果同步返回。这种方式下,在高吞吐、高性能(响应时间很快)的服务接口场景中最为适用,可以减少异步带来的额外的消耗,也方便客户端做一致性保证。

异步带回调接口,用在任务处理时间较长,客户端应用线程不愿阻塞等待,而是为了提高自身处理能力希望服务端处理完成后可以异步通知应用线程。这种方式可以大大提升客户端的吞吐量,避免因为服务端的耗时问题拖死客户端。

异步不带回调接口,一些场景为了进一步提升客户端的吞吐能力,只需发起一次服务端调用,不需关系调用结果,可以使用此种通信方式。一般在不需要严格保证数据一致性或者有其他补偿措施的情况下,选用这种,可以最小化远程调用带来的性能损耗。

来看一下Dubbo是如何实现这三种API的。核心代码在com.alibaba.dubbo.rpc.protocol.dubbo.DubboInvoker,如下图对应的位置,属于协议层的实现部分。为方便大家可以准确定位代码所在位置,使用截图的方式,而不是直接贴代码了。

上文描述的是三种API方式,Dubbo里面通过参数isOneway、isAsync来控制,isOneway=true表示异步不带回调,isAsync=true表示异步带回调,否则是同步API。具体是如何控制,看以下代码:

isOneway==true时,客户端send完请求后,直接return一个空结果的RpcResult;isAsync==true时,客户端发起请求,设置一个ResponseFuture,直接return一个空结果的RpcResult,接下来当服务端处理完成,客户端Netty层在收到响应后会通过Future通知应用线程;最后是同步情况下,客户端发起请求,并通过get()方法阻塞等待服务端的响应结果。
异步API情况下,结合NIO模型比较好理解是如何实现的(当然需要先了解NIO的reactor模型),接下来重点理解下,这个get()阻塞方法是如何做到基于非阻塞NIO实现同步阻塞效果。
直接进入get()方法内部。

可以看到是利用Java的锁机制实现,循环判断是否收到响应,如果收到或者等待超时则返回。done的实例对象如下:
private final Lock lock = new ReentrantLock();
private final Condition done = lock.newCondition();
使用可重入锁ReentrantLock,获取一个Condition对象在其上做await操作。这里有await操作,何时被唤醒呢,有两个条件,第一个是等待timeout超时,默认dubbo是1s,第二个就是被其他线程唤醒,即收到了服务端的响应。

signal信号一发出,上文循环检测内的await操作会立即返回,下一次isDone判断会变成true,直接跳出循环。
仔细看代码会发现,被唤醒的地方还有一个是在DefaultFuture内部有一个超时轮询检测的线程,这个线程主要是处理响应超时后触发资源回收、记录异常日志等操作。
private static class RemotingInvocationTimeoutScan implements Runnable {
public void run() {
while (true) {
try {
for (DefaultFuture future : FUTURES.values()) {
if (future == null || future.isDone()) {
continue;
}
if (System.currentTimeMillis() - future.getStartTimestamp() > future.getTimeout()) {
// create exception response.
Response timeoutResponse = new Response(future.getId());
// set timeout status.
timeoutResponse.setStatus(future.isSent() ? Response.SERVER_TIMEOUT : Response.CLIENT_TIMEOUT);
timeoutResponse.setErrorMessage(future.getTimeoutMessage(true));
// handle response.
DefaultFuture.received(future.getChannel(), timeoutResponse);
}
}
Thread.sleep(30);
} catch (Throwable e) {
logger.error("Exception when scan the timeout invocation of remoting.", e);
}
}
}
}
static {
Thread th = new Thread(new RemotingInvocationTimeoutScan(), "DubboResponseTimeoutScanTimer");
th.setDaemon(true);
th.start();
}
可能会有疑问,这个触发操作为何不直接在get()方法内部检测到超时直接调用DefaultFuture.received(Channel channel, Response response)来清理,而是要额外开启一个后台线程。
单独启动一个超时线程有两个好处:
- 提高超时精度
get()方法内部的轮询有一个timeout,每次超时唤醒的时间间隔至少是timeout时长,最差的情况可能会等待2*timeout作出超时反应。在超时轮询线程中,每隔30ms遍历检测一次,可以很大程度的提升超时精度。
2. 提升性能,降低响应时间
剥离超时处理逻辑到一个单独线程,可以减少对业务线程的时间占用,这个超时后的处理对应用来说并无直接作用,完全可以放到后台异步去处理。另外单独在一个线程中,实际上有批量处理的表现。
以上是就NIO通信基础上实现三种API调用的实现原理,或许有更多优于Dubbo的处理方式,可以拿出来讨论。
RPC-非阻塞通信下的同步API实现原理,以Dubbo为例的更多相关文章
- 【MPI学习4】MPI并行程序设计模式:非阻塞通信MPI程序设计
这一章讲了MPI非阻塞通信的原理和一些函数接口,最后再用非阻塞通信方式实现Jacobi迭代,记录学习中的一些知识. (1)阻塞通信与非阻塞通信 阻塞通信调用时,整个程序只能执行通信相关的内容,而无法执 ...
- 用Java实现非阻塞通信
用ServerSocket和Socket来编写服务器程序和客户程序,是Java网络编程的最基本的方式.这些服务器程序或客户程序在运行过程中常常会阻塞.例如当一个线程执行ServerSocket的acc ...
- 利用Python中SocketServer 实现客户端与服务器间非阻塞通信
利用SocketServer模块来实现网络客户端与服务器并发连接非阻塞通信 版权声明 本文转自:http://blog.csdn.net/cnmilan/article/details/9664823 ...
- 关于socket阻塞与非阻塞情况下的recv、send、read、write返回值---部分内容可能不确切,待讨论
1.阻塞模式与非阻塞模式下recv的返回值各代表什么意思?有没有区别?(就我目前了解阻塞与非阻塞recv返回值没有区分,都是 <0:出错,=0:连接关闭,>0接收到数据大小,特别:返回值 ...
- 【python】网络编程-SocketServer 实现客户端与服务器间非阻塞通信
利用SocketServer模块来实现网络客户端与服务器并发连接非阻塞通信.首先,先了解下SocketServer模块中可供使用的类:BaseServer:包含服务器的核心功能与混合(mix-in)类 ...
- Java NIO Socket 非阻塞通信
相对于非阻塞通信的复杂性,通常客户端并不需要使用非阻塞通信以提高性能,故这里只有服务端使用非阻塞通信方式实现 客户端: package com.test.client; import java.io. ...
- UE4 Socket多线程非阻塞通信
转自:https://blog.csdn.net/lunweiwangxi3/article/details/50468593 ue4自带的Fsocket用起来依旧不是那么的顺手,感觉超出了我的理解范 ...
- TCP非阻塞通信
一.SelectableChannel SelectableChannel支持阻塞和非阻塞模式的channel 非阻塞模式下的SelectableChannel,读写不会阻塞 SelectableCh ...
- socket异步通信-如何设置成非阻塞模式、非阻塞模式下判断connect成功(失败)、判断recv/recvfrom成功(失败)、判断send/sendto
socket异步通信-如何设置成非阻塞模式.非阻塞模式下判断connect成功(失败).判断recv/recvfrom成功(失败).判断send/sendto 博客分类: Linux Socket s ...
随机推荐
- JeeSite如何正确连接SQL SERVER 数据库
JeeSite如何正确连接SQL SERVER 数据库 jeesite介绍 感谢jeesite项目的作者thinkgem. 没有你我也不会更改这数据源非了恁大的劲,,,,嘻嘻嘻说多了. JeeSite ...
- OC学习14——谓词
一.谓词的基本概念与使用 1.谓词(NSPredicate)用于定义一个逻辑条件,通过该条件可执行搜索或内存中的过滤操作.上一篇文章中介绍的集合都提供了使用谓词对集合进行过滤的方法.OC中的谓词操作是 ...
- ArcGIS API for JavaScript 4.2学习笔记[15] 弹窗内容的格式与自定义格式
先看结果截图吧(不看过程可以直接看总结,在文末): 随便点击了两个城市斑块,出现结果如图. 我来解读一下这结果和以前的有什么不同: 这个例子使用了PopupTemplate,数据是Layer(使用Po ...
- BZOJ 4816 数字表格
首先是惯例的吐槽.SDOI题目名称是一个循环,题目内容也是一个循环,基本上过几年就把之前的题目换成另一个名字出出来,喜大普奔亦可赛艇.学长说考SDOI可以考出联赛分数,%%%. 下面放解题报告.并不喜 ...
- 一道叉姐的AC自动机鬼题
题面描述丢失了... 给n个串模板串,然后再给你m个串,对于这m个串的每个串,问在[L,R]的模板串中,在多少个串中出现过; 这题的正解是对于后m个串建AC自动机,然后离线,在fail树上树链求并. ...
- laravel框架一种方便的快速填充数据的方法
首先大家都知道在laravel框架里是采用seeder来填充数据的,具体命令如下,请将如下的类名称替换成你具体的seeder类名. 首先创建seeder类 php artisan make:seede ...
- useradd 命令详解
useradd 作用: 用于Linux中创建的新的系统用户, useradd 可用来建立用户账号, 账号建好之后,再用passwd 设定账号的密码, 可用userdel 删除账号. 使用useradd ...
- MySQL Replication 主从复制全方位解决方案
1.1 主从复制基础概念 在了解主从复制之前必须要了解的就是数据库的二进制日志(binlog),主从复制架构大多基于二进制日志进行,二进制日志相关信息参考:http://www.cnblogs.com ...
- ConcurrentHashMap 从Java7 到 Java8的改变
一.关于分段锁 集合框架很大程度减少了java程序员的重复劳动,然而,在Java多线程环境中,以线程安全的方式使用集合类是一个首先考虑的问题. 越来越多的程序员了解到了ConcurrentHashMa ...
- (通用)深度学习环境搭建:tensorflow安装教程及常见错误解决
区别于其他入门教程的"手把手式",本文更强调"因"而非"果".我之所以加上"通用"字样,是因为在你了解了这个开发环境之后 ...