Dubbo源码阅读——过滤器链

文章目录

      • 1. 过滤器的使用
        • 1.1 @Activate注解启用过滤器
        • 1.2 xml配置启用过滤器
          • 1.2.1 配置方式
          • 1.2.2 过滤器顺序
          • 1.2.3 剔除过滤器
          • 1.2.4 过滤器的叠加
      • 2.Dubbo内置过滤器
      • 3.Dubbo过滤器实现原理
        • 3.1 加载Protocol
        • 3.2 组装过滤器 ProtocolFilterWrapper#buildInvokerChain()
      • 4.总结

对于rpc框架,过滤器肯定是需要支持的。Dubbo中的过滤器和web应用的过滤器概念是一样的,提供服务调用前后插入自定义逻辑的途径,可针对所有调用,也可针对部分服务的调用,Dubbo提供灵活的配置。过滤器是整个Dubbo框架的重要组成部分,Dubbo的很多功能都是基于过滤器扩展而来的。Dubbo的过滤器同样支持通过SPI机制扩展。需要注意的是,对于某一支服务,每一次调用都会执行对应的过滤器,所以扩展的时候要考虑对性能的影响。
注:本文Dubbo版本为 2.6.5

1. 过滤器的使用

Dubbo已经有很多的内置过滤器,并且大多是默认启用的。不管是已实现的过滤器,自定义扩展的过滤器,启用方式有两种。

  • 使用@Activate注解默认启用
  • 在配置文件中配置

1.1 @Activate注解启用过滤器

@Activate注解在应用启动时会加载并包装到Invoker中,可以通过配置group属性控制过滤器对Provider或Consumer生效,还可以通过order参数设置过滤器执行顺序,order越小,越先执行。例如:
@Activate(group = {Constants.CONSUMER, Constants.PROVIDER}, value = Constants.CACHE_KEY, order = -10000)

1.2 xml配置启用过滤器

1.2.1 配置方式
	
    <dubbo:provider filter="aaa,bbb"/>
    
    <dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService" filter="aaa,bbb"/>
    
    <dubbo:consumer filter="aaa,bbb"/>
    
    <dubbo:reference interface="com.alibaba.dubbo.demo.DemoService" filter="aaa,bbb"/>
1.2.2 过滤器顺序

用户自定义过滤器默认在框架内置过滤器之后执行,我们可以使用 filter = "aaa,default"方式调整过滤器顺序。
写在前面的过滤器比后面的先执行。

1.2.3 剔除过滤器

可以使用filter = "-xxxFilter"使xxxFilter不生效。filter = "-default"可剔除所有默认过滤器。

1.2.4 过滤器的叠加

如果服务端和消费端都配置了同一个过滤器,两边都会执行该过滤器。不会覆盖。

2.Dubbo内置过滤器

Dubbo实现的内置过滤器有十几种,包括服务端过滤器和消费端过滤器,以及特殊的MonitorFilter,它会在服务暴露和服务引用是同时包装到Invoker。
下面是部分过滤器功能列表:

过滤器名 使用方 作用
AccessLogFilter P 打印每一次请求的日志
ActiveLimitFilter C 用于限制消费端对服务端的最大并行数
ExecuteLimitFilter P 用于限制服务端的最大并行数
ClassLoaderFilter P 用于切换不同线程的类加载器,服务调用完后会还原回去
ConsumerContextFilter C 为消费端把一些上下文信息设置到当前线程的RpcContext对象中,主要是invocation,local host,remote host
ContextFilter P 为服务端把一些上下文信息设置到当前线程的RpcContext对象中
DeprecatedFilter C 如果被调用的方法标记为被弃用,DeprecatedFilter 会记录一条错误信息
EchoFilter P 用于回声测试
ExceptionFilter P 统一异常处理,防止序列化失败
GenericFilter P 服务端实现泛化调用,实现序列化检查和处理
GenericImplFilter C 消费端实现泛化调用,实现序列化检查和处理
TimeoutFilter P 如果服务调用超时,记录告警日志
TokenFilter P 服务端下发令牌给消费端,用于防止消费端绕过注册中心,直连服务端
TpsLimitFilter P 用于服务端限流
FutureFilter C 在发起invoke调用,或得到返回值,或出现异常时触发回调事件
MonitorFilter P & C 监控并统计所有接口的调用情况,如成功、失败、耗时。后续DubboMonitor会把该过滤器收集到的数据定时发送到Dubbo-Monitor服务上

3.Dubbo过滤器实现原理

Dubbo过滤器是在服务暴露(见https://blog.csdn.net/weixin_41172473/article/details/103301463)或服务引用(见https://blog.csdn.net/weixin_41172473/article/details/103524664)的时候加载并通过装饰器模式层层包装到Invoker上。
下面以服务端为例,探究服务端过滤器链的组装过程,消费端同理:

3.1 加载Protocol

服务暴露有一个重要过程就是调用Protocol接口的export方法生成Exporter,将服务暴露出去,而这个protocol实例像Dubbo的许多组件一样,是通过ExtensionLoader加载的(关于Dubbo扩展加载机制见https://blog.csdn.net/weixin_41172473/article/details/102905655),
debug一下ExtensionLoader加载Protocol的过程:
Dubbo源码阅读——过滤器链_第1张图片
可以看到,最后返回出去的不是DubboProtocol类型的实例,而是将DubboProtocol类型的实例包装成ProtocolFilterWrapper类型,当然还可能会有其他包装类型,比如ProtocolListenerWapper。
加载完成后,服务暴露过程调用protocol.export()方法,就会走ProtocolFilterWrapper#export()方法,这里面就封装了过滤器组装的逻辑。

3.2 组装过滤器 ProtocolFilterWrapper#buildInvokerChain()

在看组装过滤器代码之前,先看看Filter接口:

@SPI
public interface Filter {
    Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException;
}

这里的invoke方法的实现,其实就是织入自己的过滤器逻辑,然后调用Invoker的invoke方法,返回结果也是invoke方法的结果。
回到组装过滤器逻辑:
ProtocolFilterWrapper#export()方法调用buildInvokerChain()方法将Filter做层层包装,导出带有过滤器逻辑的Invoker。

	@Override
    public <T> Exporter<T> export(Invoker<T> invoker) throws RpcException {
        if (Constants.REGISTRY_PROTOCOL.equals(invoker.getUrl().getProtocol())) {
            return protocol.export(invoker);
        }
        // 暴露带有过滤器逻辑的Invoker
        return protocol.export(buildInvokerChain(invoker, Constants.SERVICE_FILTER_KEY, Constants.PROVIDER));
    }
    
	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()) {
        	// 倒序遍历
            for (int i = filters.size() - 1; i >= 0; i--) {
                final Filter filter = filters.get(i);
                final Invoker<T> next = last;
                last = new Invoker<T>() {
					//这里省略其他Override方法
					
                    @Override
                    public Result invoke(Invocation invocation) throws RpcException {
                    	// 调用里层filter的invoke方法
                        return filter.invoke(next, invocation);
                    }

                };
            }
        }
        return last;
    }

代码比较简单,就是循环包装过滤器,Filter接口的invoke方法只是在调用真实Invoker.invoke()方法前后加入自己的逻辑,然后返回真实invoke调用的结果。这里过滤器链的顺序比较有意思,通过反向遍历filterList,组装到Invoker上,保证最后调用过滤器的顺序是正的,类似入栈和出栈。

4.总结

Dubbo通过装饰器模式实现过滤器链,原理重点是一下三个过程:

  1. 在加载Protocol的时候将Protocol包装成ProtocolFilterWrapper,在这里面加上组装过滤器的逻辑。
  2. 当服务暴露调用protocol.export()或者服务引用调用protocol.refer()时,执行组装过滤器的逻辑。
  3. Filter通过反复增强Invoker.invoke(),在其调用前后加入各个过滤器自己的逻辑,实现过滤器链的织入。

Dubbo内置的过滤器很多,有些默认启用,有些需要我们配置了才能生效,知道了过滤器的配置方式和实现原理,再去看具体每个过滤器的源码就很容易理解了,自己扩展一些过滤器也是可以的。

你可能感兴趣的:(Dubbo)