有三个应用serviceA,serviceB,serviceC,在确保消费没有错乱的前提下(都只有单个服务提供者),指望其调用关系为java
sequenceDiagram serviceA-->>serviceB:serviceA 异步调用serviceB serviceB->>serviceC:serviceB 同步调用serviceC serviceC->>serviceB:同步返回调用结果true
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>
然而,上面配置后,实际调用关系变为下图async
sequenceDiagram serviceA-->>serviceB:serviceA 异步调用serviceB serviceB-->>serviceC:serviceB 异步调用serviceC serviceC->>serviceB: 返回null,致使指望值boolean永远为false
如上所述,因为B->C 因为dubbo异步配置的传递性,致使变为了异步调用,结果返回了null,致使指望的同步调用结果异常, 可是B第二次调用C会正常返回
现象是因为dubbo异步调用,而后服务提供者内部又有dubbo嵌套调用,==因此咱们须要找出dubbo的内部嵌套调用是否存在异步传递性==,那么既然是传递,就须要上下文环境,进而,咱们想到了dubbo中的上下文环境==RpcContext==,咱们推测是由此类传递了异步参数ide
RpcContext 是一个 ThreadLocal 的临时状态记录器,当接收到 RPC 请求,或发起 RPC 请求时,RpcContext 的状态都会变化。 <br/>
好比:A调B,B再调C,则B机器上,在B调C以前,==RpcContext记录的是A调B的信息==,在B调C以后,RpcContext记录的是B调C的信息。咱们知道dubbo的方法调用,都是由invoker代理调用的,咱们找到AbstractInvoker,查看底层的invoke方法,源码以下:.net
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) { ··· } }
这里涉及到一个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源码的状况,能够有一下几种处理方式。code
转载请注明出处 阿布的夏天xml