dubbo异步调用原理 (1)
此文已由作者赵计刚授权网易云社区发布。
欢迎访问网易云社区,了解更多网易技术产品运营经验。
一、使用方式
服务提供方不变,调用方代码如下:
1 <dubbo:reference id="demoService" check="false" interface="com.alibaba.dubbo.demo.DemoService">
2 <dubbo:method name="sayHello" async="true" timeout="60000"/>
3 <dubbo:method name="sayBye" async="true" timeout="60000"/>
4 </dubbo:reference>
配置里添加<dubbo:method name="xxx" async="true"/>,表示单个方法xxx使用异步方式;如果demoService下的所有方法都使用异步,直接配置为<dubbo:reference async="true"/>。
1 public static void main(String[] args) throws Exception {
2 //Prevent to get IPV6 address,this way only work in debug mode
3 //But you can pass use -Djava.net.preferIPv4Stack=true,then it work well whether in debug mode or not
4 System.setProperty("java.net.preferIPv4Stack", "true");
5
6 asyncFuture2();
7 }
8
9 public static void asyncFuture1() throws ExecutionException, InterruptedException {
10 ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"META-INF/spring/dubbo-demo-consumer.xml"});
11 context.start();
12 DemoService demoService = (DemoService) context.getBean("demoService"); // get remote service proxy
13
14 long start = System.currentTimeMillis();
15
16 demoService.sayHello("zhangsan");
17 Future<String> helloFuture = RpcContext.getContext().getFuture();
18
19 demoService.sayBye("lisi");
20 Future<String> byeFuture = RpcContext.getContext().getFuture();
21
22 final String helloStr = helloFuture.get();//消耗5s
23 final String byeStr = byeFuture.get();//消耗8s
24
25 System.out.println(helloStr + " -- " + byeStr + " ,cost:" + (System.currentTimeMillis()-start));//总消耗8s
26 }
27
28 public static void asyncFuture2() throws ExecutionException, InterruptedException {
29 ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"META-INF/spring/dubbo-demo-consumer.xml"});
30 context.start();
31 DemoService demoService = (DemoService) context.getBean("demoService"); // get remote service proxy
32
33 long start = System.currentTimeMillis();
34
35 Future<String> helloFuture = RpcContext.getContext().asyncCall(()-> demoService.sayHello("zhangsan"));
36 Future<String> byeFuture = RpcContext.getContext().asyncCall(()->demoService.sayBye("lisi"));
37
38 final String helloStr = helloFuture.get();//消耗5s
39 final String byeStr = byeFuture.get();//消耗8s
40
41 System.out.println(helloStr + " -- " + byeStr + " ,cost:" + (System.currentTimeMillis()-start));//总消耗8s
42 }
Consumer启动主类。其中asyncFuture2()方法是推荐用法,注意Callable(asyncCall方法的入参)只是一个任务task,不会新建线程;所以asyncFuture2()和asyncFuture1()相似,资源占用相同,都是用一根线程进行异步操作的。
二、asyncFuture1()源码解析
先来看asyncFuture1(),总体步骤:
demoService.sayHello("zhangsan"); 创建一个Future对象,存入当前线程的上下文中
Future<String> helloFuture = RpcContext.getContext().getFuture(); 从当前线程的上下文中获取第一步存入的Future对象
final String helloStr = helloFuture.get(); 阻塞等待,从Future中获取结果
代码主要执行流(代码详细执行流看文章开头的三篇博客):
1、demoService.sayHello("zhangsan");
-->FutureFilter.invoke(final Invoker<?> invoker, final Invocation invocation)
-->DubboInvoker.doInvoke(final Invocation invocation)
FutureFilter:
1 public Result invoke(final Invoker<?> invoker, final Invocation invocation) throws RpcException {
2 final boolean isAsync = RpcUtils.isAsync(invoker.getUrl(), invocation);
3
4 fireInvokeCallback(invoker, invocation);
5 // need to configure if there's return value before the invocation in order to help invoker to judge if it's
6 // necessary to return future.
7 Result result = invoker.invoke(invocation);
8 if (isAsync) {
9 asyncCallback(invoker, invocation);
10 } else {
11 syncCallback(invoker, invocation, result);
12 }
13 return result;
14 }
对于如上异步操作(asyncFuture1()和asyncFuture2()),FutureFilter没起任何作用,该Filter主要会用在事件通知中,后续再说。
DubboInvoker.doInvoke(final Invocation invocation):
1 protected Result doInvoke(final Invocation invocation) throws Throwable {
2 RpcInvocation inv = (RpcInvocation) invocation; 3 final String methodName = RpcUtils.getMethodName(invocation);
4 inv.setAttachment(Constants.PATH_KEY, getUrl().getPath());
5 inv.setAttachment(Constants.VERSION_KEY, version);
6
7 ExchangeClient currentClient;
8 if (clients.length == 1) {
9 currentClient = clients[0];
10 } else {
11 currentClient = clients[index.getAndIncrement() % clients.length];
12 }
13 try {
14 boolean isAsync = RpcUtils.isAsync(getUrl(), invocation);
15 boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);
16 int timeout = getUrl().getMethodParameter(methodName, Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
17 if (isOneway) { //无返回值
18 boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false);
19 currentClient.send(inv, isSent);
20 RpcContext.getContext().setFuture(null);
21 return new RpcResult();
22 } else if (isAsync) { //异步有返回值
23 ResponseFuture future = currentClient.request(inv, timeout);
24 RpcContext.getContext().setFuture(new FutureAdapter<Object>(future));
25 return new RpcResult();
26 } else { //同步有返回值
27 RpcContext.getContext().setFuture(null);
28 return (Result) currentClient.request(inv, timeout).get();
29 }
30 } catch (TimeoutException e) {
31 throw new RpcException(RpcException.TIMEOUT_EXCEPTION, "Invoke remote method timeout. method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
32 } catch (RemotingException e) {
33 throw new RpcException(RpcException.NETWORK_EXCEPTION, "Failed to invoke remote method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
34 }
35 }
模式:
如果是isOneway(不需要返回值),不管同步还是异步,请求直接发出,不会创建Future,直接返回RpcResult空对象。
如果是isAsync(异步),则
先创建ResponseFuture对象,之后使用FutureAdapter包装该ResponseFuture对象;(创建ResponseFuture对象与同步的代码相同,最后得到的是一个DefaultFuture对象)
然后将该FutureAdapter对象设入当前线程的上下文中RpcContext.getContext();
最后返回空的RpcResult
如果是同步,则先创建ResponseFuture对象,之后直接调用其get()方法进行阻塞调用(见文章开头的三篇文章)
简单来看一下FutureAdapter:
1 public class FutureAdapter<V> implements Future<V> {
2
3 private final ResponseFuture future;
4
5 public FutureAdapter(ResponseFuture future) {
6 this.future = future;
7 }
8
9 public ResponseFuture getFuture() {
10 return future;
11 }
12
13 public boolean cancel(boolean mayInterruptIfRunning) {
14 return false;
15 }
16
17 public boolean isCancelled() {
18 return false;
19 }
20
21 public boolean isDone() {
22 return future.isDone();
23 }
24
25 @SuppressWarnings("unchecked")
26 public V get() throws InterruptedException, ExecutionException {
27 try {
28 return (V) (((Result) future.get()).recreate());
29 } catch (RemotingException e) {
30 throw new ExecutionException(e.getMessage(), e);
31 } catch (Throwable e) {
32 throw new RpcException(e);
33 }
34 }
35
36 @SuppressWarnings("unchecked")
37 public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
38 int timeoutInMillis = (int) unit.convert(timeout, TimeUnit.MILLISECONDS);
39 try {
40 return (V) (((Result) future.get(timeoutInMillis)).recreate());
41 } catch (com.alibaba.dubbo.remoting.TimeoutException e) {
42 throw new TimeoutException(StringUtils.toString(e));
43 } catch (RemotingException e) {
44 throw new ExecutionException(e.getMessage(), e);
45 } catch (Throwable e) {
46 throw new RpcException(e);
47 }
48 }
49 }
最后,回头看一下FutureFilter:
1 public Result invoke(final Invoker<?> invoker, final Invocation invocation) throws RpcException {
2 final boolean isAsync = RpcUtils.isAsync(invoker.getUrl(), invocation);
3
4 fireInvokeCallback(invoker, invocation);
5 // need to configure if there's return value before the invocation in order to help invoker to judge if it's
6 // necessary to return future.
7 Result result = invoker.invoke(invocation);
8 if (isAsync) {
9 asyncCallback(invoker, invocation);
10 } else {
11 syncCallback(invoker, invocation, result);
12 }
13 return result;
14 }
更多网易技术、产品、运营经验分享请点击。
相关文章:
【推荐】 使用QUIC
【推荐】 数据库路由中间件MyCat - 源代码篇(5)
dubbo异步调用原理 (1)的更多相关文章
- 9.4 dubbo异步调用原理
9.1 客户端发起请求源码.9.2 服务端接收请求消息并发送响应消息源码.9.3 客户端接收响应信息(异步转同步的实现) 分析了dubbo同步调用的源码,现在来看一下dubbo异步调用. 一.使用方式 ...
- Spring异步调用原理及SpringAop拦截器链原理
一.Spring异步调用底层原理 开启异步调用只需一个注解@EnableAsync @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTI ...
- 限时购校验小工具&dubbo异步调用实现限
本文来自网易云社区 作者:张伟 背景 限时购是网易考拉目前比较常用的促销形式,但是前期创建一个限时购活动时需要各个BU按照指定的Excel格式进行选品提报,为了保证提报数据准确,运营需要人肉校验很多信 ...
- 抓到Dubbo异步调用的小BUG,再送你一个贡献开源代码的机会
hello,大家好呀,我是小楼. 最近一个技术群有同学at我,问我是否熟悉Dubbo,这我熟啊~ 他说遇到了一个Dubbo异步调用的问题,怀疑是个BUG,提到BUG我可就不困了,说不定可以水,哦不.. ...
- Direct3D Draw函数 异步调用原理解析
概述 在D3D10中,一个基本的渲染流程可分为以下步骤: 清理帧缓存: 执行若干次的绘制: 通过Device API创建所需Buffer: 通过Map/Unmap填充数据到Buffer中: 将Buff ...
- 【转】Zookeeper-Watcher机制与异步调用原理
声明:本文转载自http://shift-alt-ctrl.iteye.com/blog/1847320,转载请务必声明. Watcher机制:目的是为ZK客户端操作提供一种类似于异步获得数据的操作. ...
- Zookeeper-Watcher机制与异步调用原理
转载于:http://shift-alt-ctrl.iteye.com/blog/1847320 Watcher机制:目的是为ZK客户端操作提供一种类似于异步获得数据的操作. 1)在创建Zookeep ...
- dubbo异步调用三种方式
异步通讯对于服务端响应时间较长的方法是必须的,能够有效地利用客户端的资源,在dubbo中,消费端<dubbp:method>通过 async="true"标识. < ...
- dubbo同步调用、异步调用和是否返回结果源码分析和实例
0. dubbo同步调用.异步调用和是否返回结果配置 (1)dubbo默认为同步调用,并且有返回结果. (2)dubbo异步调用配置,设置 async="true",异步调用可以提 ...
随机推荐
- Operating System-进程/线程内部通信-信号量、PV操作的实现和应用(解决哲学家进餐和生产者消费者问题)
本文主要内容: 信号量的实现 利用信号量解决哲学家用餐问题 利用信号量解决生产者消费者问题 一.信号量的实现 1.1 信号量结构 typedef struct { int value; struct ...
- Mesos-slave启动处理记录
1. work_dir错误导致启动异常 /etc/mesos-slave/work_dir设置的目录不存在或者权限不够将会导致启动异常. 2. 根据日志信息以及status信息来判断问题 通过serv ...
- Linux网络编程——tcp并发服务器(poll实现)
想详细彻底地了解poll或看懂下面的代码请参考<Linux网络编程——I/O复用之poll函数> 代码: #include <string.h> #include <st ...
- 用PowerShell在China Azure创建ARM虚拟机
Azure目前有两种工作模式:ASM和ARM. 在国内的Azure,我们都是使用ASM的模式.但这种模式有很多限制,比如每个VM必须有一个公网地址,部署不能批量部署等等.ARM对Azure的整体架构做 ...
- Python numpy函数:transpose()
transpose用于对高维数组进行转置,转置时候需要一个由轴编号组成的元组. 比如说三维的数组,那就对维度进行编号,也就是0,1,2:这样说可能比较抽象.这里的0,1,2可以理解为对shape返回元 ...
- (转)heX——基于 HTML5 和 Node.JS 开发桌面应用
本文转载自:http://techblog.youdao.com/?p=685 简介:heX,一个允许你采用前端技术(HTML,CSS,JavaScript)开发桌面应用软件的跨平台解决方案.是你开发 ...
- Day3-Python基础3--函数参数及调用
一.return返回值 return的两个作用: 1)需要用一个变量来接受程序结束后返回的结果 2)它是作为一个结束符,终止程序运行 def test(): print("我是return前 ...
- mina在spring中的配置多个端口
本次练习中是监听2个端口 applicationContext-mina.xml: <?xml version="1.0" encoding="UTF-8" ...
- 杂项:C# 方法、属性杂项-01
ylbtech-杂项:C# 方法.属性杂项-01 1. 属性杂项返回顶部 1. public int ReadCnt { get; set; } 2.设置默认值 public int ReadCnt ...
- C# WinForm 关闭登陆窗体后进程还再内存怎么办?
问题:我们通常再制作WinForm应用程序的时候,运行程序的第一个窗口一般是登陆窗口.代码如下: 那么这种方式有一个弊端,这种启动方式,其实就是把登陆窗口设置为主窗体.因此,再登陆后,我们通常是调用H ...