基於Nacos配置中心實現Spring Cloud Gateway的動態路由管理

前面我們瞭解過了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加載 dataIdspring-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);
}

NacosDynamicRouteServiceImplNacosDynamicRouteService實現,我們將已存在的路由刪除,重新添加,來保證內存中路由的一致性。

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:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章