迅闻网
让更多人看到你

抓到Dubbo异步调用的小BUG

hello,咱们好呀,我是小楼。
最近一个技能群有同学at我,问我是否了解Dubbo,这我熟啊~
他说遇到了一个Dubbo异步调用的问题,怀疑是个BUG,提到BUG我可就不困了,说不定能够水,哦不…写一篇文章。
问题复现
遇到问题,尤其不是自己遇到的,必须要复现出来才好排查,截一个其时的聊天记录:
他的问题原话是:
今天发现一个问题有一个dubbo接口回来类型是boolean,把接口从同步改成异步server端回来true消费端却回来false,把boolean改成Boolean就能正常回来成果有碰到过这个问题吗
注意几个重点:
接口回来类型是boolean
同步改为异步调用回来的boolean和预期不符合
boolean根本类型改成包装类型Boolean就能正常回来
听到这个描绘,我的榜首反应是这个回来成果定义为boolean肯定有问题!
《Java开发手册》中就强调了RPC接口回来最好不要运用根本类型,而要运用包装类型:
但这个是业务编码标准,假如RPC结构不能运用boolean作为回来值,岂不是个BUG?而且他强调了是同步改为异步调用才出现这种情况,阐明同步没问题,有可能是异步调用的锅。
所以我顺口问了Dubbo的版别,说不定是某个版别的BUG。得到回复,是2.7.4版别的Dubbo。

Dubbo
所以我拉了个工程准备复现这个问题。
哎,等等~
Dubbo异步调用的写法可多了,所以我又问了下他是怎样写的。
知道怎样写的就好办了,写个Demo先:
定义Dubbo接口,一个回来boolean,一个回来Boolean
publicinterfaceDemoService{
booleanisUser();
BooleanisFood();
}
完结Provider,为了简略,都回来true,并且打了日志
@ServicepublicclassDemoServiceImplimplementsDemoService{
@OverridepublicbooleanisUser(){
System.out.println(“serverisuser:true”);
returntrue;
}
@OverridepublicBooleanisFood(){
System.out.println(“serverisfood:true”);
returntrue;
}
}
完结Consumer,为了方便调用,完结了一个Controller,为了防止本机调用,injvm设置为false,这儿是经验,injvm调用逻辑和长途调用差异挺大,为了防止干扰,统一长途调用。
@RestControllerpublicclassDemoCallerService{
@Reference(injvm=false,check=false)
privateDemoServicedemoService;
@GetMapping(path=”/isUser”)
publicStringisUser()throwsException{
BlockingQueueq=newArrayBlockingQueue<>(1);
RpcContext.getContext().asyncCall(
()->demoService.isUser()
).handle(
(isUser,throwable)->{
System.out.println(“clientisuser=”+isUser);
q.add(isUser);
returnisUser;
});
q.take();
return”ok”;
}
@GetMapping(path=”/isFood”)
publicStringisFood()throwsException{
BlockingQueueq=newArrayBlockingQueue<>(1);
RpcContext.getContext().asyncCall(
()->demoService.isFood()
).handle(
(isFood,throwable)->{
System.out.println(“clientisfood=”+isFood);
q.add(isFood);
returnisFood;
});
q.take();
return”ok”;
}
}
发动一个Provider,再发动一个Consumer进行测试,公然和提问的同学表现一致:
先调用isUser(回来boolean),控制台打印:
//client…
clientisuser=false//server…
serverisuser:true
再调用isFood(回来Boolean),控制台打印:
//client…
clientisfood=true//server…
serverisfood:true
问题排查
Debug
先猜测一下是哪里的问题,server端回来true,应该问题不大,可能是client端哪里转换出错了。但这都是猜想,咱们直接从client端接收到的数据开端,假如接收的数据没问题,肯定便是后续处理出了点小差错。
假如你十分了解Dubbo的调用过程,直接知道大概在这儿
com.alibaba.dubbo.remoting.exchange.support.DefaultFuture#doReceived
假如你不了解,那就比较困难了,引荐读一下之前的文章《我是一个Dubbo数据包…》,知道得越多,干活就越快。
咱们打3个断点:
断点①为了证明咱们的请求进来了
断点②为了证明进了回调
断点③为了能从接收到数据包的初始方位开端排查
依照咱们的主意,履行次序应该是①、③、②,但是这儿很古怪,并没有依照咱们的预期履行,而是先履行①,再履行②,最终履行③!
这是为什么?对于排查问题中的这些没有符合预期的蛛丝马迹,要特别留心,很可能便是一个突破点。
所以咱们对asyncCall这个方法进行跟踪:
发现这儿callable调用call回来了false,然后false不为null且不是CompletableFuture的实例,所以直接调用了CompletableFuture.completedFuture(o)。
看到这儿估量有部分小伙伴发现了问题,正常情况下,Dubbo的异步调用,履行调用后,不会立马得到成果,只会拿到一个null或许一个CompletableFuture,然后在回调方法中等候server端的回来。
这儿的逻辑是假如回来的成果不为null且不为CompletableFuture的实例就直接将CompletableFuture设置为完结,立马履行回调。
暂时不管这个逻辑。
咱们先看为什么会回来false。这儿的callable是Dubbo生成的一个署理类,其实便是封装了调用Provider的逻辑,有没有方法看看他封装的逻辑呢?有!用arthas。
arthas
咱们下载安装一个arthas,能够参考如下文档:
https://arthas.aliyun.com/doc/quick-start.html
attach到咱们的Consumer进程上,履行sc指令(检查已加载的类)检查所有生成的署理类,因为咱们的Demo就生成了一个,所以看起来很明晰
sc*.proxy0
再运用jad指令反编译已加载的类:
jadorg.apache.dubbo.common.bytecode.proxy0
看到这儿估量小伙伴们又揭开了一层疑问,this.handler.invoke便是去调用Provider,因为这儿是异步调用,必定回来的是null,所以回来值定义为boolean的方法回来了false。
看到这儿,估量小伙伴们对《Java开发手册》里的标准有了更深的了解,这儿的处理成false也是无奈之举,否则难道回来true?归于信息丢掉了,无法区别是调用的回来仍是其他异常情况。
咱们再回头看asyncCall:
圈出来的这段代码令人深思,尤其是最终一行,为啥直接将CompletableFuture设置为完结?
从这个方法的姓名能看出它是履行异步调用,但这儿有行注释:
//localinvokewillreturndirectly
首要这个注释的格局上下不一,//之后讲道理是需求一个空格的,我觉得这儿提个PR改下代码格局肯定能被承受~
其次localinvoke,我了解应该是injvm这种调用,为啥要特别处理?这个处理直接就导致了回来根本类型的接口在异步调用时必定会回来false的BUG。
咱们测试一下injvm的调用,将demo中injvm参数改为true,Consumer和Provider都在一个进程中,公然和注释说的一样:
serverisuser:trueclientisuser=true
怎么修正
我觉得这应该算是Dubbo的一个BUG,虽然这种写法不提倡,但作为一款RPC结构,这个过错仍是不应该。
修正的方法便是在injvm分支这儿加上判断,假如是injvm调用仍是保持现状,假如不是injvm调用,直接疏忽,走最终的return逻辑:
publicCompletableFutureasyncCall(Callablecallable){
try{
try{
setAttachment(ASYNC_KEY,Boolean.TRUE.toString());
finalTo=callable.call();
//localinvokewillreturndirectly
if(o!=null){
if(oinstanceofCompletableFuture){
return(CompletableFuture)o;
}
if(injvm()){//伪代码
returnCompletableFuture.completedFuture(o);
}}else{
//Theservicehasanormalsyncmethodsignature,shouldgetfuturefromRpcContext.
}
}catch(Exceptione){
thrownewRpcException(e);
}finally{
removeAttachment(ASYNC_KEY);
}
}catch(finalRpcExceptione){
//….
}
return((CompletableFuture)getContext().getFuture());
}
最终
排查过程中还搜索了github,但没有什么发现,阐明这个BUG遇到的人很少,可能是咱们用异步调用原本就很少,再加上回来根本类型就更少,所以也不古怪。

未经允许不得转载:迅闻网 » 抓到Dubbo异步调用的小BUG
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!

 

迅闻网-让更多人看到你

登录/注册返回首页