前面我们了解过了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:
在这里插入图片描述

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐