Spring Cloud Alibaba | Gateway基於Nacos動態網關路由
本篇實戰所使用Spring有關版本:
SpringBoot:2.1.7.RELEASE
Spring Cloud:Greenwich.SR2
Spring CLoud Alibaba:2.1.0.RELEASE
前面幾篇文章我們介紹了《Nacos服務注冊與發現》和《Nacos配置管理》,還沒看過的小伙伴們快去看一下,本篇文章是建立在這兩篇文章基礎上的一次實戰。
背景介紹
在Spring Cloud微服務體系下,常用的服務網關有Netflix公司開源的Zuul,還有Spring Cloud團隊自己開源的Spring Cloud Gateway,其中NetFlix公司開源的Zuul版本已經迭代至2.x,但是Spring Cloud並未集成,目前Spring Cloud集成的Spring Cloud Zuul還是Zuul1.x,這一版的Zuul是基於Servlet
構建的,采用的方案是阻塞式的多線程方案,即一個線程處理一次連接請求,這種方式在內部延遲嚴重、設備故障較多情況下會引起存活的連接增多和線程增加的情況發生。Spring Cloud自己開源的Spring Cloud Gateway則是基於Spring Webflux
來構建的,Spring Webflux
有一個全新的非堵塞的函數式 Reactive Web
框架,可以用來構建異步的、非堵塞的、事件驅動的服務,在伸縮性方面表現非常好。使用非阻塞API, Websockets得到支持,並且由於它與Spring緊密集成,將會得到更好的開發體驗。
本文將基於Gateway服務網關來介紹如何使用Nacos的配置功能來實現服務網關動態路由。
實現方案
在開始之前我們先介紹一下具體實現方式:
- 路由信息不再配置在配置文件中,將路由信息配置在Nacos的配置中。
- 在服務網關Spring Cloud Gateway中開啟監聽,監聽Nacos配置文件的修改。
- Nacos配置文件一旦發生改變,則Spring Cloud Gateway重新刷新自己的路由信息。
環境准備
首先,需要准備一個Nacos服務,我這里的版本是使用的Nacos v1.1.3,如果不會配置Nacos服務的同學,請參考之前的文章《Nacos服務中心初探》
工程實戰
創建工程gateway-nacos-config,工程依賴pom.xml如下:
<?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 https://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.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.springcloud.alibaba</groupId>
<artifactId>gateway-nacos-config</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>gateway-nacos-config</name>
<description>gateway-nacos-config</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR2</spring-cloud.version>
<spring-cloud-alibaba.version>2.1.0.RELEASE</spring-cloud-alibaba.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</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>
</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>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</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>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- 在使用Spring Cloud Alibaba組件的時候,在
<dependencyManagement>
中需配置spring-cloud-alibaba-dependencies
,它管理了Spring Cloud Alibaba組件的版本依賴。
配置文件application.yml如下:
server:
port: 8080
spring:
application:
name: spring-cloud-gateway-server
cloud:
nacos:
discovery:
server-addr: 192.168.44.129:8848
management:
endpoints:
web:
exposure:
include: '*'
spring.cloud.nacos.discovery.server-addr
:配置為Nacos服務地址,格式為ip:port
接下來進入核心部分,配置Spring Cloud Gateway動態路由,這里需要實現一個Spring提供的事件推送接口ApplicationEventPublisherAware
,代碼如下:
@Component
public class DynamicRoutingConfig implements ApplicationEventPublisherAware {
private final Logger logger = LoggerFactory.getLogger(DynamicRoutingConfig.class);
private static final String DATA_ID = "zuul-refresh-dev.json";
private static final String Group = "DEFAULT_GROUP";
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
private ApplicationEventPublisher applicationEventPublisher;
@Bean
public void refreshRouting() throws NacosException {
Properties properties = new Properties();
properties.put(PropertyKeyConst.SERVER_ADDR, "192.168.44.129:8848");
properties.put(PropertyKeyConst.NAMESPACE, "8282c713-da90-486a-8438-2a5a212ef44f");
ConfigService configService = NacosFactory.createConfigService(properties);
configService.addListener(DATA_ID, Group, new Listener() {
@Override
public Executor getExecutor() {
return null;
}
@Override
public void receiveConfigInfo(String configInfo) {
logger.info(configInfo);
boolean refreshGatewayRoute = JSONObject.parseObject(configInfo).getBoolean("refreshGatewayRoute");
if (refreshGatewayRoute) {
List<RouteEntity> list = JSON.parseArray(JSONObject.parseObject(configInfo).getString("routeList")).toJavaList(RouteEntity.class);
for (RouteEntity route : list) {
update(assembleRouteDefinition(route));
}
} else {
logger.info("路由未發生變更");
}
}
});
}
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
/**
* 路由更新
* @param routeDefinition
* @return
*/
public void update(RouteDefinition routeDefinition){
try {
this.routeDefinitionWriter.delete(Mono.just(routeDefinition.getId()));
logger.info("路由更新成功");
}catch (Exception e){
logger.error(e.getMessage(), e);
}
try {
routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
this.applicationEventPublisher.publishEvent(new RefreshRoutesEvent(this));
logger.info("路由更新成功");
}catch (Exception e){
logger.error(e.getMessage(), e);
}
}
public RouteDefinition assembleRouteDefinition(RouteEntity routeEntity) {
RouteDefinition definition = new RouteDefinition();
// ID
definition.setId(routeEntity.getId());
// Predicates
List<PredicateDefinition> pdList = new ArrayList<>();
for (PredicateEntity predicateEntity: routeEntity.getPredicates()) {
PredicateDefinition predicateDefinition = new PredicateDefinition();
predicateDefinition.setArgs(predicateEntity.getArgs());
predicateDefinition.setName(predicateEntity.getName());
pdList.add(predicateDefinition);
}
definition.setPredicates(pdList);
// Filters
List<FilterDefinition> fdList = new ArrayList<>();
for (FilterEntity filterEntity: routeEntity.getFilters()) {
FilterDefinition filterDefinition = new FilterDefinition();
filterDefinition.setArgs(filterEntity.getArgs());
filterDefinition.setName(filterEntity.getName());
fdList.add(filterDefinition);
}
definition.setFilters(fdList);
// URI
URI uri = UriComponentsBuilder.fromUriString(routeEntity.getUri()).build().toUri();
definition.setUri(uri);
return definition;
}
}
這里主要介紹一下refreshRouting()
這個方法,這個方法主要負責監聽Nacos的配置變化,這里先使用參數構建一個ConfigService
,再使用ConfigService
開啟一個監聽,並且在監聽的方法中刷新路由信息。
Nacos配置如圖:
{
"refreshGatewayRoute":false,
"routeList":[
{
"id":"github_route",
"predicates":[
{
"name":"Path",
"args":{
"_genkey_0":"/meteor1993"
}
}
],
"filters":[
],
"uri":"https://github.com",
"order":0
}
]
}
配置格式選擇JSON,Data ID和Group與程序中的配置保持一致,注意,我這里的程序配置了namespace,如果使用默認namespace,可以不用配置。
這里配置了一個路由/meteor1993
,直接訪問這個路由會訪問到作者的Github倉庫。
剩余部分的代碼這里就不一一展示了,已經上傳至代碼倉庫,有需要的同學可以自行取用。
測試
啟動工程,這時是沒有任何路由信息的,打開瀏覽器訪問:http://localhost:8080/meteor1993 ,頁面返回404報錯信息,如圖:
同時,也可以訪問鏈接:http://localhost:8080/actuator/gateway/routes ,可以看到如下打印:
[]
打開在Nacos Server端的UI界面,選擇監聽查詢,選擇namespace為springclouddev
的欄目,輸入DATA_ID為zuul-refresh-dev.json
和Group為DEFAULT_GROUP
,點擊查詢,可以看到我們啟動的工程gateway-nacos-config正在監聽Nacos Server端,如圖:
筆者這里的本地ip為:192.168.44.1。監聽正常,這時,我們修改剛才創建的配置,將里面的refreshGatewayRoute
修改為true
,如下:
{"refreshGatewayRoute": true, "routeList":[{"id":"github_route","predicates":[{"name":"Path","args":{"_genkey_0":"/meteor1993"}}],"filters":[],"uri":"https://github.com","order":0}]}
點擊發布,可以看到工程gateway-nacos-config的控制台打印日志如下:
2019-09-02 22:09:49.254 INFO 8056 --- [38-2a5a212ef44f] c.s.a.g.config.DynamicRoutingConfig : {
"refreshGatewayRoute":true,
"routeList":[
{
"id":"github_route",
"predicates":[
{
"name":"Path",
"args":{
"_genkey_0":"/meteor1993"
}
}
],
"filters":[
],
"uri":"https://github.com",
"order":0
}
]
}
2019-09-02 22:09:49.268 INFO 8056 --- [38-2a5a212ef44f] c.s.a.g.config.DynamicRoutingConfig : 路由更新成功
這時,我們的工程gateway-nacos-config的路由已經更新成功,訪問路徑:http://localhost:8080/actuator/gateway/routes ,可以看到如下打印:
[{"route_id":"github_route","route_definition":{"id":"github_route","predicates":[{"name":"Path","args":{"_genkey_0":"/meteor1993"}}],"filters":[],"uri":"https://github.com","order":0},"order":0}]
我們再次在瀏覽器中訪問鏈接:http://localhost:8080/meteor1993 ,可以看到頁面正常路由到Github倉庫,如圖:
總結
至此,Nacos動態網關路由就介紹完了,主要運用了服務網關端監聽Nacos配置改變的功能,實現服務網關路由配置動態刷新,同理,我們也可以使用服務網關Zuul來實現基於Nacos的動態路由功能。
基於這個思路,我們可以使用配置中心來實現網關的動態路由,而不是使用服務網關本身自帶的配置文件,這樣每次路由信息變更,無需修改配置文件而后重啟服務。
目前市面上使用比較多的配置中心有攜程開源的Apollo,服務網關還有Spring Cloud Zuul,下一篇文章我們介紹如何使用Apollo來實現Spring Cloud Zuul的動態路由。