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

hello,大家好呀,我是小楼。

最近一个技术群有同学at我,问我是否熟悉Dubbo,这我熟啊~

image

他说遇到了一个Dubbo异步调用的问题,怀疑是个BUG,提到BUG我可就不困了,说不定可以水,哦不...写一篇文章。

image

问题复现

遇到问题,尤其不是自己遇到的,必须要复现出来才好排查,截一个当时的聊天记录:

image

他的问题原话是:

今天发现一个问题 有一个dubbo接口返回类型是boolean, 把接口从同步改成异步 server 端返回true 消费端却返回false,把boolean改成Boolean就能正常返回结果 有碰到过这个问题吗

注意几个重点:

  • 接口返回类型是boolean
  • 同步改为异步调用返回的boolean和预期不符合
  • boolean基本类型改成包装类型Boolean就能正常返回

听到这个描述,我的第一反应是这个返回结果定义为boolean肯定有问题!

《Java开发手册》中就强调了RPC接口返回最好不要使用基本类型,而要使用包装类型:

image

但这个是业务编码规范,如果RPC框架不能使用boolean作为返回值,岂不是个BUG?而且他强调了是同步改为异步调用才出现这种情况,说明同步没问题,有可能是异步调用的锅。

于是我顺口问了Dubbo的版本,说不定是某个版本的BUG。得到回复,是2.7.4版本的Dubbo。

于是我拉了个工程准备复现这个问题。

哎,等等~

Dubbo异步调用的写法可多了,于是我又问了下他是怎么写的。

image

知道怎么写的就好办了,写个Demo先:

  1. 定义Dubbo接口,一个返回boolean,一个返回Boolean
public interface DemoService {
    boolean isUser();
    Boolean isFood();
}
  1. 实现Provider,为了简单,都返回true,并且打了日志
@Service
public class DemoServiceImpl implements DemoService {

    @Override
    public boolean isUser() {
        System.out.println("server is user : true");
        return true;
    }

    @Override
    public Boolean isFood() {
        System.out.println("server is food : true");
        return true;
    }
}
  1. 实现Consumer,为了方便调用,实现了一个Controller,为了防止本机调用,injvm设置为false,这里是经验,injvm调用逻辑和远程调用区别挺大,为了防止干扰,统一远程调用。
@RestController
public class DemoCallerService {

    @Reference(injvm = false, check = false)
    private DemoService demoService;

    @GetMapping(path = "/isUser")
    public String isUser() throws Exception {
        BlockingQueue<Boolean> q = new ArrayBlockingQueue<>(1);
        RpcContext.getContext().asyncCall(
                () -> demoService.isUser()
        ).handle(
                (isUser, throwable) -> {
                    System.out.println("client is user = " + isUser);
                    q.add(isUser);
                    return isUser;
                });
        q.take();
        return "ok";
    }

    @GetMapping(path = "/isFood")
    public String isFood() throws Exception {
        BlockingQueue<Boolean> q = new ArrayBlockingQueue<>(1);
        RpcContext.getContext().asyncCall(
                () -> demoService.isFood()
        ).handle(
                (isFood, throwable) -> {
                    System.out.println("client is food = " + isFood);
                    q.add(isFood);
                    return isFood;
                });
        q.take();
        return "ok";
    }
}
  1. 启动一个Provider,再启动一个Consumer进行测试,果然和提问的同学表现一致:
  • 先调用isUser(返回boolean),控制台打印:
// client ...
client is user = false
// server ...
server is user : true
  • 再调用isFood(返回Boolean),控制台打印:
// client ...
client is food = true
// server ...
server is food : true

问题排查

  1. Debug

先猜测一下是哪里的问题,server端返回true,应该问题不大,可能是client端哪里转换出错了。但这都是猜想,我们直接从client端接受到的数据开始,如果接收的数据没问题,肯定就是后续处理出了点小差错。

如果你非常熟悉Dubbo的调用过程,直接知道大概在这里

com.alibaba.dubbo.remoting.exchange.support.DefaultFuture#doReceived

如果你不熟悉,那就比较困难了,推荐读一下之前的文章《我是一个Dubbo数据包...》,知道得越多,干活就越快。

我们打3个断点:

image

image

  • 断点①为了证明我们的请求进来了
  • 断点②为了证明进了回调
  • 断点③为了能从接受到数据包的初始位置开始排查

按照我们的想法,执行顺序应该是①、③、②,但是这里很奇怪,并没有按照我们的预期执行,而是先执行①,再执行②,最后执行③!

这是为什么?对于排查问题中的这些没有符合预期的蛛丝马迹,要特别留心,很可能就是一个突破点

于是我们对asyncCall这个方法进行跟踪:

image

发现这里callable调用call返回了false,然后false不为null且不是CompletableFuture的实例,于是直接调用了CompletableFuture.completedFuture(o)

看到这里估计有部分小伙伴发现了问题,正常情况下,Dubbo的异步调用,执行调用后,不会立马得到结果,只会拿到一个null或者一个CompletableFuture,然后在回调方法中等待server端的返回。

这里的逻辑是如果返回的结果不为null且不为CompletableFuture的实例就直接将CompletableFuture设置为完成,立马执行回调。

暂且不管这个逻辑。

我们先看为什么会返回false。这里的callable是Dubbo生成的一个代理类,其实就是封装了调用Provider的逻辑,有没有办法看看他封装的逻辑呢?有!用arthas。

  1. arthas

我们下载安装一个arthas,可以参考如下文档:

https://arthas.aliyun.com/doc/quick-start.html

attach到我们的Consumer进程上,执行sc命令(查看已加载的类)查看所有生成的代理类,由于我们的Demo就生成了一个,所以看起来很清晰

sc *.proxy0

image

再使用jad命令反编译已加载的类:

jad org.apache.dubbo.common.bytecode.proxy0

image

看到这里估计小伙伴们又揭开了一层疑惑,this.handler.invoke就是去调用Provider,由于这里是异步调用,必然返回的是null,所以返回值定义为boolean的方法返回了false

看到这里,估计小伙伴们对《Java开发手册》里的规范有了更深的理解,这里的处理成false也是无奈之举,不然难道返回true?属于信息丢失了,无法区分是调用的返回还是其他异常情况。

我们再回头看asyncCall

image

圈出来的这段代码令人深思,尤其是最后一行,为啥直接将CompletableFuture设置为完成?

从这个方法的名字能看出它是执行异步调用,但这里有行注释:

//local invoke will return directly

首先这个注释的格式上下不一,//之后讲道理是需要一个空格的,我觉得这里提个PR改下代码格式肯定能被接受~

其次local invoke,我理解应该是injvm这种调用,为啥要特殊处理?这个处理直接就导致了返回基本类型的接口在异步调用时必然会返回false的BUG。

我们测试一下injvm的调用,将demo中injvm参数改为true,Consumer和Provider都在一个进程中,果然和注释说的一样:

server is user : true
client is user = true

如何修复

我觉得这应该算是Dubbo的一个BUG,虽然这种写法不提倡,但作为一款RPC框架,这个错误还是不应该。

修复的办法就是在injvm分支这里加上判断,如果是injvm调用还是保持现状,如果不是injvm调用,直接忽略,走最后的return逻辑:

public <T> CompletableFuture<T> asyncCall(Callable<T> callable) {
    try {
        try {
            setAttachment(ASYNC_KEY, Boolean.TRUE.toString());
            final T o = callable.call();
            //local invoke will return directly
            if (o != null) {
                if (o instanceof CompletableFuture) {
                    return (CompletableFuture<T>) o;
                }
                if (injvm()) { // 伪代码
                    return CompletableFuture.completedFuture(o);
                }
            } else {
                // The service has a normal sync method signature, should get future from RpcContext.
            }
        } catch (Exception e) {
            throw new RpcException(e);
        } finally {
            removeAttachment(ASYNC_KEY);
        }
    } catch (final RpcException e) {
        // ....
    }
    return ((CompletableFuture<T>) getContext().getFuture());
}

最后

排查过程中还搜索了github,但没有什么发现,说明这个BUG遇到的人很少,可能是大家用异步调用本来就很少,再加上返回基本类型就更少,所以也不奇怪。

而且最新的代码这个BUG也还存在,所以你懂我意思吧?这也是个提交PR的好机会~

不过话说回来,我们写代码最好还是遵循规范,这些都是前人为我们总结的最佳实践,如果不按规范来,可能就会有意想不到的问题。

当然遇到问题也不要慌,代码就在那躺着,工具也多,还怕搞不定吗?

最后,感谢群里小伙伴提供素材,感谢大家的阅读,如果能动动小手帮我点个在看就更好了。我们下期再见~

对了,标题为什么叫《再送你一次》?因为之前送过呀~

  • 本文已收录 https://github.com/lkxiaolou/lkxiaolou 欢迎star。
  • 搜索关注微信公众号"捉虫大师",后端技术分享,架构设计、性能优化、源码阅读、问题排查、踩坑实践。

原文地址:https://www.cnblogs.com/zhuochongdashi/p/16442812.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


在网络请求时,总会有各种异常情况出现,我们需要提前处理这种情况。在完善的rpc组件dubbo中,自然是不会少了这一层东西的。我们只需要通过一些简单的配置就可以达到超时限制的作用了。dubbo的设计理念是,客户端控制优先,服务端控制兜底。 1.超时机制的实现思路要想实
作者:宇曾背景软件技术的发展历史,从单体的应用,逐渐演进到分布式应用,特别是微服务理念的兴起,让大规模、高并发、低延迟的分布式应用成为可能。云原生时代下,微服务框架本身也在不断地进化和迭代演进。微服务框架一般会涉及到以下几个知识点:本文我们着重探讨以下三大微服务框架:
hello,大家好呀,我是小楼。最近一个技术群有同学at我,问我是否熟悉Dubbo,这我熟啊~他说遇到了一个Dubbo异步调用的问题,怀疑是个BUG,提到BUG我可就不困了,说不定可以水,哦不...写一篇文章。问题复现遇到问题,尤其不是自己遇到的,必须要复现出来才好排查,截一个当时的聊天记录:他的问题
 一个软件开发人员,工作到了一定的年限(一般是3、4年左右),如果他还没学会阅读源码,那么他就会遇到瓶颈。因为到了这个时候的开发,他应该不仅仅只会做那些CURD的业务逻辑,而应该会根据公司的实际情况去写框架。而基本上没有谁能像天才一样从零写出一个框架,很多人写框架其实
当一个服务调用另一个远程服务出现错误时的外观Dubbo提供了多种容错方案,默认值为failover(重试)1)、FailoverCluster(默认)失败自动切换,当出现失败,重试其他服务器,通常用于读操作,但重试会带来更长延迟,可以通过属性retries来设置重试次数(不含第一次)2)、FailfastC
最近在看阿里开源RPC框架Dubbo的源码,顺带梳理了一下其中用到的设计模式。下面将逐个列举其中的设计模式,并根据自己的理解分析这样设计的原因和优劣。责任链模式责任链模式在Dubbo中发挥的作用举足轻重,就像是Dubbo框架的骨架。Dubbo的调用链组织是用责任链模式串连起来的。责任链
在过去持续分享的几十期阿里Java面试题中,几乎每次都会问到Dubbo相关问题,比如:“如何从0到1设计一个Dubbo的RPC框架”,这个问题主要考察以下几个方面:你对RPC框架的底层原理掌握程度。考验你的整体RPC框架系统设计能力。具体,mike来为大家详解。RPC和RPC框架1.RPC(RemoteProcedure
Dubbo在启动时会检查服务提供者所提供的服务是否可用,默认为True。(1)、单个服务关闭启动时检查(check属性置为false)1)、基于xml文件配置方式1<!--3、声明需要调用的远程服务接口,生成远程服务代理,可以和本地Bean一样使用-->2<dubbo:referenceid="userService"i
(1)、新建一个普通Maven项目,用于存放一些公共服务接口及公共的Bean等。项目: 公共Bean:1packagecn.coreqi.entities;23importjava.io.Serializable;45publicclassUserimplementsSerializable{6privateIntegerid;7privateStringuserName;
1.安装java:yuminstalljava2.下载Tomcat:wgethttp://mirrors.shu.edu.cn/apacheomcatomcat-9/v9.0.14/bin/apache-tomcat-9.0.14-fulldocs.tar.gz3.解压Tomcat:tar-xvfapache-tomcat-9.0.14.tar.gz-C/usr/local/cd/usr/local/mvapache-tomcat-9.0.14//usr/local
工程结构:主pom<?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.ap
微服务架构到底应该如何选择? 什么是微服务?微服务的概念最早是在2014年由MartinFowler和JamesLewis共同提出,他们定义了微服务是由单一应用程序构成的小服务,拥有自己的进程与轻量化处理,服务依业务功能设计,以全自动的方式部署,与其他服务使用HTTPAPI通讯。同时,服务会
(1)、dubbo-admin(管理控制台)1)、从https://github.com/apache/incubator-dubbo-ops下载解压2)、修改dubbo-admin配置文件中zookeeper的注册地址3)、使用Maven命令打包mvncleanpackage4)、使用java-jar dubbo-admin-0.0.1-SNAPSHOT.jar命令运行5)、访
Dubbo概述Dubbo的背景随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在必行,亟需一个治理系统确保架构有条不紊的演进。  单一应用架构当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。
前言跳槽时时刻刻都在发生,但是我建议大家跳槽之前,先想清楚为什么要跳槽。切不可跟风,看到同事一个个都走了,自己也盲目的开始面试起来,期间也没有准备充分,到底是因为技术原因,影响自己的发展,偏移自己规划的轨迹,还是钱给少了,不受重视。准备不充分的面试,完全是浪费时间,更是对自己的不负责
Dubbo是阿里巴巴内部使用的分布式业务框架,2012年由阿里巴巴开源。由于Dubbo在阿里内部经过广泛的业务验证,在很短时间内,Dubbo就被许多互联网公司所采用,并产生了许多衍生版本,如网易,京东,新浪,当当等等。由于阿里策略变化,2014年10月Dubbo停止维护。随后部分互联网公司公开了自行维护的Du
1.java.lang.NoSuchMethodError:org.jboss.resteasy.specimpl.BuiltResponse.getHeaders()Ljavax/wss/core/MultivaluedMap;解决:参考https://stackoverflow.com/questions/17618587/jetty-9-0-embedded-and-resteasy-3-0-keeps-throwing-nosuchmethoderror将依赖提到最前
服务消费者引用服务提供者的服务时可能由于网络原因导致长时间未返回相应,此时大量的线程将会阻塞,引起性能下降等问题。可以通过引入服务超时来解决该问题 服务超时指服务在给定的时间内未返回相应将立即终止该请求,一般配合retries(重试次数)使用。单位毫秒,默认值1000 
服务超时后重试次数【retries】,不包含第一次调用,0代表不重试*我们应该在幂等方法上设置重试次数【查询、删除、修改】,在非幂等方法上禁止设置重试次数。★幂等:指多次运行方法所产生的最终效果是一致的1<!--3、声明需要调用的远程服务接口,生成远程服务代
一、Web应用架构的演变​随着互联网的发展,网站应用的规模不断扩大,Web应用架构也在不断的演变​四个阶段:单一应用、垂直应用、分布式服务、流动计算1.单一应用架构​当网站访问量很小时,只需要一个应用程序,将所有的功能都部署在一起,以减少部署节点和成本​此时关键