SpringCloud Gateway動(dòng)態(tài)轉(zhuǎn)發(fā)后端服務(wù)實(shí)現(xiàn)過(guò)程講解
前言
API網(wǎng)關(guān)的核心功能是統(tǒng)一流量入口,實(shí)現(xiàn)路由轉(zhuǎn)發(fā),SpringCloudGateway是API網(wǎng)關(guān)開(kāi)發(fā)的技術(shù)之一,此外比較流行的還有Kong和ApiSix,這2個(gè)都是基于OpenResty技術(shù)棧。
簡(jiǎn)單的路由轉(zhuǎn)發(fā)可以通過(guò)SpringCloudGateway的配置文件實(shí)現(xiàn),在一些業(yè)務(wù)場(chǎng)景種,會(huì)需要?jiǎng)討B(tài)替換路由配置中的后端服務(wù)地址,單純靠配置文件無(wú)法滿足這種需求。
本文介紹一種將路由配置保存到數(shù)據(jù)庫(kù)中,可以根據(jù)接口請(qǐng)求的特定條件,從數(shù)據(jù)庫(kù)中動(dòng)態(tài)讀取后端服務(wù)地址,實(shí)現(xiàn)靈活轉(zhuǎn)發(fā)。
具體的代碼參照 示例項(xiàng)目 https://github.com/qihaiyan/springcamp/tree/master/spring-cloud-gateway
一、概述
通過(guò)把SpringCloudGateway的相關(guān)路由配置規(guī)則保存到數(shù)據(jù)庫(kù)中,可以動(dòng)態(tài)的靈活調(diào)整路由。在本文的實(shí)現(xiàn)中,我們通過(guò)請(qǐng)求header中的特定值,動(dòng)態(tài)選擇對(duì)應(yīng)的后端服務(wù)地址。
二、項(xiàng)目中加入依賴
在項(xiàng)目的gradle中增加依賴關(guān)系。
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技術(shù)構(gòu)建,所以依賴中的數(shù)據(jù)庫(kù)配置需要使用r2dbc 。
三、配置文件
示例程序首選通過(guò)配置文件對(duì)路由進(jìn)行基本配置,配置文件代碼:
spring:
r2dbc:
url: r2dbc:h2:mem:///testdb?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
username: sa
password:
cloud:
gateway:
routes:
- id: routeOne
predicates:
- Path=/route1/**
uri: no://op
filters:
- UriHostPlaceholderFilter=10001
- id: routeTwo
predicates:
- Path=/route2/**
uri: no://op
filters:
- UriHostPlaceholderFilter=10001
配置文件中配置了2個(gè)路由,對(duì)應(yīng)的接口地址路徑分別是 /route1/**
和 Path=/route2/**
,路徑中的 ***
表示模糊匹配,只要是以 /route1/
為前綴的路徑都可以被訪問(wèn)到。
后端服務(wù)地址配置了一個(gè)無(wú)意的地址: uri: no://op
,因?yàn)槲覀兊奶幚磉壿嫊?huì)通過(guò)從數(shù)據(jù)庫(kù)中讀取配置來(lái)動(dòng)態(tài)替換后端服務(wù)地址。
四、動(dòng)態(tài)路由數(shù)據(jù)存儲(chǔ)格式
我們通過(guò) ROUTE_FILTER_ENTITY
這個(gè)數(shù)據(jù)庫(kù)表來(lái)存儲(chǔ)接口后端服務(wù)配置數(shù)據(jù)。表結(jié)構(gòu)為:
CREATE TABLE "ROUTE_FILTER_ENTITY" ( id VARCHAR(255) PRIMARY KEY, route_id VARCHAR(255), -- 路由ID,對(duì)應(yīng)配置文件中的 ```id```配置項(xiàng) code VARCHAR(255), -- 接口請(qǐng)求header中的code參數(shù)的值 url VARCHAR(255) -- 后端服務(wù)地址 );
當(dāng)客戶端訪問(wèn) /route1/test
接口時(shí),根據(jù)配置文件的路由配置,SpringCloudGateway 會(huì)命中 id: routeOne
這個(gè)路由規(guī)則,這個(gè)規(guī)則對(duì)應(yīng)的后端服務(wù)地址是 uri: no://op
,并不是我們期望的真實(shí)后端服務(wù)地址。
因此,我們需要讀取到真實(shí)的后端服務(wù)地址,并將請(qǐng)求轉(zhuǎn)發(fā)到這個(gè)地址。跟據(jù) routeId 和 接口請(qǐng)求header中的code參數(shù)的值,就可以從 ROUTE_FILTER_ENTITY 表中查到對(duì)應(yīng)的后端服務(wù)地址 url
這個(gè)字段的值。
我們已經(jīng)讀取到了后端服務(wù)地址,還需要將請(qǐng)求轉(zhuǎn)發(fā)到這個(gè)地址,下面介紹轉(zhuǎn)發(fā)的方法。
五、后端服務(wù)動(dòng)態(tài)轉(zhuǎn)發(fā)
動(dòng)態(tài)轉(zhuǎn)發(fā)通過(guò)自定義 filter 的方式實(shí)現(xiàn),自定義 filter 代碼如下:
@Component public class UriHostPlaceholderFilter extends AbstractGatewayFilterFactory<UriHostPlaceholderFilter.Config> { @Autowired private RouteFilterRepository routeFilterRepository; public UriHostPlaceholderFilter() { super(Config.class); } @Override public List<String> shortcutFieldOrder() { return Collections.singletonList("order"); } @Override public 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 @NoArgsConstructor public static class Config { private int order; public Config(int order) { this.order = order; } } }
通過(guò)擴(kuò)展 AbstractGatewayFilterFactory 類,我們自定義了 UriHostPlaceholderFilter 這個(gè) filter 。
代碼的核心邏輯在 apply 方法中。
首先通過(guò) String code = exchange.getRequest().getHeaders().getOrDefault("code", new ArrayList<>()).stream().findFirst().orElse("")
可以獲取到接口請(qǐng)求 header 中 code 這個(gè)參數(shù)的值。
再通過(guò) String routeId = exchange.getAttribute(GATEWAY_PREDICATE_MATCHED_PATH_ROUTE_ID_ATTR)
可以獲取到 routeId 。
最后通過(guò) newurl = routeFilterRepository.findByRouteIdAndCode(routeId, code).toFuture().get().getUrl()
就可以從數(shù)據(jù)庫(kù)中讀取到配置好的后端服務(wù)地址。
拿到后端服務(wù)地址后, 通過(guò)調(diào)用 exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, newUri);
將請(qǐng)求轉(zhuǎn)發(fā)到對(duì)應(yīng)的地址。
六、單元測(cè)試
在單元測(cè)試代碼中,我們預(yù)置了一條后端服務(wù)動(dòng)態(tài)配置數(shù)據(jù):
insert into ROUTE_FILTER_ENTITY values('1','routeOne','alpha','http://httpbin.org/anything')
然后模擬請(qǐng)求 /route1/test?a=test
這個(gè)接口,根據(jù)我們的配置,請(qǐng)求會(huì)被轉(zhuǎn)發(fā)到 http://httpbin.org/anything
。
執(zhí)行單元測(cè)試后,可以從日志中發(fā)現(xiàn),接口返回的數(shù)據(jù)是 http://httpbin.org/anything 這個(gè)后端服務(wù)返回的數(shù)據(jù)。
當(dāng)我們希望調(diào)整后端服務(wù)地址時(shí),只需要把 ROUTE_FILTER_ENTITY 表中的這條配置數(shù)據(jù)中的 url 字段改成其它的任何服務(wù)地址即可,大大增加了程序的靈活度。
到此這篇關(guān)于SpringCloud Gateway動(dòng)態(tài)轉(zhuǎn)發(fā)后端服務(wù)實(shí)現(xiàn)過(guò)程講解的文章就介紹到這了,更多相關(guān)SpringCloud Gateway動(dòng)態(tài)轉(zhuǎn)發(fā)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
淺談Java list.remove( )方法需要注意的兩個(gè)坑
這篇文章主要介紹了淺談Java list.remove( )方法需要注意的兩個(gè)坑,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12Java集合之Comparable和Comparator接口詳解
Java提供了Comparable接口與Comparator接口,它們?yōu)閿?shù)組或集合中的元素提供了排序邏輯,實(shí)現(xiàn)此接口的對(duì)象數(shù)組或集合可以通過(guò)Arrays.sort或Collections.sort進(jìn)行自動(dòng)排序。本文將通過(guò)示例講講它們的使用,需要的可以參考一下2022-12-12java向數(shù)據(jù)庫(kù)插入數(shù)據(jù)顯示亂碼的幾種問(wèn)題解決
這篇文章主要給大家介紹了關(guān)于java向數(shù)據(jù)庫(kù)插入數(shù)據(jù)顯示亂碼問(wèn)題的解決方案,文章分別羅列了前臺(tái)亂碼的問(wèn)題、前臺(tái)先后臺(tái)插入數(shù)據(jù)后臺(tái)接收到的數(shù)據(jù)是亂碼以及后臺(tái)向數(shù)據(jù)庫(kù)插入數(shù)據(jù)是亂碼等幾種情況,需要的朋友可以參考下2021-11-11Javaweb實(shí)現(xiàn)完整個(gè)人博客系統(tǒng)流程
這篇文章主要介紹了怎樣用Java來(lái)實(shí)現(xiàn)一個(gè)完整的個(gè)人博客系統(tǒng),我們通過(guò)實(shí)操上手的方式可以高效的鞏固所學(xué)的基礎(chǔ)知識(shí),感興趣的朋友一起來(lái)看看吧2022-03-03Spring @Profile注解實(shí)現(xiàn)多環(huán)境配置
這篇文章主要介紹了Spring @Profile注解實(shí)現(xiàn)多環(huán)境配置,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04Spring Boot和Kotlin的無(wú)縫整合與完美交融
這篇文章主要給大家介紹了關(guān)于Spring Boot和Kotlin的無(wú)縫整合與完美交融的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2018-06-06Java?Chassis3熔斷機(jī)制的改進(jìn)路程技術(shù)解密
這篇文章主要介紹了Java?Chassis?3技術(shù)解密之熔斷機(jī)制的改進(jìn)路程實(shí)例分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01