Dubbo 的 Filter

1,145 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第8天,点击查看活动详情

Dubbo 的 Filter

查看官网的介绍:Filter

Dubbo 使用过滤器链机制实现服务提供方和服务消费方调用过程的拦截,然后就可以实现功能的包装或扩展。

注意:每次远程方法执行,该拦截都会被执行,请注意对性能的影响。

Dubbo 内置的 Filter

Dubbo 内置的默认过滤器在dubbo-rpc-api/src/main/resources/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Filter中定义的。

消费者端 Filter

1.ConsumerContextFilter

这是服务消费端的 Context 上下文 Filter,是默认就会执行的。

主要是通过 RpcContext 设置上下文信息,后续业务可以获取 RpcContext 将参数传递到服务提供者使用。

2.MonitorFilter

和服务监控相关的 FIlter,需要配置才生效。

收集每一次调用的信息,并将信息保存在内存中,定时的将信息上报给 monitor 服务。

3.FutureFilter

执行事件通知,调用前 oninvoke,同步调用后/异步调用完成后 onreturn/onthrow 的方法实现。

4.ActiveLimitFilter

用来客户端限流,控制每个消费者调用指定方法的最大并发数,主要通过 actives 参数进行配置,在每次调用的时候活跃数+1,调用结束活跃数-1。

提供端 Filter

1.ContextFilter

这是服务提供者端的 Context 上下文 Filter,是默认就会执行的。

和 ConsumerContextFilter 是相互对应的,这里可以获取到 Consumer 设置的 attachments 属性值。

2.EchoFilter

回响测试,用来验证网络是否通畅,没有业务操作。

3.ExecuteLimitFilter

服务端限流,这里主要是对 Provider 的调用进行限流。

4.ExceptionFilter

异常处理,主要是针对于全局的异常进行处理。

自定义 Filter

1.实现指定接口

实现自定义的 FIlter,需要实现org.apache.dubbo.rpc.Filter接口。

代码如下:

package it.com.dubbo.filter;

import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.Filter;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.Result;
import org.apache.dubbo.rpc.RpcException;

@Activate(group = CommonConstants.CONSUMER)
public class LogFilter implements Filter {
    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        StopWatch stopWatch = new StopWatch();
        //拿到调用的服务url
        System.out.println("开始调用:" + invoker.getUrl());
        stopWatch.start();
        Result result = invoker.invoke(invocation);
        stopWatch.stop();
        // 输出调用结果
        System.out.println("调用结果:" + result.getValue());
        //输出调用耗时
        System.out.println("调用耗时(秒):" + stopWatch.getTotalTimeSeconds());
        return result;
    }
}

这里需要注意的就是@Activate注解的配置,该注解用来激活该 Filter。

  • 如果需要在服务提供端,让该 Filter 生效,则需要指定 @Activate(group = CommonConstants.PROVIDER)
  • 如果需要在服务消费端,让该 Filter 生效,则需要指定 @Activate(group = CommonConstants.CONSUMER)
  • order 属性,用户指定顺序,值越小,越先执行。
  • before 属性,用于指定执行顺序,before 指定的过滤器在该过滤器之前执行。
  • after 属性,用于指定执行顺序,after 指定的过滤器在该过滤器之后执行。

2.SPI 方式配置 Filter

Dubbo 中大量采用了 SPI 机制,这里配置 Filter 也是这种方式。

在项目的resources下创建META-INF/dubbo文件夹,然后新建名为org.apache.dubbo.rpc.Filter的文件,就是个普通的 txt 文件,然后在文件内容中编写如下:

log=it.com.dubbo.filter.LogFilter

如下图:

3.测试运行

一切准备好之后,我们俩运行一下吧。调用http://localhost:8003/test/getPayUserVo接口。

pay 服务输出日志如下:

开始调用:dubbo://10.60.7.155:20884/it.com.api.service.UserService?anyhost=true&application=d-pay&category=providers&check=false&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&group=group-sso&init=false&interface=it.com.api.service.UserService&metadata-type=remote&methods=getUserInfo&path=it.com.api.service.UserService&pid=15672&protocol=dubbo&qos.enable=false&register.ip=10.60.7.155&release=2.7.8&remote.application=d-sso&retries=0&revision=1.0-SNAPSHOT&side=consumer&sticky=false&timeout=30000&timestamp=1666231781668&version=1.0.0
调用结果:UserInfo(uid=10000, userName=张三, age=18)
调用耗时(秒):0.0155802
开始调用:dubbo://10.60.7.155:20881/it.com.api.service.SmsService?anyhost=true&application=d-pay&category=providers&check=false&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&group=group-sms&init=false&interface=it.com.api.service.SmsService&metadata-type=remote&methods=sendSms&path=it.com.api.service.SmsService&pid=15672&protocol=dubbo&qos.enable=false&register.ip=10.60.7.155&release=2.7.8&remote.application=d-sms&retries=0&revision=1.0-SNAPSHOT&side=consumer&sticky=false&timeout=30000&timestamp=1666231686823&version=1.0.0
调用结果:ok
调用耗时(秒):0.0041984

可以看到,由于 pay 调用了 sso 和 sms 两个服务,这里的 FIlter 对两次调用过程都进行了拦截。

在 Filter 中读取配置/注入Bean

首先需要注意的是,dubbo 的拦截器这些组件是由 dubbo 自己管理的,并不是直接由 Spring 管理的,所以我们无法在 Filter 中通过 @Autowried 或者 @Value 注解来注入 bean 或者配置。

如何读取配置

可以通过 Environment 对象来获取配置内容。

还是以上面的 LogFilter 为例:

1.编写配置

application.yml 添加一个自定义配置:

project:
  author: 长的6

2.在 Filter 中读取

@Activate(group = CommonConstants.CONSUMER)
public class LogFilter implements Filter {

    private Environment environment;

    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        String author = environment.getProperty("project.author");
        System.out.println("读取配置:" + author);

        return invoker.invoke(invocation);
    }
}

3.经过测试,OK

如何注入 Bean

方式1:通过 setter 注入

dubbo 组件实例化后,会自动调用 setter 方法,在 ApplicationContext 中去查找对应的 bean。查找是优先按照 name 去匹配,然后才是 type,所以注意setter 方法的名字是:set + 要注入的 bean 的名字

比如,这里注入 这个 bean:

@Component
public class PayServiceImpl {
    public Pay getPay() {
        Pay pay = new Pay();
        pay.setPayId("11111111111");
        pay.setPayName("订单");
        pay.setPayDate(new Date());
        return pay;
    }
}

在过滤器中:

@Activate(group = CommonConstants.CONSUMER)
public class LogFilter implements Filter {

    private PayServiceImpl PayServiceImpl;

    public void setPayServiceImpl(PayServiceImpl PayServiceImpl){
        this.PayServiceImpl = PayServiceImpl;
    }

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        // 使用
        System.out.println(PayServiceImpl.getPay());
        
        Result result = invoker.invoke(invocation);
        return result;
    }
}
方式2:通过 ApplicationContext 获取

编写 SpringUtil 工具类来获取 bean 对象,实现 ApplicationContextAware 接口,如下:

import javax.annotation.Nonnull;
import org.springframework.beans.BeansException;

@Component
public class SpringBeanUtil implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(@Nonnull ApplicationContext applicationContext) throws BeansException {
        if (SpringBeanUtil.applicationContext == null) {
            SpringBeanUtil.applicationContext = applicationContext;
        }
    }

    // 获取applicationContext
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    // 通过name获取 Bean.
    public static Object getBean(String name) {
        return getApplicationContext().getBean(name);
    }
}

然后在 Filter 中使用:

@Activate(group = CommonConstants.CONSUMER)
public class LogFilter implements Filter {

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        // 获取
        PayServiceImpl payServiceImpl = (PayServiceImpl) SpringBeanUtil.getBean("payServiceImpl");
        // 使用
        System.out.println(payServiceImpl.getPay());

        return invoker.invoke(invocation);
    }
}

Filter 的执行顺序问题

默认情况下,Dubbo 自带的 Filter 先执行,自定义 Filter 后执行。

自定义的 FIlter 可以通过设置 @Activate 的 order 属性,指定顺序,值越小,越先执行。

Filter 进行隐式传参

服务调用者可以通过RpcContext.getContext().setAttachment(参数名, 参数值);的方式,向服务被调用者传递参数。参数会被放入一个 Map 中传递。

服务被调用者可以通过RpcContext.getContext().getAttachment(参数名);的方式,来获取服务提供者传递的参数值。

还是以 LogFilter 为例:

这是在 pay 服务调用者设置参数值。

@Activate(group = CommonConstants.CONSUMER)
public class LogFilter implements Filter {

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        String traceId = TraceUtil.get();
        // 设置traceId参数
        RpcContext.getContext().setAttachment(TraceUtil.TRACE_ID, traceId);
        return invoker.invoke(invocation);
    }
}

然后分别在服务被调用者 sso 和 sms 中进行参数的获取:

@DubboService(group = "group-sms", version = "1.0.0", timeout = 30000)
public class SmsServiceImpl implements SmsService {

    @Override
    public String sendSms(Sms sms) {
        // 获取隐式参数
        String traceId = RpcContext.getContext().getAttachment("traceId");
        
        System.out.println(traceId + " 发送短信成功:" + sms.getMsg());
        return "ok";
    }
}