内容简介:有三个应用serviceA,serviceB,serviceC,在确保消费没有错乱的前提下(都只有单个服务提供者),期望其调用关系为serviceA dubbo消费serviceB 配置为异步消费async="true",配置如下:然而,上面配置后,实际调用关系变为下图
有三个应用serviceA,serviceB,serviceC,在确保消费没有错乱的前提下(都只有单个服务提供者),期望其调用关系为
serviceA dubbo消费serviceB 配置为异步消费async="true",配置如下:
// serviceA 异步消费serviceB 配置如下
<dubbo:reference id="serviceB" interface="cn.ServiceB"
version="1.0.0" check="false">
<dubbo:method name="reRunJob" async="true"/>
</dubbo:reference>
复制代码
// serviceB 同步消费serviceC配置如下
<dubbo:reference id="serviceC" interface="cn.ServiceC"
version="1.0.0" check="false">
<dubbo:method name="xxx"/>
</dubbo:reference>
复制代码
然而,上面配置后,实际调用关系变为下图
如上所述,由于B->C 由于dubbo异步配置的传递性,导致变为了异步调用,结果返回了null,导致期望的同步调用结果异常, 但是B第二次调用C会正常返回
二、寻找问题根源--源码
1. 我们的排查思路
现象是由于dubbo异步调用,然后服务提供者内部又有dubbo嵌套调用,==所以我们需要找出dubbo的内部嵌套调用是否存在异步传递性==,那么既然是传递,就需要上下文环境,进而,我们想到了dubbo中的上下文环境==RpcContext==,我们推测是由此类传递了异步参数
2. 预备知识:RpcContext简介
RpcContext 是一个 ThreadLocal 的临时状态记录器,当接收到 RPC 请求,或发起 RPC 请求时,RpcContext 的状态都会变化。
比如:A调B,B再调C,则B机器上,在B调C之前,==RpcContext记录的是A调B的信息==,在B调C之后,RpcContext记录的是B调C的信息。
我们知道dubbo的方法调用,都是由invoker代理调用的,我们找到AbstractInvoker,查看底层的invoke方法,源码如下:
public Result invoke(Invocation inv) throws RpcException {
··· 省略无关代码
// 这里的代码在此处对我们的serviceB-->serviceC时,会取出RpcContext,语句上面的RpcContext知识储备,我们知道,这里的context存的是serviceA->serviceB的上下文,也就是说是异步的,到这里谜团解开
Map<String, String> context = RpcContext.getContext().getAttachments();
if (context != null) {
invocation.addAttachmentsIfAbsent(context);
}
// 注意此处代码,如果提供者方法配置了异步参数
if (getUrl().getMethodParameter(invocation.getMethodName(), Constants.ASYNC_KEY, false)){
// 会将异步参数值设置到当前调用对象中 invocation.setAttachment(Constants.ASYNC_KEY, Boolean.TRUE.toString());
}
// 最后再将异步参数存入上下文
RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
try {
return doInvoke(invocation);
} catch (InvocationTargetException e) { ···
} catch (RpcException e) {
···
} catch (Throwable e) {
···
}
}
复制代码
3. 上面还有个小问题,serviceB第二次调用serviceC,会正常返回,这又是为什么呢?
这里涉及到一个Filter ConsumerContextFilter 源码如下:
@Activate(group = Constants.CONSUMER, order = -10000)
public class ConsumerContextFilter implements Filter {
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
RpcContext.getContext()
.setInvoker(invoker)
.setInvocation(invocation)
.setLocalAddress(NetUtils.getLocalHost(), 0)
.setRemoteAddress(invoker.getUrl().getHost(),
invoker.getUrl().getPort());
if (invocation instanceof RpcInvocation) {
((RpcInvocation)invocation).setInvoker(invoker);
}
try {
return invoker.invoke(invocation);
} finally {
// ①注意这里 ,进行了上下文清理
RpcContext.getContext().clearAttachments();
}
}
}
复制代码
==如上代码,serviceB第一次调用serviceC结束时,在consumer的filter chain中,有一个ConsumerContextFilter,在调用结束后会执行RpcContext.getContext().clearAttachments()方法,清除RpcContext中的信息,也就清除了async标识==,所以,第二次调用serviceC,就正常==同步==调用了,至此,我们的疑问得到解释
解决方法
分析了问题产生的原因后,在不修改dubbo源码的情况,可以有一下几种处理方式。
-
将serviceB改为同步调用,如果业务上确实需要异步调用,有以下2种处理方式
-
serviceB的方法无需返回值,可采用oneway的方式(在消费者端配置dubbo:method中return="false")
-
有返回值,并且需要异步,最简单的方式为在实现中使用线程池执行业务。
-
增加一个Provider端的Filter,保证在filter链的结尾,在执行方法前,清除attachment中的async标志。也可达到同样的效果
以上所述就是小编给大家介绍的《采坑系列之--dubbo异步调用传递性导致嵌套调用返回null值的bug》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
JavaScript面向对象编程指南
斯托扬 / 凌杰 / 人民邮电出版社 / 2013-3 / 59.00元
《JavaScript面向对象编程指南》内容包括:JavaScript作为一门浏览器语言的核心思想;面向对象编程的基础知识及其在JavaScript中的运用;数据类型、操作符以及流程控制语句;函数、闭包、对象和原型等概念,以代码重用为目的的继承模式;BOM、DOM、浏览器事件、AJAX和JSON;如何实现JavaScript中缺失的面向对象特性,如对象的私有成员与私有方法;如何应用适当的编程模式,......一起来看看 《JavaScript面向对象编程指南》 这本书的介绍吧!