Dubbo之Filter
- dubbo版本
- Filter
- ProtocolFilterWrapper
- AccessLogFilter(Provider)
- ExecuteLimitFilter(Provider)
- ClassLoaderFilter(Provider)
- ContextFilter(Provider)
- ExceptionFilter(Provider)
- TimeoutFilter(Provider)
- TokenFilter(Provider)
- TpsLimitFilter(Provider)
- ActiveLimitFilter(Consumer)
- ConsumerContextFilter(Consumer)
- DeprecatedFilter(Consumer)
- FutureFilter(Consumer)
dubbo版本
- dubbo版本2.6.7
Filter
-
Dubbo中的过滤器与Web应用中的过滤器类似,提供了在服务调用前后插入自定义逻辑的途径。默认启用的过滤
过滤器 使用方 作用 AccessLogFilter Provider 打印每一次请求的访问日志。如果需要访问的日志只出现在指定的appender中,可以在log的配置文件中配置additivity ActiveLimitFilter Consumer 用于限制消费者对服务端的最大并行调用数 ExecuteLimitFilter Provider 用于服务端的最大并行调用数 ClassLoaderFilter Provider 用于切换不同线程的类加载器,服务调用完成后还原回去 CompatibleFilter 用于使返回值与调用程序的对象版本兼容,默认不开启。如果启用,则会把JSON的返回值转换为Map。如果返回类型与本地接口中定义的不同,则会做POJO的转换 ConsumerContextFilter Consumer 为消费者把一些上下文信息设置到当前线程的RpcContext对象中,包括invocation、localhost、remote host等 ContextFilter Provider 为服务提供者把一些上下文信息设置到当前线程的RpcContext对象中,包括invocation、localhost、remote host等 DeprecatedFilter Consumer 如果调用的方法被标记为弃用,则记录一个错误信息 EchoFilter Provider 回声测试 ExceptionFilter Provider 用于统一的异常处理,防止出现序列化失败 GenericFilter Provider 用于服务提供者实现泛化调用,实现序列化的检查和处理 GenericImplFilter Consumer 用于消费者实现泛化调用,实现序列化的检查和处理 TimeoutFilter Provider 如果某些服务调用超时,自动记录告警日志 TokenFilter Provider 服务提供者下发令牌给消费者,通常用于防止消费者绕过注册中心直接调用Provider TpsLimitFilter Provider 用于服务端的限流 MonitorFilter Provider
Consumer监控并统计所有的接口调用情况:成功、失败、耗时 TraceFilter Provider Trace指令的使用 FutureFilter Consumer 在发起invoke或得到返回值、出现异常的时候触发回调事件 -
@Activate
注解可以设置过滤器的条件和顺序表示在服务提供者有效 @Activate(group = Constants.PROVIDER, order = -110000) 表示在消费者有效 @Activate(group = Constants.CONSUMER, value = Constants.ACTIVES_KEY)
配置
-
过滤器配置
<!-- 消费方调用过程拦截 --> <dubbo:reference filter="xxx,yyy" /> <!-- 消费方调用过程缺省拦截器,将拦截所有reference --> <dubbo:consumer filter="xxx,yyy"/> <!-- 提供方调用过程拦截 --> <dubbo:service filter="xxx,yyy" /> <!-- 提供方调用过程缺省拦截器,将拦截所有service --> <dubbo:provider filter="xxx,yyy"/>
-
用户自定义的过滤器顺序默认会在内置过滤器之后,可以使用如下方式,让自定义的过滤器靠前,即写在前面的先执行
filter="xxx,default"
-
对于默认的过滤器或自动激活的过滤器,如果不想让其生效,可以按照如下配置
filter="-XxxFilter" 如果不想使用所有默认启动的过滤器 filter="-default"
-
如果Provider和Consumer都配置了过滤器,则两边的过滤器不会相互覆盖,而是相互叠加,即都会生效。可以通过剔除的方式避免叠加
ProtocolFilterWrapper
-
ProtocolFilterWrapper
包装类则实现了过滤器组装。//暴露服务 @Override public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException { if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) { return protocol.export(invoker); } return protocol.export(buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER)); } //引用服务 @Override public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException { if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) { return protocol.refer(type, url); } return buildInvokerChain(protocol.refer(type, url), Constants.REFERENCE_FILTER_KEY, Constants.CONSUMER); }
-
buildInvokerChain
方法的逻辑- 通过
ExtensionLoader#getActivateExtension
获取所有过滤器,并遍历 - 使用装饰器增强原有的Invoke,组装过滤器链。
- 通过
-
buildInvokerChain
源码:通过从里到外构造Invoker,假设有AFilter、BFilter、CFilter和Invoker。会按照CFilter、BFilter、AFilter倒序遍历,过滤器链的构建顺序为CFilter->Invoker
、BFilter->CFilter->Invoker
、AFilter->BFilter->CFilter-Invoker
。最终的调用顺序就是AFilter->BFilter->CFilter->Invoker
private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) { Invoker<T> last = invoker; //获取所有的过滤器 List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group); if (!filters.isEmpty()) { // A->B->C->invoke for (int i = filters.size() - 1; i >= 0; i--) { final Filter filter = filters.get(i); final Invoker<T> next = last; //装饰器增强原有的Invoker,组装过滤链 last = new Invoker<T>() { @Override public Class<T> getInterface() { return invoker.getInterface(); } @Override public URL getUrl() { return invoker.getUrl(); } @Override public boolean isAvailable() { return invoker.isAvailable(); } @Override public Result invoke(Invocation invocation) throws RpcException { return filter.invoke(next, invocation); } @Override public void destroy() { invoker.destroy(); } @Override public String toString() { return invoker.toString(); } }; } } return last; }
AccessLogFilter(Provider)
-
AccessLogFilter
日志过滤器,默认会被激活,但是需要配置来开启日志打印1. 将日志输出到本身的log中 <dubbo:protocol accesslog="true" /> 将日志输出到文件 <dubbo:protocol accesslog="accesslog.log" /> 2. 只是某个服务提供者或消费者打印日志 <dubbo:provider accesslog="default"> <dubbo:service accesslog="default">
-
在构造方法中初始化一个
ScheduledThreadPool
,用于accesslog配置为文件时,定时输出日志到文件。打印日志的逻辑在invoke方法中,默认如果不配置accesslog,则不会有任何额外的逻辑public Result invoke(Invoker<?> invoker, Invocation inv) throws RpcException { try { String accesslog = invoker.getUrl().getParameter(Constants.ACCESS_LOG_KEY); if (ConfigUtils.isNotEmpty(accesslog)) { ...省略(获取参数、版本、组、上下文等)... //日志字符串msg String msg = sn.toString(); //如果accesslog是default或者true,则直接打印INFO级别日志,否则输出到文件中 if (ConfigUtils.isDefault(accesslog)) { LoggerFactory.getLogger(ACCESS_LOG_KEY + "." + invoker.getInterface().getName()).info(msg); } else { //放到日志队列,定期输出到文件 log(accesslog, msg); } } } catch (Throwable t) { logger.warn("Exception in AcessLogFilter of service(" + invoker + " -> " + inv + ")", t); } return invoker.invoke(inv); }
-
由于日志队列是
ConcurrentHashSet
即无序的,因此输出到文件也是无序的。又因为是定期刷新到磁盘,所以如果突然宕机会丢失部分日志private final ConcurrentMap<String/*日志文件名*/, Set<String>> logQueue = new ConcurrentHashMap<String, Set<String>>();
ExecuteLimitFilter(Provider)
-
ExecuteLimitFilter
用于限制每个服务中每个方法的最大并发数(或占用线程池的线程数),有接口级别和方法级别的配置方式。如果不配置,默认不限制<dubbo:service interface="xxxx.DemoService" executes="10" /> <dubbo:service interface="xxxx.DemoService" executes="10" > <dubbo:method name="say" executes="10"/> </dubbo:service>
-
为每个URL生成一个IdentityString和RpcStatus对象,存放到ConcurrentHashMap中。RpcStatus用于记录对应的并发数。
ExecuteLimitFilter#invoke
核心逻辑@Override public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { URL url = invoker.getUrl(); String methodName = invocation.getMethodName(); Semaphore executesLimit = null; boolean acquireResult = false; int max = url.getMethodParameter(methodName, Constants.EXECUTES_KEY, 0); //只有配置大于0时才会限制,否则不限制 if (max > 0) { RpcStatus count = RpcStatus.getStatus(url, invocation.getMethodName()); // if (count.getActive() >= max) { /** * http://manzhizhen.iteye.com/blog/2386408 * use semaphore for concurrency control (to limit thread number) * 获取许可 */ executesLimit = count.getSemaphore(max); if (executesLimit != null && !(acquireResult = executesLimit.tryAcquire())) { throw new RpcException("Failed to invoke method " + invocation.getMethodName() + " in provider " + url + ", cause: The service using threads greater than <dubbo:service executes=\"" + max + "\" /> limited."); } } long begin = System.currentTimeMillis(); boolean isSuccess = true; RpcStatus.beginCount(url, methodName); try { Result result = invoker.invoke(invocation); return result; } catch (Throwable t) { isSuccess = false; if (t instanceof RuntimeException) { throw (RuntimeException) t; } else { throw new RpcException("unexpected exception when ExecuteLimitFilter", t); } } finally { RpcStatus.endCount(url, methodName, System.currentTimeMillis() - begin, isSuccess); if (acquireResult) { executesLimit.release(); } } }
-
RpcStatus#getSemaphore
private volatile Semaphore executesLimit; private volatile int executesPermits; public Semaphore getSemaphore(int maxThreadNum) { if(maxThreadNum <= 0) { return null; } if (executesLimit == null || executesPermits != maxThreadNum) { synchronized (this) { if (executesLimit == null || executesPermits != maxThreadNum) { executesLimit = new Semaphore(maxThreadNum); executesPermits = maxThreadNum; } } } return executesLimit; }
ClassLoaderFilter(Provider)
-
ClassLoaderFilter主要目的是临时切换当前工作线程的类加载到接口的类加载器,以便和接口的类加载器的上线文一起工作。当前框架线程的类加载器可能和Invoker接口的类加载器不是同一个,而当前框架线程中又需要获取Invoker的类加载器中的一些Class,为了避免出现
ClassNotFoundException
,需要使用线程上下文类加载@Activate(group = Constants.PROVIDER, order = -30000) public class ClassLoaderFilter implements Filter { @Override public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { ClassLoader ocl = Thread.currentThread().getContextClassLoader(); //切换上下文类加载器 Thread.currentThread().setContextClassLoader(invoker.getInterface().getClassLoader()); try { return invoker.invoke(invocation); } finally { Thread.currentThread().setContextClassLoader(ocl); } } }
-
DubboProtocol#optimizeSerialization
会根据Invoker中配置的optimizer参数获取扩展的自定义序列化处理类,这些外部引用的序列化类在框架的类加载器中肯定没有,因此需要使用Invoker的类加载器获取对应的类
ContextFilter(Provider)
-
ContextFilter
主要记录每个请求的调用上下文(收到的请求上下文信息)。ContextFilter
为每个请求维护一个RpcContext对象,改对象维护两个InternalThreadLocal
//local上下文 private static final InternalThreadLocal<RpcContext> LOCAL = new InternalThreadLocal<RpcContext>() { @Override protected RpcContext initialValue() { return new RpcContext(); } }; //server上下文 private static final InternalThreadLocal<RpcContext> SERVER_LOCAL = new InternalThreadLocal<RpcContext>() { @Override protected RpcContext initialValue() { return new RpcContext(); } };
-
ContextFilter核心逻辑
@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);// Remove async property to avoid being passed to the following invoke chain. } //设置当前请求的上下文Invoker、host、port RpcContext.getContext() .setInvoker(invoker) .setInvocation(invocation) // .setAttachments(attachments) // merged from dubbox .setLocalAddress(invoker.getUrl().getHost(), invoker.getUrl().getPort()); // mreged from dubbox // we may already added some attachments into RpcContext before this filter (e.g. in rest protocol) 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); // pass attachments to result result.addAttachments(RpcContext.getServerContext().getAttachments()); return result; } finally { //清除上下文信息 RpcContext.removeContext(); RpcContext.getServerContext().clearAttachments(); } }
ExceptionFilter(Provider)
-
如果provider中抛出了自定义的异常,而如果consumer不存在此异常类,那么就会导致序列化异常。ExceptionFilter就是防止当异常类不在consumer时序列化失败
-
ExceptionFilter处理规则
- 判断是否是泛化调用(GenericService),如果是则不会处理
- 如果是JDK内部的异常、方法签名上的异常、dubbo的RpcException则直接抛出
- 将不能处理的异常转换为字符串封装为RuntimeException,放在RpcResult中返回
-
源码逻辑
@Override public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { try { Result result = invoker.invoke(invocation); // 如果有异常或者是泛化调用,没有异常或者是泛化调用,则直接返回 if (result.hasException() && GenericService.class != invoker.getInterface()) { try { Throwable exception = result.getException(); // directly throw if it's checked exception if (!(exception instanceof RuntimeException) && (exception instanceof Exception)) { return result; } // directly throw if the exception appears in the signature try { Method method = invoker.getInterface().getMethod(invocation.getMethodName(), invocation.getParameterTypes()); //获取方法参数声明的异常 Class<?>[] exceptionClassses = method.getExceptionTypes(); for (Class<?> exceptionClass : exceptionClassses) { if (exception.getClass().equals(exceptionClass)) { return result; } } } catch (NoSuchMethodException e) { return result; } ...省略错误日志打印... String serviceFile = ReflectUtils.getCodeBase(invoker.getInterface()); String exceptionFile = ReflectUtils.getCodeBase(exception.getClass()); if (serviceFile == null || exceptionFile == null || serviceFile.equals(exceptionFile)) { return result; } // directly throw if it's JDK exception //如果JDK异常就直接抛出,这样判断是不是有些草率?万一consumer和provider的JDK版本不一致 //而抛出的异常在旧JDK版本里没有岂不是找不到? String className = exception.getClass().getName(); if (className.startsWith("java.") || className.startsWith("javax.")) { return result; } // directly throw if it's dubbo exception if (exception instanceof RpcException) { return result; } // otherwise, wrap with RuntimeException and throw back to the client //将自定义异常封装为RuntimeException避免consumer序列化异常 return new RpcResult(new RuntimeException(StringUtils.toString(exception))); } catch (Throwable e) { ...省略错误日志打印... return result; } } return result; } catch (RuntimeException e) { ...省略错误日志打印... throw e; } }
TimeoutFilter(Provider)
-
TimeoutFilter
:记录每个Invoker的调用时间,如果超过了接口设置的timeout之,则会打印警告日志@Override public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { long start = System.currentTimeMillis(); Result result = invoker.invoke(invocation); long elapsed = System.currentTimeMillis() - start; if (invoker.getUrl() != null && elapsed > invoker.getUrl().getMethodParameter(invocation.getMethodName(), "timeout", Integer.MAX_VALUE)) { if (logger.isWarnEnabled()) { logger.warn("invoke time out. method: " + invocation.getMethodName() + " arguments: " + Arrays.toString(invocation.getArguments()) + " , url is " + invoker.getUrl() + ", invoke elapsed " + elapsed + " ms."); } } return result; }
TokenFilter(Provider)
-
如果不想让消费者绕过注册中直连自己,则可以使用令牌验证。即服务提供者在发布自己的服务时会生成令牌并注册到注册中心。消费者必须通过注册中心才能获取有令牌的URL。
1. 随机Token令牌,使用UUID生成 <dubbo:provider interface="xxx.DemoService" token="true"/> <dubbo:service interface="xxx.DemoService" token="true"/> <dubbo:protocol interface="xxx.DemoService" token="true"/> 2. 固定token令牌,相当于密码 <dubbo:provider interface="xxx.DemoService" token="123456789"/> <dubbo:service interface="xxx.DemoService" token="123456789"/> <dubbo:protocol interface="xxx.DemoService" token="123456789"/>
-
令牌的工作流程
- 消费者从注册中心获取服务提供者包含令牌的URL
- 消费者RPC调用设置令牌(附加信息中)
- 服务提供者认证令牌
-
源码
@Override public Result invoke(Invoker<?> invoker, Invocation inv) throws RpcException { //服务提供者的token String token = invoker.getUrl().getParameter(Constants.TOKEN_KEY); if (ConfigUtils.isNotEmpty(token)) { Class<?> serviceType = invoker.getInterface(); Map<String, String> attachments = inv.getAttachments(); //客户端请求带过来的token String remoteToken = attachments == null ? null : attachments.get(Constants.TOKEN_KEY); if (!token.equals(remoteToken)) { throw new RpcException("Invalid token! Forbid invoke remote service " + serviceType + " method " + inv.getMethodName() + "() from consumer " + RpcContext.getContext().getRemoteHost() + " to provider " + RpcContext.getContext().getLocalHost()); } } return invoker.invoke(inv); }
TpsLimitFilter(Provider)
-
TpsLimitFilter
主要用于服务提供者端的限流。TpsLimitFilter
并没有在META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Filter
文件中配置。如果需要使用,需要自己配置。1. 每次发放1000个令牌 <dubbo:parameter key="tps" value="1000"> 2. 令牌刷新时间间隔为1秒,不配置,默认是60s <dubbo:parameter key="tps.interval" value="1000">
-
TpsLimitFilter
限流是基于令牌,同一时间内只分配N个令牌(使用AtomicInteger实现),每个请求过来都会消耗一个令牌,消耗完后面的请求会被拒绝。主要委托DefaultTPSLimiter
实现逻辑@Activate(group = Constants.PROVIDER, value = Constants.TPS_LIMIT_RATE_KEY) public class TpsLimitFilter implements Filter { private final TPSLimiter tpsLimiter = new DefaultTPSLimiter(); @Override public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { if (!tpsLimiter.isAllowable(invoker.getUrl(), invocation)) { throw new RpcException( "Failed to invoke service " + invoker.getInterface().getName() + "." + invocation.getMethodName() + " because exceed max service tps."); } return invoker.invoke(invocation); } }
ActiveLimitFilter(Consumer)
-
ActiveLimitFilter
是限制消费端的并发数,类似服务提供者端的ExecuteLimitFilter
<dubbo:service interface="xxx.DemoService" actives="10"/> <dubbo:reference interface="xxx.DemoService" actives="10" /> <dubbo:reference interface="xxx.DemoService" actives="10" > <dubbo:method name="say" actives="10" /> </dubbo:reference>
-
如果达到限流阈值,并不是直接抛出异常(与服务端的
ExecuteLimitFilter
不一样),而是先等到直接超时,因为请求是有timeout属性的。当并发数达到阈值时,会先加锁抢占当前接口的RpcStatus对象,然后通过wait方法进行等待。@Override public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { URL url = invoker.getUrl(); String methodName = invocation.getMethodName(); int max = invoker.getUrl().getMethodParameter(methodName, Constants.ACTIVES_KEY, 0); RpcStatus count = RpcStatus.getStatus(invoker.getUrl(), invocation.getMethodName()); if (max > 0) { long timeout = invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.TIMEOUT_KEY, 0); long start = System.currentTimeMillis(); long remain = timeout; int active = count.getActive(); if (active >= max) { synchronized (count) { // 大于阈值就等待,while是避免虚假唤醒,这里使用信号量不是更好? while ((active = count.getActive()) >= max) { try { count.wait(remain); } catch (InterruptedException e) { } long elapsed = System.currentTimeMillis() - start; //先等到超时,再抛出异常(这点与服务端的不一样) remain = timeout - elapsed; if (remain <= 0) { throw new RpcException("Waiting concurrent invoke timeout in client-side for service: " + invoker.getInterface().getName() + ", method: " + invocation.getMethodName() + ", elapsed: " + elapsed + ", timeout: " + timeout + ". concurrent invokes: " + active + ". max concurrent invoke limit: " + max); } } } } } try { long begin = System.currentTimeMillis(); RpcStatus.beginCount(url, methodName); try { Result result = invoker.invoke(invocation); RpcStatus.endCount(url, methodName, System.currentTimeMillis() - begin, true); return result; } catch (RuntimeException t) { RpcStatus.endCount(url, methodName, System.currentTimeMillis() - begin, false); throw t; } } finally { if (max > 0) { //唤醒另一个线程 synchronized (count) { count.notify(); } } } }
ConsumerContextFilter(Consumer)
ConsumerContextFilter
与ContextFilter
配合使用,在链式调用中(A->B->C->D),收到请求时,当前节点可以被看作是一个服务提供者,在ContextFilter
上设置上下文。当发起请求到下一个服务时,当前服务变为一个消费者,由ConsumerContextFilter
设置上下文。主要逻辑为
- 设置当前请求上下文
- 服务调用,清楚Response上下文,然后继续调用下一个节点
- 清楚上下文信息。每次调用完成,都会把附加上下文清楚
DeprecatedFilter(Consumer)
- 检查所有调用,如果方法已经设置了过时,在会打印ERROR日志,只会打印一次(接口名+方法名,Set保存)
<dubbo:parameter deprecated ="true" />
-
源码
private static final Set<String> logged = new ConcurrentHashSet<String>(); @Override public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { //为什么不是方法签名呢? 方法名可能重复呀 String key = invoker.getInterface().getName() + "." + invocation.getMethodName(); if (!logged.contains(key)) { logged.add(key); if (invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.DEPRECATED_KEY, false)) { LOGGER.error("The service method " + invoker.getInterface().getName() + "." + getMethodSignature(invocation) + " is DEPRECATED! Declare from " + invoker.getUrl()); } } return invoker.invoke(invocation); }
FutureFilter(Consumer)
-
FutureFilter
主要实现框架在调用前后出现异常时,触发调用用配置的回调方法。oninvoke只对异步调用有效<bean id="callback" class="Xxx.CallBack"> <dubbo:reference id="demoService" interface="xxx.DemoService"> <dubbo:method name="bar" onreturn="callback.onreturn" onthrow="callback.onthrow" oninvoke="callback.oninvoke" /> </dubbo:referenc>