由于dubbo版本较低遇到了一个诡异的问题。

场景

dubboRpcContext存放了一个attachments属性,用于隐式传递参数,每次发起调用之后会clear清空。

使用了cat监控之后,可以在dubbo服务调用之间传递一个id作为链路跟踪。而这个参数就是在Comsumer发起请求前放入attachments,在Provider接收到请求后从attachments拿出。

测试环境调用某服务会出现这个id时有时无的情况,于是分析日志,发现没有这个id的情况下,dubbo协议走的是hessian协议。原来此服务提供了hessian访问,于是客户端会随机走dubbo或者hessian协议。

为什么hessian协议会丢attachments?这个id是通过实现dubboFilter塞进去的,理论上Filter应该是协议无关的。通过debug发现,在发送请求之前的RpcContext.attachments里也的确是有这个id。

探索

dubbo协议

dubbo的consumer调用抽象为Invoker和Invocation,在Invoker执行invoke方法之前,需要执行负载均衡、重试计数、拦截器调用链,最后执行抽象类AbstractInvoker.invoke方法,继而调用不同协议的doInvoke方法

看看dubbo协议的doInvoke方法,

upload successful 可以看到请求是由一个HeaderExchangeClient去发起, upload successful 这里的的channel是一个nettyClient,而request的内容如下: upload successful dubbo协议的实现是利用socket长连接,将整个request对象发送过去的,没有丢attachments

hessian协议

hessian协议下是通过com.caucho.hessian.client.HessianProxy#invoke来发起请求,这个方法签名如下:

public Object invoke(Object proxy, Method method, Object[] args)

第一个参数不知道是干嘛的,第二个参数和第三个参数分别是调用服务的方法对象和参数。反正这个方法是没地方放attchments这种隐式参数含义的东西。

debug看他的调用栈,

upload successful

定位到这个com.alibaba.dubbo.rpc.proxy.AbstractProxyInvoker#invoke方法

    public Result invoke(Invocation invocation) throws RpcException {
        try {
            return new RpcResult(doInvoke(proxy, invocation.getMethodName(), invocation.getParameterTypes(), invocation.getArguments()));
        } catch (InvocationTargetException e) {
            return new RpcResult(e.getTargetException());
        } catch (Throwable e) {
            throw new RpcException("Failed to invoke remote proxy method " + invocation.getMethodName() + " to " + getUrl() + ", cause: " + e.getMessage(), e);
        }
    }

可以看到,从这里开始

return new RpcResult(doInvoke(proxy, invocation.getMethodName(), invocation.getParameterTypes(), invocation.getArguments()));

就把invocation里的attachments丢了

upload successful

解决方法

dubbo版本至少升级直2.6.3,这个版本对提供了对hessian协议的attachments支持。

upload successful

下面是部分代码,

public class DubboHessianURLConnectionFactory extends HessianURLConnectionFactory {

    @Override
    public HessianConnection open(URL url) throws IOException {
        HessianConnection connection = super.open(url);
        RpcContext context = RpcContext.getContext();
        for (String key : context.getAttachments().keySet()) {
            connection.addHeader(Constants.DEFAULT_EXCHANGER + key, context.getAttachment(key));
        }

        return connection;
    }
}

dubbo通过继承hessian库的类,在处理URL的时候把attachments放到header里去了,接收请求时再从header里拿出来。