VDN 微服務架構搭建篇(三):基于 Nacos 的 Spring Cloud Gateway 動態路由管理
在微服務架構中,網關 是整個系統的入口,負責 流量管理、請求路由、安全控制等關鍵功能。
Spring Cloud Gateway 作為 Spring 生態官方推薦的網關方案,具備 異步非阻塞 的高性能特性,并支持 動態路由、限流、負載均衡等功能。
但在傳統網關架構中,路由規則往往寫死在配置文件中,每次修改都需要 手動調整配置 & 重啟服務,這對于 高可用系統 來說并不友好。
本文將介紹如何使用 Spring Cloud Gateway + Nacos 配置中心 實現 動態路由管理,支持 自動發現、實時更新,讓網關在運行時自動感知微服務變化,無需重啟即可刷新路由規則,從而 提升微服務治理的靈活性和高可用性。
一、Spring Cloud Gateway 基礎配置
1.1 添加依賴
在 pom.xml
中引入 Spring Cloud Gateway 和 Nacos 相關依賴:
<dependencies><!-- Spring Cloud Gateway --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency><!-- Nacos 服務發現 --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><!-- Nacos 配置中心 --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId></dependency><!-- Knife4j 網關文檔 --><dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-gateway-spring-boot-starter</artifactId></dependency>
</dependencies>
1.2 在 Nacos 注冊微服務
在 application.yml
配置 Nacos 相關信息,使服務可以自動注冊到 Nacos:
spring:application:name: vdn-systemcloud:nacos:discovery:server-addr: 127.0.0.1:8848username: nacospassword: nacosgroup: vdnnamespace: dev17config:server-addr: ${spring.cloud.nacos.discovery.server-addr}username: ${spring.cloud.nacos.discovery.username}password: ${spring.cloud.nacos.discovery.password}namespace: ${spring.cloud.nacos.discovery.namespace}group: ${spring.cloud.nacos.discovery.group}file-extension: yaml
1.3 配置網關的靜態路由
在 application.yml
中配置 靜態路由(僅用于演示,后續會改為動態路由):
spring:cloud:gateway:routes:- id: vdn-systemuri: lb://vdn-systempredicates:- Path=/sys/**
二、Nacos 動態路由管理
2.1 在 Nacos 配置中心創建動態路由
(1)在 Nacos 控制臺添加配置
- Data ID:
gateway-routes.json
- Group:
vdn
- Namespace:
dev17
- 內容(JSON 格式):
[{"id": "vdn-system","uri": "lb://vdn-system","predicates": ["Path=/sys/**"]}
]
(2)在 bootstrap.yml
中添加以下配置
spring:application:name: vdn-gatewaycloud:nacos:discovery:# Nacos服務器地址server-addr: localhost:8848username: nacospassword: nacosgroup: vdn# Nacos命名空間namespace: dev17config:server-addr: ${spring.cloud.nacos.discovery.server-addr}username: ${spring.cloud.nacos.discovery.username}password: ${spring.cloud.nacos.discovery.password}namespace: ${spring.cloud.nacos.discovery.namespace}# 配置分組名稱group: ${spring.cloud.nacos.discovery.group}# 文件擴展名,指示配置文件格式file-extension: yaml
2.2 監聽 Nacos 變更 & 動態更新路由
在 NacosRouteDefinitionRepository
類中實現 監聽 Nacos 變更,動態更新 Gateway 路由:
import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.cloud.nacos.NacosConfigProperties;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.common.utils.StringUtils;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionRepository;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;/*** Nacos 動態路由管理* <p>* 該類用于管理 Spring Cloud Gateway 的路由,并將其存儲在 Nacos 配置中心中。* 通過監聽 Nacos 配置的變更,實現網關路由的動態刷新。*/
@Component
public class NacosRouteDefinitionRepository implements RouteDefinitionRepository {private final Logger log = LoggerFactory.getLogger(NacosRouteDefinitionRepository.class);// 用于發布 Spring 事件(刷新路由)private final ApplicationEventPublisher publisher;// Nacos 配置屬性private final NacosConfigProperties nacosConfigProperties;// Nacos 配置管理器private final NacosConfigManager nacosConfigManager;// JSON 解析工具private final ObjectMapper objectMapper;// Nacos 中存儲路由的 `dataId`private static final String DATA_ID = "gateway-routes.json";// 配置獲取超時時間(單位:毫秒)private static final int CONFIG_TIMEOUT_MS = 3000;/*** 構造方法** @param publisher Spring 事件發布器,用于動態刷新網關路由* @param nacosConfigProperties Nacos 配置屬性*/@Autowiredpublic NacosRouteDefinitionRepository(ApplicationEventPublisher publisher, NacosConfigProperties nacosConfigProperties) {this.publisher = publisher;this.nacosConfigProperties = nacosConfigProperties;this.nacosConfigManager = new NacosConfigManager(nacosConfigProperties);this.objectMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);// 初始化 Nacos 配置監聽器nacosListener();}/*** Nacos 配置監聽器* <p>* 監聽 Nacos 中 `gateway-routes.json` 發生變更時,觸發 Gateway 重新加載路由。*/private void nacosListener() {// 使用單線程線程池,避免 Listener 需要 Executor 時出錯ExecutorService executorService = Executors.newSingleThreadExecutor();try {nacosConfigManager.getConfigService().addListener(DATA_ID, nacosConfigProperties.getGroup(), new Listener() {@Overridepublic Executor getExecutor() {return executorService;}@Overridepublic void receiveConfigInfo(String configInfo) {log.info("收到新的路由配置: {}", configInfo);// 發布 Spring 事件,觸發網關路由刷新publisher.publishEvent(new RefreshRoutesEvent(this));}});} catch (NacosException e) {log.error("Nacos 監聽器初始化失敗", e);} finally {// 關閉線程池,避免資源泄露executorService.shutdown();}}/*** 獲取 Nacos 中的路由配置* <p>* 該方法會從 Nacos 讀取 `gateway-routes.json` 配置,并解析為 RouteDefinition 列表。** @return 返回 Flux<RouteDefinition>,用于 Gateway 加載路由*/@Overridepublic Flux<RouteDefinition> getRouteDefinitions() {try {// 從 Nacos 讀取配置String routeConfig = nacosConfigManager.getConfigService().getConfig(DATA_ID, nacosConfigProperties.getGroup(), CONFIG_TIMEOUT_MS);// 路由列表List<RouteDefinition> routeDefinitionList = new ArrayList<>();// 如果配置不為空,則解析 JSONif (StringUtils.hasText(routeConfig)) {routeDefinitionList = objectMapper.readValue(routeConfig, new TypeReference<>() {});}return Flux.fromIterable(routeDefinitionList);} catch (Exception e) {log.error("從 Nacos 獲取路由定義失敗", e);return Flux.error(e);}}/*** 保存路由定義* <p>* 該方法會將新的路由定義追加到 `gateway-routes.json` 中,并同步更新到 Nacos。** @param route 需要保存的路由定義* @return 返回 Mono<Void>*/@Overridepublic Mono<Void> save(Mono<RouteDefinition> route) {return route.flatMap(r -> {try {// 將新的 RouteDefinition 轉換為 JSONString routeJson = objectMapper.writeValueAsString(r);// 發布到 NacosnacosConfigManager.getConfigService().publishConfig(DATA_ID, nacosConfigProperties.getGroup(), routeJson);return Mono.empty();} catch (Exception e) {log.error("保存路由定義失敗", e);return Mono.error(e);}});}/**·* 刪除路由定義* <p>* 該方法會從 `gateway-routes.json` 中移除指定的路由 ID,并同步更新 Nacos。** @param routeId 需要刪除的路由 ID* @return 返回 Mono<Void>*/@Overridepublic Mono<Void> delete(Mono<String> routeId) {return routeId.flatMap(id -> {try {// 獲取 Nacos 中的路由配置String routeConfig = nacosConfigManager.getConfigService().getConfig(DATA_ID, nacosConfigProperties.getGroup(), CONFIG_TIMEOUT_MS);// 解析 JSON 為 RouteDefinition 列表List<RouteDefinition> routeDefinitionList = objectMapper.readValue(routeConfig, new TypeReference<>() {});// 刪除指定 ID 的路由routeDefinitionList.removeIf(rd -> rd.getId().equals(id));// 重新生成 JSON 并更新到 NacosString updatedRouteJson = objectMapper.writeValueAsString(routeDefinitionList);nacosConfigManager.getConfigService().publishConfig(DATA_ID, nacosConfigProperties.getGroup(), updatedRouteJson);return Mono.empty();} catch (Exception e) {log.error("刪除路由定義失敗,ID: {}", id, e);return Mono.error(e);}});}
}
三、總結
- 動態路由刷新:網關無需重啟,即可自動刷新路由配置。
- 中心化管理:所有路由規則存儲于 Nacos 配置中心,便于維護。
- 自動發現 & 負載均衡:結合 Nacos 注冊中心,可自動發現新微服務并添加路由。
通過本文的介紹,你已經掌握了 如何基于 Spring Cloud Gateway + Nacos 實現動態路由管理 🎯。
你可以在項目中直接應用這個方案,讓網關更智能、更高效! 🚀🚀🚀
寫在最后
上一篇:👉 VDN 微服務架構搭建篇(二)服務注冊與配置中心Nacos
下一篇:👉 待完善
源碼🚀🚀🚀