持续创作,加速成长!这是我参与「掘金日新计划 · 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®ister.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×tamp=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®ister.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×tamp=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";
}
}