dubbo异步调用的bug

现象
现有3个服务,关系如下,serviceA异步调用serviceB,serviceB同步调用serviceC。其中serviceB暴露出的接口为异步方式。表现的现象为,serviceB每次调用serviceC时,第一次的返回结果为null,后面几次调用时均能正常返回结果。

问题排查
项目中对于所有的dubbo调用均有记录日志,每次调用主要包含2条日志,CS和CR日志。CS为consumer send,开始调用时日志。CR为接收到provider的应答日志。 
通过观察日志发现。调用serviceC的日志有不合理的地方。在正常的同步调用中,如有多次调用,日志现实顺序为CS,CR,CS,CR。实际出现的日志顺序为CS,CS,CR,CR。初步怀疑第一次返回为null是由于异步调用所致。 
通过dubbo admin查看接口的配置信息,发现参数均无异常。并且在serviceC端的provider上观察到每次调用均有返回数据。所以排除了serviceC的provider的问题。 
再次编写testcase单独测试serviceC服务,发现testcase返回数据均正常。也证实了serviceC服务没有问题。 
最后通过询问是否有异步配置时,发现serviceB暴露的接口是异步方式。修改serviceB为同步方式,重新测试,发现问题解决。每次调用均正常接收到结果。调整serviceB为异步,问题重现。 
最终确认了问题发生场景,即serviceB为异步服务,在serviceB里面同步调用serviceC,最终表现为serivceC的调用是异步,导致问题产生。

代码分析
dubbo的provider和consumer均由Invoker演变而来。并且都是AbstractInvoker的实现类。 
AbstractInvoker
Map<String, String> context = RpcContext.getContext().getAttachments();
        if (context != null) {
            // A点
            invocation.addAttachmentsIfAbsent(context);
        }
        // B点
        if (getUrl().getMethodParameter(invocation.getMethodName(), Constants.ASYNC_KEY, false)){
            invocation.setAttachment(Constants.ASYNC_KEY, Boolean.TRUE.toString());
        }

DubboInvoker

// C点
boolean isAsync = RpcUtils.isAsync(getUrl(), invocation);
....
if (isAsync) {
                ResponseFuture future = currentClient.request(inv, timeout) ;
                RpcContext.getContext().setFuture(new FutureAdapter<Object>(future));
                // D点
                return new RpcResult();
                }

异步调用serviceB时,此时B点的判断为true,会在attachment中设置async的值为true,RpcContext中设置了async的值。
随后调用serviceB的实际方法。serviceB调用serviceC是,代码在A点,会将RpcContext的值设置到当前的invocation中
由于RpcContext是采用ThreadLocal保存的数据,并且在第一步时设置了async值,导致在2方法执行后,invocation中的async的值为true。
在进行serviceC的远程调用时,由于invocation中async值为true,导致C点判断为异步调用,在D点时new了一个RpcResult。所以在第一次调用serviceC时,返回结果为null。
第一次调用serviceC结束时,在consumer的filter chain中,有一个ConsumerContextFilter,在调用结束后会执行RpcContext.getContext().clearAttachments()方法,清除RpcContext中的信息,也就清除了async标识。
第二次调用serviceC时,由于RpcContext中没有了async标识,判断为同步调用,所以会正常返回结果。
 

解决方式
分析了问题产生的原因后,在不修改dubbo源码的情况,可以有一下几种处理方式。 
1. 将serviceB改为同步调用,如果业务上确实需要异步调用,有以下2种处理方式 
* serviceB的方法无需返回值,可采用oneway的方式 
* 有返回值,并且需要异步,最简单的方式为在实现中使用线程池执行业务。 
2. 增加一个Provider端的Filter,保证在filter链的结尾,在执行方法前,清除attachment中的async标志。也可达到同样的效果。
   或写个dubbo接口的重新封装,再加个springboot的async标识,代理异步。

猜你喜欢

转载自blog.csdn.net/zou100/article/details/85797254