Dubbo服务调用扩展点学习及实践

Dubbo有哪些服务调用扩展点?

dubbo filter 改写 result dubbo filter扩展_dubbo


在服务调用过程中,主要分为两部分,一部分是消费者端链路,另一部分是服务端链路。

  • 消费者端:
  • 首先由Stub将请求封装成Invocation对象,将Invocation对象传递给ClusterFilter。
  • ClusterFilter是一个扩展点,主要做消费者端的请求拦截,实现请求预处理、参数转换、请求日志记录、限流等操作。
  • 随后进入Cluster中,Cluster 是第二个扩展点,主要进行集群调用逻辑的决策,如快速失败模式、安全失败模式等决策都是在此阶段进行的。
  • Cluster首先调用Directory获取所有可用的服务端地址。
  • Directory调用Router对服务端地址进行筛选。此阶段主要是从全量的地址信息中筛选出本次调用允许调用到的目标,如基于打标的流量路由就是在此阶段进行的。
  • Cluster从Directory中获取到本次可用的服务端地址后,会调用LoadBalance选出一个可调用地址,如随机调用、一致哈希、轮询等策略在该步骤实现。
  • Cluster 获得目标的 Invoker 以后将 Invocation 传递给对应的 Invoker,并等待返回结果,如果出现报错则执行对应的决策(如快速失败、安全失败等)

其中Directory、Router、LoadBalance均为扩展点。

  • 获取到带有目标地址的Invoker后,会再次进入Filter阶段,Filter同样是扩展点,进行选址后的一些处理。该阶段的Filter与服务量级一致,如果没有特殊需求,推荐使用第二步的ClusterFilter做处理,提高性能。
  • 最后 Invocation 会被通过网络发送给服务端。
  • 服务端:
  • 服务端接受到请求后,由协议端构建出Invocation,发送给Filter。
  • Filter做服务调用前的一些预处理,如日志记录、限流、参数校验等。
  • 最后由动态代理做真正的服务调用。

可以看到,在整个服务调用链路中,几乎每个阶段都是可扩展点。在实际应用中,根据业务需求可以对不同阶段进行扩展。体现了框架的可扩展性。

如何扩展Filter

想要实现扩展Filter,我们可以看看Dubbo框架中现有的Filter是如何实现的。

dubbo filter 改写 result dubbo filter扩展_java_02


主要有两个地方,首先需要创建类并实现Filter接口,然后使用@Activate注解生效该拦截器。

@Activate(group = "customer")
public class MyDemoFilter implements Filter {
    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
    	System.out.println("----进入自定义拦截----");
        System.out.println(JSONObject.toJSONString(invocation));
        return invoker.invoke(invocation);
    }
}

我们新建一个类MyDemoFilter实现Filter接口,需要重写invoke方法,该方法有两个参数,其中Invocation就是上文提高的请求封装后的类;invoker则是服务端提供的服务的封装,它提供invoke方法,用于向服务端发起请求。

在类上添加@Activate注解,激活拦截器。@Activate类图如下:

dubbo filter 改写 result dubbo filter扩展_java_03


它有5个方法:

  • group()用于指定该拦截器生效的位置,需要在 Consumer 侧生效,group值为{CommonConstants.CONSUMER};需要在 Provider 侧生效,group值为{CommonConstants.PROVIDER};
  • value()用于指定该拦截器在什么情况下会被调用,value=“{demo}”,表示当请求参数中存在key为demo的请求是,会调用该拦截器。
  • order()指定拦截器调用顺序,值越小越先被调用。
  • before() 和after() 也是用于指定调用顺序的,before的值为在该拦截器之前调用的拦截器列表,after的值为在该拦截器之后调用的拦截器列表,这两个方法在2.7.0后被视为过时的方法,不推荐使用。

最后我们需要在src/main/resource目录下创建META-INFO/dubbo/org.apache.dubbo.rpc.Filter文件,在文件中添加内容:
myDemoFilter=com.juffy.dubboconsumer.filter.MyDemoFilter

dubbo filter 改写 result dubbo filter扩展_服务端_04


dubbo filter 改写 result dubbo filter扩展_学习_05


dubbo filter 改写 result dubbo filter扩展_学习_06


Dubbo支持从这三个目录下加载扩展。

跟踪源码后,可以看到拦截器由DefaultFilterChainBuilder类加载,该类的buildInvokerChain()方法,调用ExtensionLoader.getExtensionLoader()方法加载拦截器,传入的参数为Filter.class。

dubbo filter 改写 result dubbo filter扩展_拦截器_07


dubbo filter 改写 result dubbo filter扩展_dubbo_08


而文件名称是调用的class.getName()方法。即org.apache.dubbo.rpc.Filter 为文件名称。

dubbo filter 改写 result dubbo filter扩展_服务端_09

最后在loadDirectory()方法中,将目录 和文件名称拼接,组成完整的文件路径。

dubbo filter 改写 result dubbo filter扩展_拦截器_10


这就是为什么文件路径为META-INFO/dubbo/org.apache.dubbo.rpc.Filter。

同样,也可以放在META-INFO/services/org.apache.dubbo.rpc.Filter或者META-INFO/dubbo/internal/org.apache.dubbo.rpc.Filter。

不过有个疑问,找了许多文档,为什么只有配置文件的方式,不能支持注解形式吗???

接下来我们将消费者、调用者运行起来, 在浏览器中访问http://localhost:8091/consumer/user/2。

可以在消费者的输出中看到输出:

dubbo filter 改写 result dubbo filter扩展_拦截器_11


Dubbo还支持许多其他扩展,比如集群扩展、路由扩展等,以后再一一学习并记录。