Dubbo过滤器 - ContextFilter & ConsumerContextFilter
概述 ContextFilter 和 ConsumerContextFilter 分别用来初始化服务提供端和消费端的上下文 RpcContext 。无论是消费端发起的调用,还是服务端收到的调用,从节点的角度看都是一次调用,都可能产生很多中间临时信息,我们不可能要求在每个方法的参数位置都加一个上下文参数,然后一路往下传。通常做法都是放在 ThreadLocal 中,作为一个全局参数,当前线程中的任何一个地方都可以直接操作上下文信息。
RPC 上下文 RpcContext 是 Dubbo 的上下文信息,其定义如下:
1 上下文中存放的是当前调用过程中所需的环境信息,如 Invoker信息,Invocation信息、地址信息等。 2 RpcContext 是一个 ThreadLocal 的临时状态记录器,该对象维护两个 InternalThreadLocal,分别记录 local 和 server 的上下文。每次收到或发起 RPC 调用的时候,上下文信息都会发生改变。比如:A 调用 B,B 再调用 C,则 B 机器上:在 B 调 C 之前,RpcContext 记录的是 A 调 B 的上下文信息,在 B 开始调 C 时 ,RpcContext 记录的是 B 调 C 的上下文信息。发起调用的时候上下文是由 ConsumerContextFilter 实现的,ContextFilter 保存的是收到的请求的上下文。
服务消费方 1 2 3 4 5 6 7 8 9 10 xxxService.xxx(); boolean isConsumerSide = RpcContext.getContext().isConsumerSide();String serverIP = RpcContext.getContext().getRemoteHost(); String application = RpcContext.getContext().getUrl().getParameter("application" ); yyyService.yyy();
服务提供方 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class XxxServiceImpl implements XxxService { public void xxx () { boolean isProviderSide = RpcContext.getContext().isProviderSide(); String clientIP = RpcContext.getContext().getRemoteHost(); String application = RpcContext.getContext().getUrl().getParameter("application" ); yyyService.yyy(); boolean isProviderSide = RpcContext.getContext().isProviderSide(); } }
ConsumerContextFilter 在服务消费者中使用,负责发起调用时初始化 RpcContext 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 @Activate (group = Constants.CONSUMER, order = -10000 )public class ConsumerContextFilter implements Filter { @Override public Result invoke (Invoker<?> invoker, Invocation invocation) throws RpcException { RpcContext.getContext() .setInvoker(invoker) .setInvocation(invocation) .setLocalAddress(NetUtils.getLocalHost(), 0 ) .setRemoteAddress(invoker.getUrl().getHost(), invoker.getUrl().getPort()); if (invocation instanceof RpcInvocation) { ((RpcInvocation) invocation).setInvoker(invoker); } try { RpcResult result = (RpcResult) invoker.invoke(invocation); RpcContext.getServerContext().setAttachments(result.getAttachments()); return result; } finally { RpcContext.getContext().clearAttachments(); } } }
ConsumerContextFilter 通常会和 ContextFilter 配合使用,因为在微服务环境中有很多链式调用。收到请求时,当前节点可以被看作一个服务提供者,由 ContextFilter 设置上下文。当发起请求调用其他服务,当前服务变成一个消费者,由 ConsumerContextFilter 设置上下文。
ContextFilter 在服务提供者中使用,负责被调用时初始化 RpcContext 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 @Activate (group = Constants.PROVIDER, order = -10000 )public class ContextFilter implements Filter { @Override public Result invoke (Invoker<?> invoker, Invocation invocation) throws RpcException { Map<String, String> attachments = invocation.getAttachments(); if (attachments != null ) { attachments = new HashMap<String, String>(attachments); attachments.remove(Constants.PATH_KEY); attachments.remove(Constants.GROUP_KEY); attachments.remove(Constants.VERSION_KEY); attachments.remove(Constants.DUBBO_VERSION_KEY); attachments.remove(Constants.TOKEN_KEY); attachments.remove(Constants.TIMEOUT_KEY); attachments.remove(Constants.ASYNC_KEY); } RpcContext.getContext() .setInvoker(invoker) .setInvocation(invocation) .setLocalAddress(invoker.getUrl().getHost(), invoker.getUrl().getPort()); if (attachments != null ) { if (RpcContext.getContext().getAttachments() != null ) { RpcContext.getContext().getAttachments().putAll(attachments); } else { RpcContext.getContext().setAttachments(attachments); } } if (invocation instanceof RpcInvocation) { ((RpcInvocation) invocation).setInvoker(invoker); } try { RpcResult result = (RpcResult) invoker.invoke(invocation); result.addAttachments(RpcContext.getServerContext().getAttachments()); return result; } finally { RpcContext.removeContext(); RpcContext.getServerContext().clearAttachments(); } } }
隐式参数 Dubbo 中的 Attachment 在服务消费方和提供方之间隐式传递参数,可以通过 RpcContext
上的 setAttachment
和 getAttachment
在服务消费方和提供方之间进行参数的隐式传递,而传递的载体就是 Invocation
对象。Attachment 传递的过程如下图所示:
注意: path
, group
, version
, dubbo
, token
, timeout
,async
几个 key 是保留字段,在设置 Attachment 参数的 key 时需要使用其它名称。
消费端 在消费端使用 setAttachment
设置 KV 对,在完成一次远程调用会被清空,即多次远程调用要多次设置。
1 2 3 4 5 RpcContext.getContext().setAttachment("index" , "1" ); Invoker.invoke();
服务端 在服务提供端使用 getAttachment
获取隐式参数。
1 2 String index = RpcContext.getContext().getAttachment("index" );
小结 本篇文章对 RpcContext 分别在服务提供端和服务消费端的初始化进行了介绍,初始化的时机是每次发起调用和每次被调用。有了 RpcContext 就不需要将调用相关信息通过方法依次传递了。