Dubbo 自定义 Filter 编码实例

news2024/11/25 13:13:47

Dubbo的Filter机制为我们做应用的扩展设计提供了很多可能性,这里的Filter也是“责任链”机制的一种实现场景,作为Java码农,我们也经常接触到很多责任链的实现场景,如Tomcat进入Servlet前的filter,如Spring Aop代理的链式调用等。本篇文章通过一个实例来看看Dubbo自定义Filter的代码具体如何编写。

我们为Dubbo实现Filter扩展的时候,一般是分为两个方向来做

1.服务消费者发起调用的流程

2.服务提供者接收请求的流程

如上图所示,无论当服务消费者发起调用时,还是服务提供者接收请求时,都会走过一条filter链,我们要做的就是往这个filter链中加入我们自己的filter,也就是图中的“自定义FIlter”这块内容。

本次编码目标

实现功能:

  • 服务消费者发起调用时,打印请求信息,收到返回结果后,打印返回信息与整体调用耗时。
  • 服务提供者收到请求时,打印请求信息,Service方法执行完后,打印返回信息与Service方法执行耗时。
  • 服务消费者在发起调用时,带上trace_id,服务提供者收到请求后,拿到trace_id并通过MDC加到本次执行线程中,打印日志时打印出trace_id。以便通过日志跟踪一次请求的完整链路。

编码实现

项目结构如下,一个consumer服务,一个provider服务,外加一个api接口的定义包。

api中定义了一个接口,用来根据userId获取用户信息

public interface IUserService {
    UserInfoDTO getUserInfo(Integer uerId);
}

provider项目中有这个接口的实现类,并向外暴露作为Dubbo的一个服务提供者。

@DubboService
@Slf4j
public class UserServiceImpl implements IUserService {

    @Override
    public UserInfoDTO getUserInfo(Integer userId) {
        log.info("进入到getUserInfo方法了");

        RoleDTO rolePO1 = new RoleDTO(1,"质检员");
        RoleDTO rolePO2 = new RoleDTO(2,"初审员");
        return new UserInfoDTO(userId,"张三",Arrays.asList(rolePO1,rolePO2));
    }

}

涉及到的pojo类就不再展示了,Service功能不是本次的编码的关注点,所以简单点,所有的userId都返回同样的用户数据,都叫“张三”,角色都是“质检员、初审员”。

接口定义及实现类有了,接下来就开始编写Filter。

Filter类需要实现接口org.apache.dubbo.rpc.Filter,实现invoke接口。

1.消费端编码

trace_id相关的Filter实现

这个Filter实现的功能为,将当前线程中的trace_id加入到RpcContext的附加参数中,当RPC请求发出后,服务端也能够从RpcContext的附加参数中获取到这个trace_id。

public class DubboConstant {
    public static final String TRACE_ID = "trace_id";
}
@Slf4j
@Activate(group = CommonConstants.CONSUMER, order = 1)
public class ConsumerTraceFilter implements Filter {
    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        RpcContext rpcContext = RpcContext.getContext();
        // 首先获取当前线程中的traceId,如果为空的话就new一个
        String traceId = MDC.get(DubboConstant.TRACE_ID);
        if(StringUtils.isBlank(traceId)) {
            traceId = UUID.fastUUID().toString();
        }
        rpcContext.setAttachment(DubboConstant.TRACE_ID,traceId);
        return invoker.invoke(invocation);
    }
}

日志相关的Filter实现

这个Filter实现的功能为,在发起RPC请求前,将远程服务的ip端口及本次请求的参数打印出来。在收到返回结果后,打印本次调用耗时及结果信息。

@Slf4j
@Activate(group = CommonConstants.CONSUMER, order = 2)
public class ConsumerLogFilter implements Filter {
    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        String host = invoker.getUrl().getHost();
        int port = invoker.getUrl().getPort();

        String serviceName = invocation.getTargetServiceUniqueName();
        String methodName = invocation.getMethodName();
        Object[] arguments = invocation.getArguments();

        // 这里的traceId是在TraceFilter放进到RpcContext里的
        String traceId = RpcContext.getContext().getAttachment(DubboConstant.TRACE_ID);

        log.info("[send request] trace_id:{}, host:{}, port:{}, serviceName:{}, methodName:{}, arguments:{}", traceId, host, port, serviceName, methodName, arguments);

        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        Result result = invoker.invoke(invocation);
        stopWatch.stop();
        long totalTimeMillis = stopWatch.getTotalTimeMillis();

        log.info("[receive response] trace_id:{}, elapsed time:{}ms, response:{}", traceId, totalTimeMillis, result.getValue().toString());
        return result;
    }
}

 SPI配置

要想使自定义的Filter生效,需要将Filter声明到一个文件中,dubbo会自动去这个文件中加载你设置的Filter,如果你写了Filter却没有声明到这个文件中,那么它也是不生效的。

这个文件就是  resources/META-INF/dubbo/org.apache.dubbo.rpc.Filter ,这是一个纯文本文件,内容如下

rpcLogFilter=com.hml.consumer.filter.ConsumerLogFilter
traceFilter=com.hml.consumer.filter.ConsumerTraceFilter

每个Filter都单独占一行,每行的格式为    [FIlter名字]:[自定义Filter的全限定名]。 其中Filter名字可以随便定义,没有固定格式。

这里我们将刚才写的两个FIlter加进去了。

另外,有一点要注意,这是我的实际踩坑经验,那就是META-INF.dubbo这个东西,它是两个文件夹,在使用idea建文件夹的时候最好一个一个建,先建META-INF,再建dubbo。否则,如果只建一个文件,文件名叫“META-INF.dubbo”的话,idea不会替你分成两个文件夹,它就仍然只是一个文件夹,文件名就叫“META-INF.dubbo”,Dubbo项目启动时也不会去找这个文件夹,注意注意!!!

配置Filter的生效范围

Filter的生效范围 首先分为两类:1.在服务消费者发起调用时生效,2.在服务提供者接收请求时生效

而每个大类里有分为两种生效范围:1.针对某个接口类下的接口生效 2.针对所有接口生效

官方提供了两种方式来配置FIlter的生效范围:通过配置文件的方式 和 通过注解的方式

目前大家暴露或调用dubbo接口的方式一般是使用注解了,几乎不用xml形式的配置了,所以对于1,3 两种针对单个接口的方式,一般是直接声明在 @DubboReference 或 @DubboService 注解中,例如

@DubboReference(filter = {"traceFilter"})

@DubboService(filter = "xxxFilter")

对于2,4 两种全局Filter,目前两种方式都有见到(我们本次编码的FIlter都属于全局Filter)

通过配置文件的方式

在application.properties配置文件中加入如下配置

// 配置服务消费者调用时生效的Filter

dubbo.consumer.filter=traceFilter,rpcLogFilter

// 配置服务提供者接收请求时生效的FIlter

dubbo.provider.filter=xxxFilter

traceFilter与rpcLogFilter就是我们在resources/META-INF/dubbo/org.apache.dubbo.rpc.Filter 这个文件中给FIlter设置的名字。

两个Filter的调用顺序就是上面的配置配好的顺序,traceFilter在前,rpcLogFilter在后。

通过注解的方式

使用@Activate注解同样也可以声明FIlter的生效范围及排序,我本次编码使用的就是该注解,在展示Filter代码时已经展示出来了。

例如

group代表了该FIlter的生效范围

对于group的值,我们一般使用org.apache.dubbo.common.constants.CommonConstants中设置好的值,常用到的就是这两个值

  • CommonConstants.CONSUMER 代表服务消费者发起调用时生效
  • CommonConstants.PROVIDER 代表服务提供者接收请求时生效
order代表了该Filter在自定义FIlter链中的顺序

order是个int值,值越小,顺序越靠前,越先被执行

Consumer编码总结

上面描述的自定义Filter编码过程其实一共分成了三步,三步完成后,Consumer端的Filter就配置好了

  1. 首先编码自定义FIlter类。
  2. 在resources/META-INF/dubbo/org.apache.dubbo.rpc.Filter这个配置文件中声明自定义Filter。
  3. 通过注解或配置文件的方式,声明Filter的生效范围及执行顺序。

2.服务提供端编码

服务提供端的FIlter编码同消费端编码,也是分为三步。第二步和第三步没什么可说的了,我们这里仅看FIlter的实现即可。

trace_id相关的Filter实现

消费端通过ConsumerTraceFilter已经将trace_id放到RpcContext里了,服务提供端要干的事就是从RpcContext中将 trace_id 取出来,并通过MDC放到本次执行线程中。

@Slf4j
@Activate(group = CommonConstants.PROVIDER, order = 1)
public class ProviderTraceFilter implements Filter {
    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        RpcContext rpcContext = RpcContext.getContext();
        String traceId = rpcContext.getAttachment(DubboConstant.TRACE_ID);
        if(StringUtils.isNotBlank(traceId)) {
            MDC.put(DubboConstant.TRACE_ID,traceId);
        }
        Result result = invoker.invoke(invocation);

        // 因为线程可能会被复用,所以在一次请求结束后要移除该线程ThreadLocal中的trace_id
        MDC.remove(DubboConstant.TRACE_ID);
        return result;
    }
}

将服务提供端的日志格式设置一下,打印出 trace_id

logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} %-5level [%thread] trace_id:[%X{trace_id}] %logger : %msg%n

日志相关的Filter实现

这个Filter实现的功能为,在收到RPC请求后,将本次请求的参数打印出来。在拿到service方法的返回结果后,打印本次service方法执行耗时及返回结果。

@Slf4j
@Activate(group = CommonConstants.PROVIDER, order = 2)
public class ProviderLogFilter implements Filter {
    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        String host = invoker.getUrl().getHost();
        int port = invoker.getUrl().getPort();

        String serviceName = invocation.getTargetServiceUniqueName();
        String methodName = invocation.getMethodName();
        Object[] arguments = invocation.getArguments();

        String traceId = RpcContext.getContext().getAttachment(DubboConstant.TRACE_ID);

        log.info("[receive request] trace_id:{}, host:{}, port:{}, serviceName:{}, methodName:{}, arguments:{}", traceId, host, port, serviceName, methodName, arguments);

        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        Result result = invoker.invoke(invocation);
        stopWatch.stop();
        long totalTimeMillis = stopWatch.getTotalTimeMillis();

        log.info("[send response] trace_id:{}, elapsed time:{}ms, response:{}", traceId, totalTimeMillis, result.getValue().toString());
        return result;
    }
}

效果展示

启动provider服务,consumer通过测试类的形式启动服务并发起调用

测试类代码

@SpringBootTest
public class UserTest {

    @DubboReference
    IUserService userService;

    @Test
    public void userTest() {
        UserInfoDTO userInfo = userService.getUserInfo(1);
        System.out.println(JSON.toJSONString(userInfo));
    }
}

测试方法成功执行完毕,日志展示

consumer端日志

2024-06-03 15:47:17.853  INFO 52493 --- [           main] c.hml.consumer.filter.ConsumerLogFilter  : [send request] trace_id:41005892-cdd1-4f0c-af13-c7d3aef4cdf0, host:192.168.40.24, port:20880, serviceName:com.hml.api.user.IUserService, methodName:getUserInfo, arguments:[1]
2024-06-03 15:47:17.900  INFO 52493 --- [           main] c.hml.consumer.filter.ConsumerLogFilter  : [receive response] trace_id:41005892-cdd1-4f0c-af13-c7d3aef4cdf0, elapsed time:46ms, response:UserInfoDTO(id=1, name=张三, roles=[RoleDTO(roleId=1, roleName=质检员), RoleDTO(roleId=2, roleName=初审员)])
{"id":1,"name":"张三","roles":[{"roleId":1,"roleName":"质检员"},{"roleId":2,"roleName":"初审员"}]}

由于consumer我没有设置日志格式,所以还是默认格式。

provider端日志

2024-06-03 15:47:17 INFO  [DubboServerHandler-192.168.40.24:20880-thread-2] trace_id:[41005892-cdd1-4f0c-af13-c7d3aef4cdf0] com.hml.provider.filter.ProviderLogFilter : [receive request] trace_id:41005892-cdd1-4f0c-af13-c7d3aef4cdf0, host:192.168.40.24, port:20880, serviceName:com.hml.api.user.IUserService:0.0.0, methodName:getUserInfo, arguments:[1]
2024-06-03 15:47:17 INFO  [DubboServerHandler-192.168.40.24:20880-thread-2] trace_id:[41005892-cdd1-4f0c-af13-c7d3aef4cdf0] com.hml.provider.user.UserServiceImpl : 进入到getUserInfo方法了
2024-06-03 15:47:17 INFO  [DubboServerHandler-192.168.40.24:20880-thread-2] trace_id:[41005892-cdd1-4f0c-af13-c7d3aef4cdf0] com.hml.provider.filter.ProviderLogFilter : [send response] trace_id:41005892-cdd1-4f0c-af13-c7d3aef4cdf0, elapsed time:1ms, response:UserInfoDTO(id=1, name=张三, roles=[RoleDTO(roleId=1, roleName=质检员), RoleDTO(roleId=2, roleName=初审员)])

可以看到,trace_id 的传递机制已经实现,consumer与provider的请求、返回 日志也打印出来了,本次编码目标-实现。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1795839.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

语言模型解构——Tokenizer

1. 认识Tokenizer 1.1 为什么要有tokenizer&#xff1f; 计算机是无法理解人类语言的&#xff0c;它只会进行0和1的二进制计算。但是呢&#xff0c;大语言模型就是通过二进制计算&#xff0c;让你感觉计算机理解了人类语言。 举个例子&#xff1a;单1&#xff0c;双2&#x…

龙迅LT8712X TYPE-C或者DP转HDMI加VGA输出,内置MCU,只是IIS以及4K60HZ分辨率

龙迅LT8712X描述&#xff1a; LT8712X是一种高性能的Type-C/DP1.2到HDMI2.0和VGA转换器&#xff0c;设计用于将USB Type-C源或DP1.2源连接到HDMI2.0和VGA接收器。LT8712X集成了一个DP1.2兼容的接收器&#xff0c;一个HDMI2.0兼容的发射机和一个高速三角机窝视频DAC。此外&…

【论文速读】LM的文本生成方法,Top-p,温度,《The Curious Case of Neural Text Degeneration》

论文链接&#xff1a;https://arxiv.org/abs/1904.09751 https://ar5iv.labs.arxiv.org/html/1904.09751 这篇文章&#xff0c;描述的是语言模型的文本生成的核采样的方法&#xff0c;就是现在熟知的top-p 大概看看&#xff0c;还有几个地方比较有趣&#xff0c;值得记录一下。…

kotlin1.8.10问题导致gson报错TypeToken type argument must not contain a type variable

书接上回&#xff0c;https://blog.csdn.net/jzlhll123/article/details/139302991。 之前我发现gson报错后&#xff1a; gson在2.11.0给我的kotlin项目代码报错了。 IllegalArgumentException: TypeToken type argument must not contain a type variable 上次解释原因是因为&…

WALT算法简介

WALT(Windows-Assist Load Tracing)算法是由Qcom开发&#xff0c; 通过把时间划分为窗口&#xff0c;对 task运行时间和CPU负载进行跟踪计算的方法。为任务调度、迁移、负载均衡及CPU调频 提供输入。 WALT相对PELT算法&#xff0c;更能及时反映负载变化&#xff0c; 更适用于…

PasteCode系列系统说明

定义 PasteCode系列是指项目是基于PasteTemplate构建的五层以上项目&#xff0c;包括不仅限于 Domain EntityFrameworkCore Application.Contracts Application HttpApi.Host 熟悉ABP vNext就很好理解了&#xff0c;因为PasteTemplate就是基于ABP的框架精简而来&#xff01;在…

CVE-2022-4230

CVE-2022-4230 漏洞介绍 WP Statistics WordPress 插件13.2.9之前的版本不会转义参数&#xff0c;这可能允许经过身份验证的用户执行 SQL 注入攻击。默认情况下&#xff0c;具有管理选项功能 (admin) 的用户可以使用受影响的功能&#xff0c;但是该插件有一个设置允许低权限用…

绘画参数配置及使用

绘画参数配置及使用 路径&#xff1a;站点后台-功能-AI绘画 进入参数配置 接口选择&#xff1a;多种接口自主选择&#xff08;需自己准备key&#xff09;&#xff0c;对应接口的key对话和绘画通用 存储空间&#xff1a; 位置在超管后台-存储空间 自主选择存储&#xff08;需…

Python画图(多图展示在一个平面)

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

Go实战 | 使用Go-Fiber采用分层架构搭建一个简单的Web服务

前言 &#x1f4e2;博客主页&#xff1a;程序源⠀-CSDN博客 &#x1f4e2;欢迎点赞&#x1f44d;收藏⭐留言&#x1f4dd;如有错误敬请指正&#xff01; 一、环境准备、示例介绍 Go语言安装&#xff0c;GoLand编辑器 这个示例实现了一个简单的待办事项&#xff08;todo&#xf…

应用解析 | 面向智能网联汽车的产教融合解决方案

背景介绍 随着科技的飞速发展&#xff0c;智能网联汽车已成为汽车产业的新宠&#xff0c;引领着未来出行的潮流。然而&#xff0c;行业的高速发展也带来了对高素质技术技能人才的迫切需求。为满足这一需求&#xff0c;推动教育链、人才链与产业链、创新链的深度融合&#xff0…

【Java】static 修饰变量

static 一种java内置关键字&#xff0c;静态关键字&#xff0c;可以修饰成员变量、成员方法。 static 成员变量 1.static 成员变量2.类变量图解3.类变量的访问4.类变量的内存原理5.类变量的应用 1.static 成员变量 成员变量按照有无static修饰&#xff0c;可以分为 类变量…

AGP8+ android.useNewApkCreator‘ is deprecated 打包失败

问题 新建一个项目&#xff0c;默认使用最新版的 AGP 和 Gradle&#xff0c;打包构建立马失败&#xff01; 错误日志 Caused by: com.android.builder.errors.EvalIssueException: The option android.useNewApkCreator is deprecated. An exception occurred applying plu…

STM32-16-ADC

STM32-01-认识单片机 STM32-02-基础知识 STM32-03-HAL库 STM32-04-时钟树 STM32-05-SYSTEM文件夹 STM32-06-GPIO STM32-07-外部中断 STM32-08-串口 STM32-09-IWDG和WWDG STM32-10-定时器 STM32-11-电容触摸按键 STM32-12-OLED模块 STM32-13-MPU STM32-14-FSMC_LCD STM32-15-DMA…

关于2024中国海洋装备博览会(福州)的参展通知

2024中国航洋装备博览会 2024世界航海装备大会 2024中国船舶供应链大会 2024中国航洋装备博览会2024世界航海装备大会 时间地点、规模、主题、定位 1.时间&#xff1a;2024年11月15日至18日 2.地点&#xff1a;福州海峡国际会展中心、冠城大通游艇码头 3.规模&#xff1…

Seed-TTS语音编辑有多强?对比实测结果让你惊叹!

GLM-4-9B 开源系列模型 前言 就在最近&#xff0c;ByteDance的研究人员最近推出了一系列名为Seed-TTS的大规模自回归文本转语音(TTS)模型,能够合成几乎与人类语音无法区分的高质量语音。那么Seed-TTS的表现究竟有多强呢?让我们一起来感受下Seed-TTS带来的惊喜吧! 介绍Seed-TTS…

Vivado 设置关联使用第三方仿真软件 Modelsim

目录 1.前言2.Vivado 设置关联使用第三方仿真软件 Modelsim 微信公众号获取更多FPGA相关源码&#xff1a; 1.前言 Vivado 软件自带有仿真功能,该功能使用还是比较方便的,初学者可以直接使用自带的仿真功能。 Modelsim仿真工具是Model公司开发的。它支持Verilog、VHDL以及他…

27 - 求关注者的数量(高频 SQL 50 题基础版)

27 - 求关注者的数量 selectuser_id,count(*) followers_count fromFollowers group byuser_id;

什么是阻塞IO和非阻塞IO

一、IO模型 五种&#xff1a;阻塞IO、非阻塞IO、多路复用IO、信号驱动IO、异步IO 1.阻塞IO&#xff1a;一个应用程序执行I/O操作时&#xff0c;会被阻塞&#xff0c;直到数据准备好或操作完成。这种模型通常简单易用&#xff0c;但会造成资源浪费。因为CPU在等待I/O操作完成时…

为什么要学习扣子(Coze)

&#x1f9d9;‍♂️ 诸位好&#xff0c;吾乃斜杠君&#xff0c;编程界之翘楚&#xff0c;代码之大师。算法如流水&#xff0c;逻辑如棋局。 &#x1f4dc; 吾之笔记&#xff0c;内含诸般技术之秘诀。吾欲以此笔记&#xff0c;传授编程之道&#xff0c;助汝解技术难题。 &#…