注:文章中的坑出现在2.5.4版本之前,这个坑在2.5.4版本已经得到修复。
问题描述
场景描述,如上图所示:
monospace; border-radius: 3px; background-color: #f7f7f9; border: 0px; white-space: normal; font-weight: 600; font-size: 14px;">客户端
?远程异步调用?服务A
?,?服务A
?在处理客户端请求的过程中需要远程同步调用?服务B
?,服务A
?从?服务B
?的响应中取数据时,得到的是?null
!!!
RPC请求响应参数传递过程
1)Client在发起RPC调用请求前,将请求参数构建成?RpcInvocation
?;
2)Client在发起RPC调用请求前,会经过Filter处理:
ConsumerContextFilter
?会将请求信息,如invoker、invocation、Address等,写入?RpcContext
?;instanceof RpcInvocation) { (( ) ) (invoker); } { (invocation); } { () (); } } }(group = Constants.CONSUMER, order = - ) public class ConsumerContextFilter implements Filter { (Invoker<?> invoker, Invocation invocation) { () (invoker) (invocation) (NetUtils.getLocalHost(), ) (invoker.getUrl().getHost(), invoker.getUrl().getPort()); (invocation
3)Client在发起RPC调用请求前,会经过AbstractInvoker:
AbstractInvoker
?会将?RpcContext
?中的?attachments
?内容写入到?RpcInvocation
?,以实现附加参数的传递;Map<String, String>= RpcContext () (); ( != ) { invocation ( ); }
AbstractInvoker
?会从RPC请求参数?URL
?中?ASYNC_KEY
?的值,并设置到?RpcInvocation
?的attachment
?中;(getUrl().getMethodParameter(invocation.getMethodName(), Constants.ASYNC_KEY, false)) { (Constants.ASYNC_KEY, Boolean.TRUE.toString()); }
4)Client在发起RPC调用请求时,会经过DubboInvoker:
RpcInvocation
?的?attachment
?中获取并判断?ASYNC_KEY
?是否为true,以实现消费端的异步调用;isAsync(URL url, Invocation inv) { isAsync; ( . .toString().equals(inv.getAttachment(Constants.ASYNC_KEY))) { isAsync = ; } { isAsync = url.getMethodParameter(getMethodName(inv), Constants.ASYNC_KEY, ); } isAsync; }
5)Client在发起RPC调用请求时,会将?RpcInvocation
?作为调用参数传递给服务提供方:
RpcInvocation
?中的扩展属性?attachments
?,实现了请求调用扩展信息传递的功能;1)服务端在接收到RPC请求,调用真正实现接口前,会经过?ContextFilter
?。
ContextFilter
?会将请求信息,如invoker、invocation、Address等,写入?RpcContext
?;ContextFilter
?会将请求参数?RpcInvocation
?的?attachments
?扩展信息取出,过滤掉某些特定KEY之后,将其余扩展属性设置到当前?RpcContext
?的?attachments
?中;Map<, > attachments = invocation.getAttachments(); (attachments != null) { attachments = new HashMap< , >(attachments); attachments. (Constants.PATH_KEY); attachments. (Constants.GROUP_KEY); attachments. (Constants.VERSION_KEY); attachments. (Constants.DUBBO_VERSION_KEY); attachments. (Constants.TOKEN_KEY); attachments. (Constants.TIMEOUT_KEY); attachments. (Constants.ASYNC_KEY);//清空消费端的异步参数, . 版本才新加进去的 } RpcContext.getContext() .setInvoker(invoker) .setInvocation(invocation) .setAttachments(attachments) .setLocalAddress(invoker.getUrl().getHost(), invoker.getUrl().getPort());
其中?attachments.remove(Constants.ASYNC_KEY);//清空消费端的异步参数
?这行代码是在dubbo的2.5.4版本才加进去的,也就是之前的版本中并没有这行代码。
2)在2.5.4版本之前,对于?Client
?发来的异步调用请求,其?RpcInvocation
?参数中包含了?ASYNC=true
?的?attachment
?扩展信息:
ASYNC=true
?的这个扩展信息就会被设置到服务A的?RpcContext
?的扩展属性中;服务A
?处理RPC调用,执行实际接口实现类的逻辑时,因为依赖的?服务B
?,所以会继续发送RPC调用请求给?服务B
?;服务A
?调用?服务B
?时,?服务A
?的?RpcContext
?的扩展属性会被写入到?A -> B
?的?RpcInvocation
?参数中,这就导致?ASYNC=true?的扩展属性参数被误传到?A -> B
?的?RpcInvocation
?参数中,进而导致在服务A发起RPC请求调用时触发了错误的异步调用逻辑;服务A
?获取到的RPC执行结果?RpcResult
?的内容当然是个空;(isAsync) { ResponseFuture future = currentClient.request(inv, timeout); RpcContext.getContext().setFuture( FutureAdapter< >(future)); RpcResult(); } { RpcContext.getContext().setFuture( ); (Result) currentClient.request(inv, timeout). (); }
以上就是这个坑的产生原因
自己写了个Filter,添加到Dubbo服务提供方接收请求后、实际处理请求前的Filter执行链中。
从请求参数?URL
?中解析出?ASYNC
?扩展参数标识,而不依赖?RpcInvocation
?中的值。
(group = {Constants.PROVIDER}, order = - ) { { isAsync = invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.ASYNC_KEY, ); RpcContext.getContext().setAttachment(Constants.ASYNC_KEY, String.valueOf(isAsync)); invoker.invoke(invocation); } }