【自研网关】过滤器链设计

🌈Yu-Gateway:基于 Netty 构建的自研 API 网关,采用 Java 原生实现,整合 Nacos 作为注册配置中心。其设计目标是为微服务架构提供高性能、可扩展的统一入口和基础设施,承载请求路由、安全控制、流量治理等核心网关职能。

🌈项目代码地址:GitHub - YYYUUU42/YuGateway-master

如果该项目对你有帮助,可以在 github 上点个 ⭐ 喔 🥰🥰

🌈自研网关系列:可以点开专栏,参看完整的文档

目录

1、什么是过滤器

2、实现过滤器

3、实现流程

1、什么是过滤器

在我们的微服务架构中,构建了一个关键组件——网关服务,它作为系统的入口,负责对所有进、出流量进行统一管理和控制。为了实现这一功能,前面文章已成功将其注册至注册中心,并从配置中心获取了相关配置。接下来,我们将深入探讨如何构建网关服务的核心部分——过滤器链

过滤器链,顾名思义,是由一系列有序排列的过滤器构成的执行链条。每个过滤器承载特定的业务逻辑,对经过的请求和响应进行特定处理。当一个过滤器完成其预设的过滤流程后,会遵循链条顺序,将请求传递给下一个过滤器继续执行。通过这种方式,过滤器链实现了对请求与响应的深度定制化处理。

过滤器链中的成员可根据其作用范围分为全局过滤器和局部过滤器两种类型:

  1. 全局过滤器:这类过滤器具有广泛的适用性,对所有进入网关的请求均进行处理。它们通常负责执行诸如身份验证、权限校验、日志记录、跨域支持等通用性操作,确保所有请求在到达具体业务服务前符合系统的基本规范和要求。
  2. 局部过滤器:Spring Cloud框架已为我们预置了一套局部过滤器,用于应对特定场景下的请求处理。尽管如此,我们依然可以根据实际需求,通过继承并实现相关接口来自定义局部过滤器,以满足特定业务逻辑或优化性能。

过滤器链的运作机制遵循严格的流程:

  • 请求首先被送入链首的过滤器进行处理。
  • 每个过滤器依据自身职责对请求进行检查、修改或增强,然后将处理后的请求传递给链中的下一个过滤器。
  • 这一过程持续进行,直到链尾的路由过滤器接收到请求。路由过滤器的核心职责是根据请求信息和预设的路由规则,准确地将请求转发至相应的后台服务进行实际业务处理。
  • 后台服务完成任务后,将响应返回给路由过滤器。
  • 路由过滤器再将响应沿着过滤器链逆序传递,让每个过滤器有机会对响应进行必要的后期处理。
  • 最终,经过完整过滤器链洗礼的响应被写回客户端。

在过滤器链执行过程中,若遇到任何异常情况,可通过设置专门的异常处理过滤器来捕获并妥善处理这些异常,如返回友好的错误信息、记录异常日志等,确保系统的稳定性和用户体验。

当请求在整个生命周期中均正常流转且后台服务处理完毕后,使用 context.writeAndFlush() 方法将处理结果(即响应数据)高效地写回客户端,标志着一次完整的请求响应流程在过滤器链的保驾护航下圆满结束。

综上所述,过滤器链作为网关服务的核心组件,通过串联各个具有特定功能的过滤器,对进出系统的请求与响应进行全方位、多层次的精细化管理,实现了微服务架构下流量的有效管控与优化。

大概流程图:

2、实现过滤器

项目结构图

具体代码在 github 上,不一一展示

  1. Filter:这是一个接口,定义了过滤器需要实现的方法。所有的过滤器都需要实现这个接口,并实现doFilter方法来执行具体的过滤操作。getOrder方法用于获取过滤器的执行顺序。
  2. FilterAspect:这是一个注解,用于标记过滤器的一些属性,如ID、名称和执行顺序。这个注解被应用在实现了Filter接口的类上。
  3. FilterChainFactory:这是一个接口,定义了过滤器链工厂需要实现的方法。过滤器链工厂的主要职责是根据给定的上下文构建过滤器链。
  4. GatewayFilterChain:这是一个类,代表了过滤器链。它包含了一个过滤器列表,并提供了添加过滤器和执行过滤器链的方法。
  5. GatewayFilterChainFactory:这个类的具体实现可能会根据你的应用有所不同,但一般来说,它应该是FilterChainFactory接口的一个实现,负责创建GatewayFilterChain实例。这个类可能会使用单例模式,以确保整个应用只有一个GatewayFilterChainFactory实例。

总的来说,这些类和接口共同工作,以创建和管理过滤器链。过滤器链是由一系列过滤器组成的,这些过滤器按照特定的顺序执行,以对通过网关的请求进行处理。

3、实现流程

和之前一样,通过 debug 的方式来讲解过滤器链的实现

首先过滤器工厂具体实现类的无参构造

/*** SPI加载本地过滤器实现类对象* 过滤器存储映射 过滤器id - 过滤器对象*/
public GatewayFilterChainFactory() {//加载所有过滤器ServiceLoader<Filter> serviceLoader = ServiceLoader.load(Filter.class);serviceLoader.stream().forEach(filterProvider -> {Filter filter = filterProvider.get();FilterAspect annotation = filter.getClass().getAnnotation(FilterAspect.class);log.info("load filter success:{},{},{},{}", filter.getClass(), annotation.id(), annotation.name(), annotation.order());//添加到过滤集合String filterId = annotation.id();if (StringUtils.isEmpty(filterId)) {filterId = filter.getClass().getName();}processorFilterIdMap.put(filterId, filter);processFilterIdName.put(filterId, annotation.name());});
}

这里还是和注册和配置中心一样的思路,用 SPI 来加载类,加载实时可用的过滤器,组装为网关过滤器链

然后就将各个过滤器的id,名称,排序弄到 ConcurrentHashMap 中

其中 GatewayFilterChainFactory 类采用的是单例模式

在网关之中,GatewayFilterChainFactory 负责创建和管理过滤器链。由于过滤器链在整个应用中是共享的,因此没有必要为每个请求创建一个新的 GatewayFilterChainFactory 实例。

使用单例模式可以确保所有的请求都使用同一个GatewayFilterChainFactory实例,这样可以避免重复创建实例,节省内存,并保证所有请求使用的过滤器链的一致性

此外,GatewayFilterChainFactory 在初始化时会加载所有的过滤器,这可能是一个耗时的操作。如果每个请求都创建一个新的 GatewayFilterChainFactory 实例,那么这个耗时的初始化操作就会被重复执行,这会影响应用的性能。

使用单例模式,初始化操作只会执行一次,可以提高应用的性能。 总的来说,这里使用单例模式是为了保证过滤器链的一致性,节省内存,提高性能。

在 NettyCoreProcessor 会获取 GatewayFilterChainFactory 这个单例的

/*** 过滤器链工厂*/
private FilterChainFactory chainFactory = GatewayFilterChainFactory.getInstance();

在 process 方法中调用构建过滤器链条这个功能

// 创建并填充 GatewayContext 以保存有关传入请求的信息。
GatewayContext gatewayContext = RequestHelper.doContext(request, ctx);// 组装过滤器并执行过滤操作
chainFactory.buildFilterChain(gatewayContext).doFilter(gatewayContext);

构建过滤器链条利用本地缓存,主要确保对于同一规则ID的请求,可以复用已经构建的过滤器链,而不需要每次都重新构建,从而提高了性能

/*** 过滤器链缓存(服务ID ——> 过滤器链)* ruleId —— GatewayFilterChain*/
private Cache<String, GatewayFilterChain> chainCache = Caffeine.newBuilder().recordStats().expireAfterWrite(10, TimeUnit.SECONDS).build();
/*** 构建过滤器链条*/
@Override
public GatewayFilterChain buildFilterChain(GatewayContext ctx) throws Exception {// 获取规则IDString ruleId = ctx.getRules().getId();// 从缓存中获取过滤器链GatewayFilterChain chain = chainCache.getIfPresent(ruleId);// 如果缓存中没有过滤器链,那么构建一个新的过滤器链if (chain == null) {chain = doBuildFilterChain(ctx.getRules());// 将新构建的过滤器链添加到缓存中chainCache.put(ruleId, chain);}// 返回过滤器链return chain;
}

构建一个新的过滤器链

根据给定的规则构建一个过滤器链,这个过滤器链用于处理 HTTP 请求

为什么每个服务请求最终最后需要添加路由过滤器

在网关中,路由过滤器的作用是将请求路由(转发)到适当的后端服务

在过滤器链中,路由过滤器通常是最后一个执行的过滤器,因为它需要在所有其他过滤器(如权限、限流、负载均衡等)成功执行后才进行路由。

这个方法中,路由过滤器被添加到过滤器链的末尾,这是因为在执行所有其他过滤器并对请求进行各种检查和处理后,最后的步骤是将请求路由到适当的后端服务。

如果没有路由过滤器,那么即使请求通过了所有其他过滤器,也无法到达任何后端服务,因此每个服务请求最终都需要添加路由过滤器。

在 NettyCoreProcessor 中还会执行 doFilter() 方法,就是遍历过滤器链,逐个执行过滤

// 组装过滤器并执行过滤操作
chainFactory.buildFilterChain(gatewayContext).doFilter(gatewayContext);
/*** 执行过滤器链*/
public GatewayContext doFilter(GatewayContext ctx) {if (filters.isEmpty()) {return ctx;}try {for (Filter filter : filters) {filter.doFilter(ctx);}} catch (Exception e) {log.error("执行过滤器发生异常: {}", e.getMessage());throw new RuntimeException(e);}return ctx;
}

大概的过滤器设计就这样,还是需要自己根据代码 debug 一下才能根据清晰,点个 ⭐ !!!

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

day9 | 栈与队列 part-1 (Go) | 232 用栈实现队列、225 用队列实现栈

今日任务 栈与队列的理论基础 (介绍:代码随想录)232 用栈实现队列(题目: . - 力扣&#xff08;LeetCode&#xff09;)225 用队列实现栈 (题目: . - 力扣&#xff08;LeetCode&#xff09; ) 栈与队列的理论基础 栈 : 先进后出 队列: 后进先出 老师给的讲解:代码随想录 …

基于Pytorch实现图像分类——基于jupyter

分类任务 网络基本构建与训练方法&#xff0c;常用函数解torch.nn.functional模块nn.Module模块 MNIST数据集下载 from pathlib import Path import requestsDATA_PATH Path("data") PATH DATA_PATH / "mnist"PATH.mkdir(parentsTrue, exist_okTrue)U…

CSS核心样式-03-浮动+背景属性

目录 五、标准文档流 1. 微观现象 ①空白折叠现象 ②文字类的元素如果排在一行会出现一种高低不齐、底边对齐效果 ③自动换行 2. 元素等级 ①块级元素 ②行内元素 ③ 行内块元素 六、显示模式 display display四个属性值 脱离标准流 七、浮动属性&#xff08;脱标…

超越传统Lambda函数:深入解析Out-of-line Lambdas的奇妙之处

超越传统函数&#xff1a;深入解析线外 Lambda函数 的奇妙之处 一、背景二、lambda 的捕获三、可能出现的警告四、lambda的广义捕获五、为每种情况进行重载六、总结 一、背景 Out-of-line Lambdas翻译过来就是“线外Lambda函数”或“离线Lambda函数”。Lambda 是使代码更具表现…

【opencv】示例-peopledetect.cpp HOG(方向梯度直方图)描述子和SVM(支持向量机)进行行人检测...

// 包含OpenCV项目所需的objdetect模块头文件 #include <opencv2/objdetect.hpp> // 包含OpenCV项目所需的highgui模块头文件&#xff0c;用于图像的显示和简单操作 #include <opencv2/highgui.hpp> // 包含OpenCV项目所需的imgproc模块头文件&#xff0c;用于图像…

在vue中配置样式 max-width:100px时,发现和width:100px一样没有对应的递增到最大宽度的效果?怎么回事?怎么解决?

原因&#xff1a; 可能时vue的样式大部分和display相关&#xff0c;有很多的联系&#xff0c;导致不生效 解决&#xff1a; 对设置max-width样式的元素设置display:inline-block;属性&#xff0c;即可生效&#xff0c;实现随着子元素的扩展而扩展并增加固定到最大的宽度

视频批量高效剪辑,支持将视频文件转换为音频文件,轻松掌握视频格式

在数字化时代&#xff0c;视频内容日益丰富&#xff0c;管理和编辑这些视频变得愈发重要。然而&#xff0c;传统的视频剪辑软件往往操作复杂&#xff0c;难以满足高效批量处理的需求。现在&#xff0c;一款全新的视频批量剪辑神器应运而生&#xff0c;它支持将视频文件一键转换…

【opencv】示例-phase_corr.cpp 捕获视频流并通过计算相位相关性来检测画面中的移动...

// 包含OpenCV库的头文件 #include "opencv2/core.hpp" // 包含OpenCV核心功能 #include "opencv2/videoio.hpp" // 包含视频IO功能 #include "opencv2/highgui.hpp" // 包含高级GUI功能&#xff0c;显示图像 #include "opencv2/imgproc.hp…

数据结构之单链表相关刷题

找往期文章包括但不限于本期文章中不懂的知识点&#xff1a; 个人主页&#xff1a;我要学编程(ಥ_ಥ)-CSDN博客 所属专栏&#xff1a;数据结构 数据结构之单链表的相关知识点及应用-CSDN博客 下面题目基于上面这篇文章&#xff1a; 下面有任何不懂的地方欢迎在评论区留言或…

Open CASCADE学习|BRepOffsetAPI_DraftAngle

BRepOffsetAPI_DraftAngle 是 Open CASCADE Technology (OCCT) 中用于创建带有草图斜面的几何体的类。草图斜面是一种在零件设计中常见的特征&#xff0c;它可以在零件的表面上创建一个倾斜的面&#xff0c;通常用于便于零件的脱模或是增加零件的强度。 本例演示了如何创建一个…

mp3怎样才能转换成wav格式?音频互相转换的方法

一&#xff0c;什么是WAV WAV&#xff0c;全称为波形音频文件&#xff08;Waveform Audio File Format&#xff09;&#xff0c;是一种由微软公司和IBM公司联合开发的音频文件格式。自1991年问世以来&#xff0c;WAV格式因其无损的音频质量和广泛的兼容性&#xff0c;成为了多…

在Linux驱动中,如何确保中断上下文的正确保存和恢复?

大家好&#xff0c;今天给大家介绍在Linux驱动中&#xff0c;如何确保中断上下文的正确保存和恢复&#xff1f;&#xff0c;文章末尾附有分享大家一个资料包&#xff0c;差不多150多G。里面学习内容、面经、项目都比较新也比较全&#xff01;可进群免费领取。 在Linux驱动中&am…