SpringCloudGateway 动态转发后端服务
API网关的核心功能是统一流量入口,实现路由转发,SpringCloudGateway是API网关开发的技术之一,此外比较流行的还有Kong和ApiSix,这2个都是基于OpenResty技术栈。
简单的路由转发可以通过SpringCloudGateway的配置文件实现,在一些业务场景种,会需要动态替换路由配置中的后端服务地址,单纯靠配置文件无法满足这种需求。
本文介绍一种将路由配置保存到数据库中,可以根据接口请求的特定条件,从数据库中动态读取后端服务地址,实现灵活转发。
具体的代码参照 示例项目 https://github.com/qihaiyan/springcamp/tree/master/spring-cloud-gateway
一、概述
通过把SpringCloudGateway的相关路由配置规则保存到数据库中,可以动态的灵活调整路由。在本文的实现中,我们通过请求header中的特定值,动态选择对应的后端服务地址。
二、项目中加入依赖
在项目的gradle中增加依赖关系。
build.gradle:
plugins {id 'org.springframework.boot' version '3.0.2'id 'io.spring.dependency-management' version '1.1.0'id 'java'
}group = 'cn.springcamp'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'configurations {compileOnly {extendsFrom annotationProcessor}testCompileOnly {extendsFrom testAnnotationProcessor}
}repositories {mavenCentral()
}dependencies {implementation "org.springframework.boot:spring-boot-starter-json"implementation 'org.springframework.boot:spring-boot-starter-validation'implementation 'org.springframework.boot:spring-boot-starter-data-r2dbc'implementation 'org.springframework.cloud:spring-cloud-starter-gateway'runtimeOnly 'com.h2database:h2'runtimeOnly 'io.r2dbc:r2dbc-h2'annotationProcessor 'org.projectlombok:lombok'testAnnotationProcessor 'org.projectlombok:lombok'testImplementation "org.springframework.boot:spring-boot-starter-test"testImplementation 'org.junit.vintage:junit-vintage-engine'testImplementation 'io.projectreactor:reactor-test'testImplementation 'com.h2database:h2'testImplementation 'io.r2dbc:r2dbc-h2'testImplementation 'org.junit.vintage:junit-vintage-engine'
}dependencyManagement {imports {mavenBom "org.springframework.cloud:spring-cloud-dependencies:2022.0.1"}
}test {useJUnitPlatform()
}
由于SpringCloudGateway基于SpringWebFlux技术构建,所以依赖中的数据库配置需要使用r2dbc 。
三、配置文件
示例程序首选通过配置文件对路由进行基本配置,配置文件代码:
spring:r2dbc:url: r2dbc:h2:mem:///testdb?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSEusername: sapassword:cloud:gateway:routes:- id: routeOnepredicates:- Path=/route1/**uri: no://opfilters:- UriHostPlaceholderFilter=10001- id: routeTwopredicates:- Path=/route2/**uri: no://opfilters:- UriHostPlaceholderFilter=10001
配置文件中配置了2个路由,对应的接口地址路径分别是 /route1/**和 Path=/route2/**,路径中的 ***表示模糊匹配,只要是以 /route1/为前缀的路径都可以被访问到。
后端服务地址配置了一个无意的地址: uri: no://op,因为我们的处理逻辑会通过从数据库中读取配置来动态替换后端服务地址。
三、动态路由数据存储格式
我们通过 ROUTE_FILTER_ENTITY这个数据库表来存储接口后端服务配置数据。表结构为:
CREATE TABLE "ROUTE_FILTER_ENTITY"
(id VARCHAR(255) PRIMARY KEY,route_id VARCHAR(255), -- 路由ID,对应配置文件中的 ```id```配置项code VARCHAR(255), -- 接口请求header中的code参数的值url VARCHAR(255) -- 后端服务地址
);
当客户端访问 /route1/test接口时,根据配置文件的路由配置,SpringCloudGateway 会命中 id: routeOne这个路由规则,这个规则对应的后端服务地址是 uri: no://op,并不是我们期望的真实后端服务地址。
因此,我们需要读取到真实的后端服务地址,并将请求转发到这个地址。跟据 routeId 和 接口请求header中的code参数的值,就可以从 ROUTE_FILTER_ENTITY 表中查到对应的后端服务地址 url这个字段的值。
我们已经读取到了后端服务地址,还需要将请求转发到这个地址,下面介绍转发的方法。
四、后端服务动态转发
动态转发通过自定义 filter 的方式实现,自定义 filter 代码如下:
@Component
public class UriHostPlaceholderFilter extends AbstractGatewayFilterFactory<UriHostPlaceholderFilter.Config> {@Autowiredprivate RouteFilterRepository routeFilterRepository;public UriHostPlaceholderFilter() {super(Config.class);}@Overridepublic List<String> shortcutFieldOrder() {return Collections.singletonList("order");}@Overridepublic GatewayFilter apply(Config config) {return new OrderedGatewayFilter((exchange, chain) -> {String code = exchange.getRequest().getHeaders().getOrDefault("code", new ArrayList<>()).stream().findFirst().orElse("");String routeId = exchange.getAttribute(GATEWAY_PREDICATE_MATCHED_PATH_ROUTE_ID_ATTR);if (StringUtils.hasText(code)) {String newurl;try {newurl = routeFilterRepository.findByRouteIdAndCode(routeId, code).toFuture().get().getUrl();} catch (InterruptedException | ExecutionException e) {throw new RuntimeException(e);}if (StringUtils.hasText(exchange.getRequest().getURI().getQuery())) {newurl = newurl + "?" + exchange.getRequest().getURI().getQuery();}URI newUri = null;try {newUri = new URI(newurl);} catch (URISyntaxException e) {log.error("uri error", e);}exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, newUri);}return chain.filter(exchange);}, config.getOrder());}@Data@NoArgsConstructorpublic static class Config {private int order;public Config(int order) {this.order = order;}}
}
通过扩展 AbstractGatewayFilterFactory 类,我们自定义了 UriHostPlaceholderFilter 这个 filter 。
代码的核心逻辑在 apply 方法中。
首先通过 String code = exchange.getRequest().getHeaders().getOrDefault("code", new ArrayList<>()).stream().findFirst().orElse("")可以获取到接口请求 header 中 code 这个参数的值。
再通过 String routeId = exchange.getAttribute(GATEWAY_PREDICATE_MATCHED_PATH_ROUTE_ID_ATTR)可以获取到 routeId 。
最后通过 newurl = routeFilterRepository.findByRouteIdAndCode(routeId, code).toFuture().get().getUrl()就可以从数据库中读取到配置好的后端服务地址。
拿到后端服务地址后, 通过调用 exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, newUri);将请求转发到对应的地址。
四、单元测试
在单元测试代码中,我们预置了一条后端服务动态配置数据:
insert into ROUTE_FILTER_ENTITY values('1','routeOne','alpha','http://httpbin.org/anything')
然后模拟请求 /route1/test?a=test这个接口,根据我们的配置,请求会被转发到 http://httpbin.org/anything。
执行单元测试后,可以从日志中发现,接口返回的数据是 http://httpbin.org/anything 这个后端服务返回的数据。
当我们希望调整后端服务地址时,只需要把 ROUTE_FILTER_ENTITY 表中的这条配置数据中的 url 字段改成其它的任何服务地址即可,大大增加了程序的灵活度。
相关文章:
SpringCloudGateway 动态转发后端服务
API网关的核心功能是统一流量入口,实现路由转发,SpringCloudGateway是API网关开发的技术之一,此外比较流行的还有Kong和ApiSix,这2个都是基于OpenResty技术栈。 简单的路由转发可以通过SpringCloudGateway的配置文件实现…...
使用canvas写一个flappy bird小游戏
简介 canvas 是HTML5 提供的一种新标签,它可以支持 JavaScript 在上面绘画,控制每一个像素,它经常被用来制作小游戏,接下来我将用它来模仿制作一款叫flappy bird的小游戏。flappy bird(中文名:笨鸟先飞&am…...
KVM-2、虚拟化基础
1. 虚拟化概念 什么是虚拟化 **虚拟化是使用所谓虚拟机管理程序从一台物理机上创建若干个虚拟机的过程。**虚拟机的行为和运转方式与物理机一样,但它们会使用物理机的计算资源,如 CPU 、内存和存储。虚拟机管理程序会根据需要将这些计算资源分配给每个虚拟机。 虚拟化有哪…...
设计模式之观察者模式与访问者模式详解和应用
目录1.访问者模式详解1.1 访问者模式的定义1.1.1 访问者模式在生活中的体现1.1.2 访问者模式的适用场景1.2 访问者模式的通用实现1.3 访问者模式的使用案例之KPI考核1.3.1 类图设计1.3.2 代码实现1.4 访问者模式扩展---分派1.4.1 java中静态分派示例代码1.4.2 java中动态分派1.…...
spring注解方式整合Dubbo源码解析
系列文章目录 前言 本节我们的Dubbo源码版本基于2.6.x 在前一章我们的整合案例中,我们有几个比较关键的步骤: 在启动类上标注了EnableDubbo注解在provider类上面标注了Service注解来提供dubbo服务在消费的时候通过Reference注解引入dubbo服务在配置文件…...
大数值金额大写转换(C语言)
关于大数值金额大写转换,在财务管理的应用方面没什么意义。一般来说,千亿级,万亿级的数值就够了。因为在国家级层面是以亿为单位的,也就表达为千万亿,万万亿。在企业层面数值金额转换设置到千亿、万亿就行了。大的集团…...
迷宫问题图解 : 基于骨架提取、四邻域
目录 1. 迷宫的连通域 2. How to remove branch ? 3. 基于4邻域的 remove 分支 3.1 找到分支的端点 3.2 4邻域的 remove 分支 3.3 循环移除分支 3.4 code 4. 迷宫路线 4.1 预处理 4.2 提取骨架 4.3 分支的端点 4.4 去除分支的端点 4.5 循环去除分支 4…...
设计模式 - 如何在库和主程序之间互相调用数据和函数
背景:在项目开发过程中,难免碰到这种情况,当我们想要通过我们开发的库,调用主程序中的一些变量或者函数的时候,就会导致一些问题,因为在项目构建过程中,库都是不依赖于主程序编译的,…...
Redis面试题:1~2亿条数据需要缓存,请问如何设计这个存储案例
目录 前言 一、哈希取余分区 优点 缺点 二、一致性哈希算法分区 背景 步骤 ① 算法构建一致性哈希环 ② 服务器IP节点映射 ③ key落到服务器的落键规则 优点 ① 容错性 ② 扩展性 缺点 三、哈希槽分区 前言 单机单台100%不可能,肯定是分布式存储&am…...
程序员必备的软技能-《如何阅读一本书》
阅读很重要,我们真的会阅读吗? 这本书的初版是 1940年,时隔 80年,其内容仍然不过时。第一次读这本书时,给我最大的影响就是主题阅读,每次学习一个新理论、技术,都入手多本关于这项理论、技术的书…...
Java数据结构-栈、队列常用类(Stack、ArrayDeque、LinkedLList)
数据结构的三要素包括:逻辑结构、存储结构、数据的运算。逻辑结构描述的是数据之间的逻辑关系,分为线性结构(线性表(数组、链表)、栈、队列)和非线性结构(图、树、集合)。物理结构也…...
拯救了大批爬虫程序员,因为一个简单的神器
相信大家应该都写过爬虫,简单的爬虫只需要使用 requests 即可。遇到复杂的爬虫,就需要在程序里面加上请求头和参数信息。类似这种:我们一般的步骤是,先到浏览器的网络请求中找到我们需要的请求,然后将请求头和参数信息…...
2023年美赛C题Wordle预测问题三、四建模及Python代码详细讲解
更新时间:2023-2-19 16:30 相关链接 (1)2023年美赛C题Wordle预测问题一建模及Python代码详细讲解 (2)2023年美赛C题Wordle预测问题二建模及Python代码详细讲解 (3)2023年美赛C题Wordle预测问题三、四建模…...
相关性-回忆录(持续更新)
1.TODO方向 (1)数据增强:finetuning阶段需要大量人工标注样本,消耗时间和成本。用户点击数据作为弱监督学习,可以尝试图网络构建节点和边(query聚合); 使用展现未点击生成对抗网络进…...
(必备技能)使用Python实现屏幕截图
(必备技能)使用Python实现屏幕截图 文章目录 (必备技能)使用Python实现屏幕截图 一、序言二、环境配置 1、下载pyautogui包2、下载opencv-python包3、下载PyQt5包4、下载pypiwin32包 三、屏幕截屏源码与解析 1、使用pyautogui方法实现截屏2、使用PyQt方法实现截屏 a.获取窗口…...
「数据仓库」怎么选择现代数据仓库?
构建自己的数据仓库时要考虑的基本因素我们用过很多数据仓库。当我们的客户问我们,对于他们成长中的公司来说,最好的数据仓库是什么时,我们会根据他们的具体需求来考虑答案。通常,他们需要几乎实时的数据,价格低廉&…...
6.3 使用 Swagger 生成 Web API 文档
第6章 构建 RESTful 服务 6.1 RESTful 简介 6.2 构建 RESTful 应用接口 6.3 使用 Swagger 生成 Web API 文档 6.4 实战:实现 Web API 版本控制 6.3 使用 Swagger 生成 Web API 文档 高质量的 API 文档在系统开发的过程中非常重要。本节介绍什么是 Swaggerÿ…...
Day894.加锁规则的一些问题 -MySQL实战
加锁规则的一些问题 Hi,我是阿昌,今天学习记录的是关于加锁规则的一些问题的内容。 加锁规则,这个规则中,包含了两个“原则”、两个“优化”和一个“bug”: 原则 1:加锁的基本单位是 next-key lock。nex…...
【Flutter入门到进阶】Dart进阶篇---Dart异步编程
1 并行与并发的编程区别 1.1 并发与并行 1.1.1 说明 我们举个例子,如果有条高速公路 A 上面并排有 8 条车道,那么最大的并行车辆就是 8 辆此条高速公路 A 同时并排行走的车辆小于等于 8 辆的时候,车辆就可以并行运行。 CPU 也是这个原理,一个 CPU 相当于一个高速公路 A,核心数…...
synchronized 学习
学习源: https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖,也要考虑性能问题(场景) 2.常见面试问题: sync出…...
Lombok 的 @Data 注解失效,未生成 getter/setter 方法引发的HTTP 406 错误
HTTP 状态码 406 (Not Acceptable) 和 500 (Internal Server Error) 是两类完全不同的错误,它们的含义、原因和解决方法都有显著区别。以下是详细对比: 1. HTTP 406 (Not Acceptable) 含义: 客户端请求的内容类型与服务器支持的内容类型不匹…...
Opencv中的addweighted函数
一.addweighted函数作用 addweighted()是OpenCV库中用于图像处理的函数,主要功能是将两个输入图像(尺寸和类型相同)按照指定的权重进行加权叠加(图像融合),并添加一个标量值&#x…...
对WWDC 2025 Keynote 内容的预测
借助我们以往对苹果公司发展路径的深入研究经验,以及大语言模型的分析能力,我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际,我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测,聊作存档。等到明…...
Ascend NPU上适配Step-Audio模型
1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统,支持多语言对话(如 中文,英文,日语),语音情感(如 开心,悲伤)&#x…...
蓝桥杯3498 01串的熵
问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798, 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...
Unity UGUI Button事件流程
场景结构 测试代码 public class TestBtn : MonoBehaviour {void Start(){var btn GetComponent<Button>();btn.onClick.AddListener(OnClick);}private void OnClick(){Debug.Log("666");}}当添加事件时 // 实例化一个ButtonClickedEvent的事件 [Formerl…...
【Veristand】Veristand环境安装教程-Linux RT / Windows
首先声明,此教程是针对Simulink编译模型并导入Veristand中编写的,同时需要注意的是老用户编译可能用的是Veristand Model Framework,那个是历史版本,且NI不会再维护,新版本编译支持为VeriStand Model Generation Suppo…...
第八部分:阶段项目 6:构建 React 前端应用
现在,是时候将你学到的 React 基础知识付诸实践,构建一个简单的前端应用来模拟与后端 API 的交互了。在这个阶段,你可以先使用模拟数据,或者如果你的后端 API(阶段项目 5)已经搭建好,可以直接连…...
Java详解LeetCode 热题 100(26):LeetCode 142. 环形链表 II(Linked List Cycle II)详解
文章目录 1. 题目描述1.1 链表节点定义 2. 理解题目2.1 问题可视化2.2 核心挑战 3. 解法一:HashSet 标记访问法3.1 算法思路3.2 Java代码实现3.3 详细执行过程演示3.4 执行结果示例3.5 复杂度分析3.6 优缺点分析 4. 解法二:Floyd 快慢指针法(…...
