注:文章中的坑出现在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?;(group = Constants.CONSUMER, order = -) public class ConsumerContextFilter implements Filter { (Invoker<?> invoker, Invocation invocation) { () (invoker) (invocation) (NetUtils.getLocalHost(), ) (invoker.getUrl().getHost(), invoker.getUrl().getPort()); (invocation instanceof RpcInvocation) { (() )(invoker); } { (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); } }