9.1 客户端发起请求源码9.2 服务端接收请求消息并发送响应消息源码9.3 客户端接收响应信息(异步转同步的实现) 分析了dubbo同步调用的源码,现在来看一下dubbo异步调用。

一、使用方式

服务提供方不变,调用方代码如下:

     <dubbo:reference id="demoService" check="false" interface="com.alibaba.dubbo.demo.DemoService">
<dubbo:method name="sayHello" async="true" timeout="60000"/>
<dubbo:method name="sayBye" async="true" timeout="60000"/>
</dubbo:reference>

配置里添加<dubbo:method name="xxx" async="true"/>,表示单个方法xxx使用异步方式;如果demoService下的所有方法都使用异步,直接配置为<dubbo:reference async="true"/>。

     public static void main(String[] args) throws Exception {
//Prevent to get IPV6 address,this way only work in debug mode
//But you can pass use -Djava.net.preferIPv4Stack=true,then it work well whether in debug mode or not
System.setProperty("java.net.preferIPv4Stack", "true"); asyncFuture2();
} public static void asyncFuture1() throws ExecutionException, InterruptedException {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"META-INF/spring/dubbo-demo-consumer.xml"});
context.start();
DemoService demoService = (DemoService) context.getBean("demoService"); // get remote service proxy long start = System.currentTimeMillis(); demoService.sayHello("zhangsan");
Future<String> helloFuture = RpcContext.getContext().getFuture(); demoService.sayBye("lisi");
Future<String> byeFuture = RpcContext.getContext().getFuture(); final String helloStr = helloFuture.get();//消耗5s
final String byeStr = byeFuture.get();//消耗8s System.out.println(helloStr + " -- " + byeStr + " ,cost:" + (System.currentTimeMillis()-start));//总消耗8s
} public static void asyncFuture2() throws ExecutionException, InterruptedException {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"META-INF/spring/dubbo-demo-consumer.xml"});
context.start();
DemoService demoService = (DemoService) context.getBean("demoService"); // get remote service proxy long start = System.currentTimeMillis(); Future<String> helloFuture = RpcContext.getContext().asyncCall(()-> demoService.sayHello("zhangsan"));
Future<String> byeFuture = RpcContext.getContext().asyncCall(()->demoService.sayBye("lisi")); final String helloStr = helloFuture.get();//消耗5s
final String byeStr = byeFuture.get();//消耗8s System.out.println(helloStr + " -- " + byeStr + " ,cost:" + (System.currentTimeMillis()-start));//总消耗8s
}

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:

     public Result invoke(final Invoker<?> invoker, final Invocation invocation) throws RpcException {
final boolean isAsync = RpcUtils.isAsync(invoker.getUrl(), invocation); fireInvokeCallback(invoker, invocation);
// need to configure if there's return value before the invocation in order to help invoker to judge if it's
// necessary to return future.
Result result = invoker.invoke(invocation);
if (isAsync) {
asyncCallback(invoker, invocation);
} else {
syncCallback(invoker, invocation, result);
}
return result;
}

对于如上异步操作(asyncFuture1()和asyncFuture2()),FutureFilter没起任何作用,该Filter主要会用在事件通知中,后续再说。

DubboInvoker.doInvoke(final Invocation invocation):

     protected Result doInvoke(final Invocation invocation) throws Throwable {
RpcInvocation inv = (RpcInvocation) invocation;
final String methodName = RpcUtils.getMethodName(invocation);
inv.setAttachment(Constants.PATH_KEY, getUrl().getPath());
inv.setAttachment(Constants.VERSION_KEY, version); ExchangeClient currentClient;
if (clients.length == 1) {
currentClient = clients[0];
} else {
currentClient = clients[index.getAndIncrement() % clients.length];
}
try {
boolean isAsync = RpcUtils.isAsync(getUrl(), invocation);
boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);
int timeout = getUrl().getMethodParameter(methodName, Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);
if (isOneway) { //无返回值
boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false);
currentClient.send(inv, isSent);
RpcContext.getContext().setFuture(null);
return new RpcResult();
} else if (isAsync) { //异步有返回值
ResponseFuture future = currentClient.request(inv, timeout);
RpcContext.getContext().setFuture(new FutureAdapter<Object>(future));
return new RpcResult();
} else { //同步有返回值
RpcContext.getContext().setFuture(null);
return (Result) currentClient.request(inv, timeout).get();
}
} catch (TimeoutException e) {
throw new RpcException(RpcException.TIMEOUT_EXCEPTION, "Invoke remote method timeout. method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
} catch (RemotingException e) {
throw new RpcException(RpcException.NETWORK_EXCEPTION, "Failed to invoke remote method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);
}
}

模式:

  • 如果是isOneway(不需要返回值),不管同步还是异步,请求直接发出,不会创建Future,直接返回RpcResult空对象。
  • 如果是isAsync(异步),则
    • 先创建ResponseFuture对象,之后使用FutureAdapter包装该ResponseFuture对象;(创建ResponseFuture对象与同步的代码相同,最后得到的是一个DefaultFuture对象)
    • 然后将该FutureAdapter对象设入当前线程的上下文中RpcContext.getContext();
    • 最后返回空的RpcResult
  • 如果是同步,则先创建ResponseFuture对象,之后直接调用其get()方法进行阻塞调用(见文章开头的三篇文章)

简单来看一下FutureAdapter:

 public class FutureAdapter<V> implements Future<V> {

     private final ResponseFuture future;

     public FutureAdapter(ResponseFuture future) {
this.future = future;
} public ResponseFuture getFuture() {
return future;
} public boolean cancel(boolean mayInterruptIfRunning) {
return false;
} public boolean isCancelled() {
return false;
} public boolean isDone() {
22 return future.isDone();
23 } @SuppressWarnings("unchecked")
public V get() throws InterruptedException, ExecutionException {
try {
return (V) (((Result) future.get()).recreate());
} catch (RemotingException e) {
throw new ExecutionException(e.getMessage(), e);
} catch (Throwable e) {
throw new RpcException(e);
}
} @SuppressWarnings("unchecked")
public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
int timeoutInMillis = (int) unit.convert(timeout, TimeUnit.MILLISECONDS);
try {
return (V) (((Result) future.get(timeoutInMillis)).recreate());
} catch (com.alibaba.dubbo.remoting.TimeoutException e) {
throw new TimeoutException(StringUtils.toString(e));
} catch (RemotingException e) {
throw new ExecutionException(e.getMessage(), e);
} catch (Throwable e) {
throw new RpcException(e);
}
}
}

最后,回头看一下FutureFilter:

     public Result invoke(final Invoker<?> invoker, final Invocation invocation) throws RpcException {
final boolean isAsync = RpcUtils.isAsync(invoker.getUrl(), invocation); fireInvokeCallback(invoker, invocation);
// need to configure if there's return value before the invocation in order to help invoker to judge if it's
// necessary to return future.
Result result = invoker.invoke(invocation);
if (isAsync) {
asyncCallback(invoker, invocation);
} else {
syncCallback(invoker, invocation, result);
}
return result;
}
     private void asyncCallback(final Invoker<?> invoker, final Invocation invocation) {
Future<?> f = RpcContext.getContext().getFuture();
if (f instanceof FutureAdapter) {
ResponseFuture future = ((FutureAdapter<?>) f).getFuture();
future.setCallback(new ResponseCallback() {
public void done(Object rpcResult) {
if (rpcResult == null) {
logger.error(new IllegalStateException("invalid result value : null, expected " + Result.class.getName()));
return;
}
///must be rpcResult
if (!(rpcResult instanceof Result)) {
logger.error(new IllegalStateException("invalid result type :" + rpcResult.getClass() + ", expected " + Result.class.getName()));
return;
}
Result result = (Result) rpcResult;
if (result.hasException()) {
fireThrowCallback(invoker, invocation, result.getException());
} else {
fireReturnCallback(invoker, invocation, result.getValue());
}
} public void caught(Throwable exception) {
fireThrowCallback(invoker, invocation, exception);
}
});
}
}

这里的future对象时之前创建好的DefaultFuture对象。

     private volatile Response response;
private volatile ResponseCallback callback; public boolean isDone() {
return response != null;
} public void setCallback(ResponseCallback callback) {
if (isDone()) {
invokeCallback(callback);
} else {
boolean isdone = false;
lock.lock();
try {
if (!isDone()) {
this.callback = callback;
} else {
isdone = true;
}
} finally {
lock.unlock();
}
if (isdone) {
invokeCallback(callback);
}
}
}

这里判断响应是否已经返回了,如果返回了,直接执行invokeCallback(callback),否则将传入的ResponseCallback对象赋值给callback对象。

2、Future<String> helloFuture = RpcContext.getContext().getFuture(); 

RpcContext:

     private static final ThreadLocal<RpcContext> LOCAL = new ThreadLocal<RpcContext>() {
@Override
protected RpcContext initialValue() {
return new RpcContext();
}
}; private Future<?> future; public static RpcContext getContext() {
return LOCAL.get();
} public <T> Future<T> getFuture() {
return (Future<T>) future;
}

从当前线程上下文中获取之前存进去的FutureAdapter对象。

3、final String helloStr = helloFuture.get(); 

helloFuture是上述的FutureAdapter对象,其get()调用的是内部的DefaultFuture的get(),该方法与同步调用时相同,源码分析见文章开头的三篇文章。

     public V get() throws InterruptedException, ExecutionException {
try {
return (V) (((Result) future.get()).recreate());
} catch (RemotingException e) {
throw new ExecutionException(e.getMessage(), e);
} catch (Throwable e) {
throw new RpcException(e);
}
}

get方法的超时设置除了直接在xml中配置之外,还可以在代码中手动执行(优先级高)

 final String helloStr2 = helloFuture.get(7000, TimeUnit.MILLISECONDS);

三、asyncFuture2()源码解析

下面来看一下asyncFuture2()源码:

1、Future<String> helloFuture = RpcContext.getContext().asyncCall(()-> demoService.sayHello("zhangsan"));

     public <T> Future<T> asyncCall(Callable<T> callable) {
try {
try {
setAttachment(Constants.ASYNC_KEY, Boolean.TRUE.toString());
// 1 执行传入的任务(此处创建FutureAdapter对象,并且设置到当前线程的RpcContext的future对象中)
final T o = callable.call();
//local invoke will return directly
if (o != null) {
FutureTask<T> f = new FutureTask<T>(new Callable<T>() {
public T call() throws Exception {
return o;
}
});
f.run();
return f;
} else { }
} catch (Exception e) {
throw new RpcException(e);
} finally {
removeAttachment(Constants.ASYNC_KEY);
}
} catch (final RpcException e) {
return new Future<T>() {
public boolean cancel(boolean mayInterruptIfRunning) {
return false;
} public boolean isCancelled() {
return false;
} public boolean isDone() {
return true;
} public T get() throws InterruptedException, ExecutionException {
throw new ExecutionException(e.getCause());
} public T get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException,
TimeoutException {
return get();
}
};
}
// 2 从当前线程的RpcContext中获取future对象
return ((Future<T>) getContext().getFuture());
}

这里外层的catch的作用是什么?没搞清楚 https://github.com/alibaba/dubbo/issues/1346

2、final String helloStr = helloFuture.get();

与同步相同。

总结:dubbo异步与同步的差别:

  • 同步:创建DefaultFuture之后,直接get阻塞等待;
  • 异步:创建DefaultFuture之后,使用FutureAdapter进行包装,之后设置到当前线程的RpcContext中;后续用户在合适的时候自己从RpcContext获取future,之后get。

9.4 dubbo异步调用原理的更多相关文章

  1. dubbo异步调用原理 (1)

    此文已由作者赵计刚授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 一.使用方式 服务提供方不变,调用方代码如下: 1     <dubbo:reference id=& ...

  2. Spring异步调用原理及SpringAop拦截器链原理

    一.Spring异步调用底层原理 开启异步调用只需一个注解@EnableAsync @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTI ...

  3. 限时购校验小工具&dubbo异步调用实现限

    本文来自网易云社区 作者:张伟 背景 限时购是网易考拉目前比较常用的促销形式,但是前期创建一个限时购活动时需要各个BU按照指定的Excel格式进行选品提报,为了保证提报数据准确,运营需要人肉校验很多信 ...

  4. 抓到Dubbo异步调用的小BUG,再送你一个贡献开源代码的机会

    hello,大家好呀,我是小楼. 最近一个技术群有同学at我,问我是否熟悉Dubbo,这我熟啊~ 他说遇到了一个Dubbo异步调用的问题,怀疑是个BUG,提到BUG我可就不困了,说不定可以水,哦不.. ...

  5. Direct3D Draw函数 异步调用原理解析

    概述 在D3D10中,一个基本的渲染流程可分为以下步骤: 清理帧缓存: 执行若干次的绘制: 通过Device API创建所需Buffer: 通过Map/Unmap填充数据到Buffer中: 将Buff ...

  6. 【转】Zookeeper-Watcher机制与异步调用原理

    声明:本文转载自http://shift-alt-ctrl.iteye.com/blog/1847320,转载请务必声明. Watcher机制:目的是为ZK客户端操作提供一种类似于异步获得数据的操作. ...

  7. Zookeeper-Watcher机制与异步调用原理

    转载于:http://shift-alt-ctrl.iteye.com/blog/1847320 Watcher机制:目的是为ZK客户端操作提供一种类似于异步获得数据的操作. 1)在创建Zookeep ...

  8. dubbo异步调用三种方式

    异步通讯对于服务端响应时间较长的方法是必须的,能够有效地利用客户端的资源,在dubbo中,消费端<dubbp:method>通过 async="true"标识. < ...

  9. dubbo同步调用、异步调用和是否返回结果源码分析和实例

    0. dubbo同步调用.异步调用和是否返回结果配置 (1)dubbo默认为同步调用,并且有返回结果. (2)dubbo异步调用配置,设置 async="true",异步调用可以提 ...

随机推荐

  1. concat layer

    参考:http://blog.csdn.net/bailufeiyan/article/details/50876728#reply

  2. Linux VMware tools安装步骤

    Linux VMware tools安装步骤: 1.安装环境介绍 #虚拟机版本:VMware-workstation-full-10 #linux分发版本:CentOS-6.4-i386-LiveCD ...

  3. 转:前端页面a标签嵌套a标签效果的两种解决方案

      这是由工作中的一个小改动需求得到的这个解决方案的:那个需求是这样的,如图: 需求原来是球队名字没有点击功能的,而蓝色方框两队之间的比赛点击的时候会跳转到比赛文字直播页面.现在需要要求点击球队名字要 ...

  4. 性能测试三:jmeter进阶之图形插件

    一.图形化插件的使用 使用Jmeter插件可以更直观的查看tps和响应时间 插件官网: http://jmeter-plugins.org/downloads/all 第一种方法,找到需要的插件下载j ...

  5. Linux系统上安装docker + Compose并创建WordPress

    安装docker可参考我的另一篇文章 安装Compose Docker Compose 是 Docker 官方编排(Orchestration)项目之一, 负责快速在集群中部署分布式应用. 方法一 1 ...

  6. python 全栈开发,Day15(递归函数,二分查找法)

    一.递归函数 江湖上流传这这样一句话叫做:人理解循环,神理解递归.所以你可别小看了递归函数,很多人被拦在大神的门槛外这么多年,就是因为没能领悟递归的真谛. 递归函数:在一个函数里执行再调用这个函数本身 ...

  7. 【AtCoder】CODE FESTIVAL 2017 qual C

    A - Can you get AC? No #include <bits/stdc++.h> #define fi first #define se second #define pii ...

  8. Python 可命名元祖

    import collections MytupleClass = collections.namedtuple('MytupleClass',['x','y','z']) obj = Mytuple ...

  9. 关于BeanUtils.copyProperties() 用法及区别

    这两个类在不同的包下面,而这两个类的copyProperties()方法里面传递的参数赋值是相反的. 例如:a,b为对象BeanUtils.copyProperties(a, b); BeanUtil ...

  10. FastAdmin 的上传代码在哪里?

    FastAdmin 的上传代码在哪里? FastAdmin 默认是通过 Ajax 上传到本地. 使用 admin/ajax/upload 方式返回 url. 代码文件地址在 application\a ...