27.各个filter分析以及和spring cloud对比

主要阐述dubbo rpc的filter的实现,包括作用,用法,原理,与Spring Cloud在这些能力的对比。

共提供了多少个?是哪些?发布时默认装配了哪些给他自身的扩展点机制?

从类与接口关系分析的结果文档中可以看到共20个:
241 Filter
–241.1 CacheFilter
–241.2 MonitorFilter
–241.3 AccessLogFilter
–241.4 ActiveLimitFilter
–241.5 ClassLoaderFilter
–241.6 CompatibleFilter
–241.7 ConsumerContextFilter
–241.8 ContextFilter
–241.9 DeprecatedFilter
–241.10 EchoFilter
–241.11 ExceptionFilter
–241.12 ExecuteLimitFilter
–241.13 GenericFilter
–241.14 GenericImplFilter
–241.15 TimeoutFilter
–241.16 TokenFilter
–241.17 TpsLimitFilter
–241.18 FutureFilter
–241.19 TraceFilter
–241.20 ValidationFilter

从发布的jar中的META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Filter中发现除了TpsLimitFilter之外,其余的都装上了。
cache=com.alibaba.dubbo.cache.filter.CacheFilter
validation=com.alibaba.dubbo.validation.filter.ValidationFilter
echo=com.alibaba.dubbo.rpc.filter.EchoFilter
generic=com.alibaba.dubbo.rpc.filter.GenericFilter
genericimpl=com.alibaba.dubbo.rpc.filter.GenericImplFilter
token=com.alibaba.dubbo.rpc.filter.TokenFilter
accesslog=com.alibaba.dubbo.rpc.filter.AccessLogFilter
activelimit=com.alibaba.dubbo.rpc.filter.ActiveLimitFilter
classloader=com.alibaba.dubbo.rpc.filter.ClassLoaderFilter
context=com.alibaba.dubbo.rpc.filter.ContextFilter
consumercontext=com.alibaba.dubbo.rpc.filter.ConsumerContextFilter
exception=com.alibaba.dubbo.rpc.filter.ExceptionFilter
executelimit=com.alibaba.dubbo.rpc.filter.ExecuteLimitFilter
deprecated=com.alibaba.dubbo.rpc.filter.DeprecatedFilter
compatible=com.alibaba.dubbo.rpc.filter.CompatibleFilter
timeout=com.alibaba.dubbo.rpc.filter.TimeoutFilter
trace=com.alibaba.dubbo.rpc.protocol.dubbo.filter.TraceFilter
future=com.alibaba.dubbo.rpc.protocol.dubbo.filter.FutureFilter
monitor=com.alibaba.dubbo.monitor.support.MonitorFilter

这些filter都有什么作用?如何使用?实现原理是什么?Spring Cloud是否也提供了这些能力?有什么差异?

CacheFilter

作用

缓存调用结果,比如配置在consumer端, 比如我们通过id查某个用户的信息,对于特定的一个id,在consumer端第一次调用时会给provider端发请求,后面再调用时,直接用consumer端缓存的结果返回,你不再发请求给provider端。

使用方式

consumer侧的配置:

<dubbo:reference id=”userService” interface=”org.simonme.dubbo.demo.provider.service.UserService” filter=”cache”>
<dubbo:parameter key=”cache” value=”lru” />

能支持的全部cache定义在META-INF/dubbo/internal/com.alibaba.dubbo.cache.CacheFactory 当然你也可以遵循dubbo扩展点机制进行扩展。
默认提供三种:
threadlocal=com.alibaba.dubbo.cache.support.threadlocal.ThreadLocalCacheFactory
lru=com.alibaba.dubbo.cache.support.lru.LruCacheFactory
jcache=com.alibaba.dubbo.cache.support.jcache.JCacheFactory

实现原理

缓存的key是你远程方法调用时传递的所有参数按规则组装成字符串作为key。
具体规则就是: 基本类型 直接拼接,复合类型转成json字符串后再拼接。

实现原理也不是很复杂,根据invoker的url找到其对应的cache对象,再跟据上述缓存的key找到缓存的结果。
有个不是太要紧的小问题,因为是根据invoker的url找到其对应的cache对象的,又因为invoker的url中含有remote.timestamp参数,所以你如果启用了consumer侧的缓存,consumer一直在服务状态,此时provider服务做了重启,那么consumer侧的缓存失效,会重新调用provider端。

ThreadLocalCache 是ThreadLocal配合HashMap实现
LRUCache 是继承自LinkedHashMap,同时结合ReentrantLock实现的线程安全的lru cache(最近最少使用),LinkedHashMap自带lru性质,通过构造参数控制,默认是fifo。
jache是封装的JCache API (JSR 107)。

与Spring Cloud对比

Spring Cloud提供了consumer侧的缓存能力,Hystrix组件支持用requestCache.enabled配置是否启用缓存,也支持用cacheKeyMethod注解指定getkey方法。

ValidationFilter

作用

在consumer和provider端提供了校验能力

使用方式

假设你要对consumer端进行校验,在配置文件中配置如下:

<dubbo:reference id="userService" interface="org.simonme.dubbo.demo.provider.service.UserService" filter="validation">
    <dubbo:parameter key="validation" value="JValidator" />
    
    <!-- 配置一个实现了javax.validation.spi.ValidationProvider<T>接口校验器 -->
    <dubbo:parameter key="jvalidation" value="org.hibernate.validator.HibernateValidator" />
</dubbo:reference>

filter要是配置多个的话,用逗号拼接,但是逗号前后不能有空格。
此处使用了hibernate的validator ,在你需要校验的接口方法上加校验注解即可,示例如下:

public User queryUser(@Range(min=0,message="用户id值不能小于0")int id);

当consumer端调用时传递了校验不通过的参数时,会收到ConstraintViolationException的异常。

实现原理

dubbo对接了javax.validation.Validation,hibernate等都有对其对接的实现,按需使用即可。也就是说dubbo自己不做具体校验的事情。

与Spring Cloud对比

spring在很早就支持validator。

EchoFilter

作用

在provider端提供回声服务的服务端的实现。

使用方式

这个filter略有特殊,无需在provider端的dubbo:service标签的filter中去配置,只要你在consumer做了echo回声调用,他都会产生作用,调试的时候也能看到能走到EchoFilter中。

consumer端的示例代码:

public class HelloClientTest
{
    @Autowired
    private HelloService helloService;

    @SuppressWarnings("static-access")
    @Test
    public void testSayHello()
    {
        System.out.println(((EchoService)helloService).$echo("aaaa"));
    }
}

就是把你的service类强转成EchoService,至于为什么能强转,可以参见之前写的文章 reference bean发起调用

实现原理

直接看EchoFilter代码,很简单,不再多说。

与Spring Cloud对比

Spring Cloud貌似没有这个能力。

GenericFilter

GenericImplFilter

这两个filter上篇文章已经阐述了。

TokenFilter

作用

官方文档说法是:

通过令牌验证在注册中心控制权限,以决定要不要下发令牌给消费者,可以防止消费者绕过注册中心访问提供者,另外通过注册中心可灵活改变授权方式,而不需修改或升级提供者。

使用方式

provider端:在provider上配置token值。 TokenFilter会在provider侧校验。

<dubbo:service interface="org.simonme.dubbo.demo.provider.service.UserService" ref="m00001.app001.xx.userService" timeout="600000" token="123456">

consumer端可以用编程的方式获取后塞,

RpcContext.getContext().setAttachment("token", "a37b6115-c171-43cd-b65c-38b636ee96cc");

或者通过配置parameter,

<dubbo:parameter key="token" value="123456" />

或者啥都不要处理,默认consumer会从provider服务url中解析到。provider的url中会含有token字段。

实现原理

如果 token 配置的是true, 那么在provider export服务时,ServiceConfig会生成UUID,这个其实不是由注册中心生成的。
当然token也支持配置固定密码。
比对过程:

if (!ConfigUtils.isEmpty(token)) {
    if (ConfigUtils.isDefault(token)) {// 是 true或者default字段串就是表示默认
        map.put(Constants.TOKEN_KEY, UUID.randomUUID().toString());
    } else {
        map.put(Constants.TOKEN_KEY, token);
    }
}

consumer端如果没有通过上述代码的方式或者parameter配置的方式传送token,那么consumer端会在调用时,先将从注册中心拿到的provider端的url中部分参数转换成attachment给consumer端用,这个部分参数就包括token。具体代码在DubboInvoker中,如下:

public DubboInvoker(Class<T> serviceType, URL url, ExchangeClient[] clients, Set<Invoker<?>> invokers) {
    super(serviceType, url, new String[]{Constants.INTERFACE_KEY, Constants.GROUP_KEY, Constants.TOKEN_KEY, Constants.TIMEOUT_KEY});// 此处会调用父类方法进行需要的参数从url转到attachment中
    this.clients = clients;
    // get version.
    this.version = url.getParameter(Constants.VERSION_KEY, "0.0.0");
    this.invokers = invokers;
}

当provider端使用注册中心,consumer试图不带token进行直接消费时,会被拒绝。 当consumer端也是连注册中心时,哪怕不送显式送token(实际上dubbo会自动送)也可以正常调用。但是如果consumer端用了注册中心,且显式送了token,那么就要送对。否则报错。

与Spring Cloud对比

Spring Cloud的注册中心eureka也是支持密码验证的。

AccessLogFilter

作用

用在provider端打印rpc请求日志,支持打到指定文件,支持异步。

使用方式

在provider侧配置名为accesslog的filter,若需要指定路径,则将accesslog参数设置成具体路径即可,默认需要将其配置成true。

<dubbo:service interface="org.simonme.dubbo.demo.provider.service.UserService" ref="m00001.app001.xx.userService" filter="accesslog" 
    timeout="600000" token="123456">
    <dubbo:parameter key="accesslog" value="true" />

</dubbo:service>
实现原理

参见AccessLogFilter 代码,比较简单。

与Spring Cloud对比

zuul网关也支持。
zuul.debug.request=true #如果设置了这个,默认所有的请求都会debug
zuul.include-debug-header: true #打印头
未设置zuul.debug.request=true,可以用zuul_host:zuul_port/路径?debug=true debug你的指定请求

ActiveLimitFilter

作用

在consumer端实现并发数控制,能支持到方法级。

使用方式

在consumer侧配置

<dubbo:reference interface="com.foo.BarService">
    <dubbo:method name="sayHello" actives="10" />
</dubbo:service>
实现原理

ConcurrentHashMap 配合 AtomicInteger AtomicLong完成。相关代码参见 ActiveLimitFilter 与 RpcStatus。

与Spring Cloud对比

Spring Cloud也支持。

ExecuteLimitFilter 与之类似,只是用在了provider端。

ClassLoaderFilter

作用

保持 调用下层invoker前后的ClassLoader一致

ContextFilter

作用

在provider端,对一些dubbo自己使用的保留key进行过滤,防止别人误传。

实现原理

参见代码即可,比较简单。

与Spring Cloud对比

N/A

ConsumerContextFilter

作用

在consumer端,进行一些参数设置,诸如本端地址,对端地址等。

使用方式

无需配置,consumer侧默认使用。
在dubbo中,对于这些filter如果在META-INF中配置了且filter类的注解@Activate上没有配置value值,那么就是默认使用。 可以参见com.alibaba.dubbo.common.extension.ExtensionLoader.isActive(Activate, URL)方法实现。

实现原理

参见代码即可,比较简单。

与Spring Cloud对比

N/A

ExceptionFilter

作用

在provider端,对调用异常进行选择性进行包装。
非受检异常直接抛出,jdk的异常直接抛出,异常类与接口类在一个jar包内的直接抛出,是服务接口方法自己声明的要throw的异常直接抛出。
其余包装成受检异常放到RpcResult中返回。

使用方式

无需配置,provider侧默认使用。

实现原理

参见代码即可,比较简单。

与Spring Cloud对比

N/A

DeprecatedFilter

作用

对于DEPRECATED的方法打一行错误日志。 是配置在consumer端,没太明白他的实际作用,既然要在consumer端配置DEPRECATED,还要打日志做啥呢?consumer端就知道了啊。 有点不解。 或许就是为了标注,这是一个废弃的调用吧。

使用方式

consumer端配置。

<dubbo:reference id="userService" interface="org.simonme.dubbo.demo.provider.service.UserService" filter="deprecated" >  
    <dubbo:method name="queryUser">
        <dubbo:parameter key="deprecated" value="true" />
    </dubbo:method>
</dubbo:reference>
实现原理

参见代码即可,比较简单。

与Spring Cloud对比

Spring Cloud貌似没有这个能力。

CompatibleFilter

作用

兼容适配器,能对结果返回值做一些类型转换,注入基本类型到装箱类型的互转,复合类型到序列化值的转换(依赖你配置的序列化类型)等。

使用方式

在consumer配置的filter上加上compatible即可。

实现原理

参见CompatibleFilter代码即可,比较简单。

TimeoutFilter

作用

用在provider侧,对超时的服务调用,打一个警告日志。

使用方式

无需配置,默认生效。

实现原理

参见TimeoutFilter代码即可,比较简单。

TraceFilter

作用

用在provider侧。

使用方式

无需配置,默认启用这个filter,但是要真正trace,需要telnet管理台,给其发指令,才能真正trace。支持指定接口,指定方法,指定最大trace多少次。

实现原理

trace的内容如下:

if (count < max) {
    String prompt = channel.getUrl().getParameter(Constants.PROMPT_KEY, Constants.DEFAULT_PROMPT);
      channel.send("\r\n" + RpcContext.getContext().getRemoteAddress() + " -> "
                                        + invoker.getInterface().getName()
                                        + "." + invocation.getMethodName()
                                        + "(" + JSON.toJSONString(invocation.getArguments()) + ")" + " -> " + JSON.toJSONString(result.getValue())
                                        + "\r\nelapsed: " + (end - start) + " ms."
                                        + "\r\n\r\n" + prompt);
}

FutureFilter

作用

用在consumer侧。dubbo的事件机制支持oninvoke、onreturn、onreturn事件监听,就是靠这个filter完成对接。

使用方式

参见dubbo的事件机制

实现原理

比较简单,参见FutureFilter代码。或者参见Dubbo源码分析—-过滤器之FutureFilter

MonitorFilter

作用

consumer,provider侧都可用。 会将服务的耗时,并发数等送给监控服务。

使用方式

filter默认启用,但是需要配置后才能触发监控。 配置dubbo:monitor。

实现原理

比较简单,参见MonitorFilter。 具体收集监控动作由MonitorService接口实现完成。dubbo自带了DubboMonitor实现。收集的数据暂时同步擦欧洲放在一个ConcurrentHashMap中,再由ScheduledExecutorService定时异步发送。

与Spring Cloud对比

Spring Cloud有专门的组件干这个。