前面我們瞭解過了Sentinel 網關流量控制之Spring Cloud Gateway實戰,今天帶給大家是基於Nacos配置中心實現Spring Cloud Gateway的動態路由
管理。
1.爲什要使用nacos來實現動態路由管理
大家如果瞭解Spring Cloud Gateway
啓動過程的話,應該都知道Spring Cloud Gateway
啓動時,就將yml配置文件中的路由配置和規則
加載到內存
裏,使用InMemoryRouteDefinitionRepository
來管理。但是我們的上線項目一般都無法做到不重啓網關,就可以添加或刪除一個新的路由配置和規則。於是這是Nacos
就可以出場了,來擔任次任務。當我們需要添加或刪除一個新的路由配置和規則,我們直接通過·Nacos配置中心下發添加或者刪除路由的功能,網關監聽配置的更改,就可以輕鬆實現在不重啓網關的情況下,實現動態路由管理。
2.使用nacos-config-spring-boot-starter 實現自動配置
2.1 添加依賴
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.lidong</groupId>
<artifactId>spring-cloud-gateway-service-dynamic-nacos</artifactId>
<version>1.0.0</version>
<name>spring-cloud-gateway-service-dynamic-nacos</name>
<description>基於Nacos實現Spring Cloud Gateway的動態管理</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.RELEASE</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>nacos-config-spring-boot-starter</artifactId>
<version>0.2.3</version>
</dependency>
<dependency>
<groupId>com.alibaba.boot</groupId>
<artifactId>nacos-config-spring-boot-actuator</artifactId>
<version>0.2.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.51</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
</project>
spring-cloud-starter-gateway
:路由轉發、請求過濾(權限校驗、限流以及監控等)spring-boot-starter-actuator
:監控網關的健康狀況nacos-config-spring-boot-starter
:springboot實現的nacos的自動化配置nacos-config-spring-boot-actuator
:springboot實現的nacos的健康spring-cloud-starter-alibaba-nacos-discovery
:nacos 作爲註冊中心
這裏爲什麼沒有使用spring-cloud-starter-alibaba-nacos-config
是因爲目前2.1.0版本對json的支持有點問題。這裏採用的springboot
的版本來實現
2.2 配置文件
2.2.1 application.yml 配置文件
server:
port: 9921 #網關的端口
management:
endpoints:
web:
exposure:
include: '*' #暴露端點,這樣actuator就可以監控的到健康狀況
logging:
level:
org.springframework.cloud.gateway: trace
org.springframework.http.server.reactive: debug
org.springframework.web.reactive: debug
reactor.ipc.netty: debug
# config: classpath:logback-spring.xml
nacos:
config:
server-addr: 127.0.0.1:8848 #nacos的serverAdd配置
group: NAOCS-SPRING-CLOUD-GATEWAY #分組的配置
file-extension: json
data-id: spring-cloud-gateway.json #data-id的配置
2.2.1 bootstrap.yml 配置文件
spring:
application:
name: spring-cloud-gateway-service-dynamic-nacos
jackson:
serialization:
indent-output: true
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 #naocs配置中心地址
gateway:
discovery: #是否與服務發現組件進行結合,通過 serviceId(必須設置成大寫) 轉發到具體的服務實例。
#默認爲false,
#設爲true便開啓通過服務中心的自動根據 serviceId 創建路由的功能。
locator: #路由訪問方式:http://Gateway_HOST:Gateway_PORT/大寫的serviceId/**,其中微服務應用名默認大寫訪問。
enabled: false
enabled: true #如果包含啓動程序,但出於某些原因,不希望啓用網關,則設置spring.cloud.gateway.enabled=false
routes:
- id: 163
uri: http://www.163.com/
predicates:
- Path=/163/**
2.3 使用 @NacosPropertySource加載配置源
使用 @NacosPropertySource
加載 dataId
爲spring-cloud-gateway.json
的配置源,並開啓自動更新:
package com.lidong.gateway.config;
import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.api.annotation.NacosInjected;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.spring.context.annotation.config.NacosPropertySource;
import com.lidong.gateway.service.NacosDynamicRouteService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.concurrent.Executor;
@Slf4j
@Component
@NacosPropertySource(dataId = "${nacos.config.data-id}", autoRefreshed = true, groupId = "${nacos.config.group}")
public class NacosGatewayDefineConfig implements CommandLineRunner {
@NacosInjected
private ConfigService configService;
@Value("${nacos.config.data-id}")
private String dataId;
@Value("${nacos.config.group}")
private String group;
@Autowired
NacosDynamicRouteService nacosDynamicRouteService;
/**
* Callback used to run the bean.
*
* @param args incoming main method arguments
* @throws Exception on error
*/
@Override
public void run(String... args) throws Exception {
addRouteNacosListen();
}
/**
* 添加動態路由監聽器
*/
private void addRouteNacosListen() {
try {
String configInfo = configService.getConfig(dataId, group, 5000);
log.info("從Nacos返回的配置:" + configInfo);
getNacosDataRoutes(configInfo);
//註冊Nacos配置更新監聽器,用於監聽觸發
configService.addListener(dataId, group, new Listener() {
@Override
public void receiveConfigInfo(String configInfo) {
log.info("Nacos更新了!");
log.info("接收到數據:"+configInfo);
getNacosDataRoutes(configInfo);
}
@Override
public Executor getExecutor() {
return null;
}
});
} catch (NacosException e) {
log.error("nacos-addListener-error", e);
e.printStackTrace();
}
}
/**
* 從Nacos返回的配置
* @param configInfo
*/
private void getNacosDataRoutes(String configInfo) {
List<RouteDefinition> list = JSON.parseArray(configInfo, RouteDefinition.class);
list.stream().forEach(definition -> {
log.info(""+JSON.toJSONString(definition));
nacosDynamicRouteService.update(definition);
});
}
}
2.4 定義NacosDynamicRouteService實現路由的更新
package com.lidong.gateway.service;
import org.springframework.cloud.gateway.route.RouteDefinition;
/**
* 更新內存中的路由信息
*/
public interface NacosDynamicRouteService {
/**
* 更新路由信息
* @param gatewayDefine
* @return
* @throws Exception
*/
String update(RouteDefinition gatewayDefine);
}
NacosDynamicRouteServiceImpl
是NacosDynamicRouteService
實現,我們將已存在的路由刪除,重新添加,來保證內存中路由的一致性。
package com.lidong.gateway.service;
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.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
@Service
public class NacosDynamicRouteServiceImpl implements NacosDynamicRouteService {
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
@Autowired
private ApplicationEventPublisher publisher;
/**
* 更新路由
* 只能是先刪除在添加,由於沒有提供更新路由的方法
*
* @param definition
* @return
*/
@Override
public String update(RouteDefinition definition) {
try {
this.routeDefinitionWriter.delete(Mono.just(definition.getId()));
} catch (Exception e) {
return "刪除路由失敗: RouteId:" + definition.getId();
}
try {
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return "更新路由成功";
} catch (Exception e) {
return "更新路由失敗";
}
}
}
這裏我們使用RouteDefinitionWriter
來對路由進行操作,最後使用 ApplicationEventPublisher
發佈事件,去刷新路由信息。
2.5啓動類GatewayApplication的實現
package com.lidong.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
/**
*
*
* CachingRouteDefinitionLocator 緩存目標
* CompositeRouteDefinitionLocator 組合多種
* DiscoveryClientRouteDefinitionLocator 從註冊中心
* InMemoryRouteDefinitionRepository 讀取內存中的
* PropertiesRouteDefinitionLocator 讀取配置文件 GatewayProperties yml/properties
* RouteDefinitionRepository 從存儲器讀取
*
* @Bean
* @ConditionalOnMissingBean(RouteDefinitionRepository.class)
* public InMemoryRouteDefinitionRepository inMemoryRouteDefinitionRepository() {
* return new InMemoryRouteDefinitionRepository();
* }
* 通過上面代碼,可以看到如果沒有RouteDefinitionRepository的實例,
* 則默認用InMemoryRouteDefinitionRepository。而做動態路由的關鍵就在這裏。
* 即通過自定義的RouteDefinitionRepository類,來提供路由配置信息。
*
*/
@EnableDiscoveryClient //開啓服務發現
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
3 測試網關
3.1在nacos控制檯創建如下配置文件
- data-id: spring-cloud-gateway.json
- group: NAOCS-SPRING-CLOUD-GATEWAY #分組的配置
- 配置格式:json
- 配置內容
[
{
"id": "aliyun_route","uri":"https://www.aliyun.com/","order": 0,
"filters": [],
"predicates":
[{"args": {"pattern":"/product/**"},"name":"Path"}]
},
{
"id": "aliyun_route1","uri":"https://www.aliyun.com/","order": 0,
"filters": [],
"predicates":
[{"args": {"pattern":"/product1/**"},"name":"Path"}]
}
]
3.2 啓動GatewayApplication啓動類
2019-09-17 17:21:30.714 INFO 10512 --- [ restartedMain] c.l.g.config.NacosGatewayDefineConfig : 從Nacos返回的配置:[
{
"id": "aliyun_route","uri":"https://www.aliyun.com/","order": 0,
"filters": [],
"predicates":
[{"args": {"pattern":"/product/**"},"name":"Path"}]
},
{
"id": "aliyun_route1","uri":"https://www.aliyun.com/","order": 0,
"filters": [],
"predicates":
[{"args": {"pattern":"/product1/**"},"name":"Path"}]
}
]
2019-09-17 17:21:30.727 INFO 10512 --- [ restartedMain] c.l.g.config.NacosGatewayDefineConfig : {"filters":[],"id":"aliyun_route","order":0,"predicates":[{"args":{"pattern":"/product/**"},"name":"Path"}],"uri":"https://www.aliyun.com/"}
2019-09-17 17:21:30.732 INFO 10512 --- [ restartedMain] c.l.g.config.NacosGatewayDefineConfig : {"filters":[],"id":"aliyun_route1","order":0,"predicates":[{"args":{"pattern":"/product1/**"},"name":"Path"}],"uri":"https://www.aliyun.com/"}
從日誌我們可以看出,網關已經加載到了路由信息。
現在我們在瀏覽器訪問: http://localhost:9921/actuator/gateway/routes
我們在使用http://localhost:9921/product/ahas訪問,返回瞭如下頁面。
到這裏基於Nacos配置中心實現Spring Cloud Gateway的動態路由管理就基本ok。歡迎大家評論,一起交流。
源代碼已經上傳github:https://github.com/lidong1665/spring-cloud-learning-example/tree/master/spring-cloud-gateway-service-dynamic-nacos
最後想一起交流技術的可以加我wx: