【微服务实战系列】 GateWay动态路由Nacos配置实时生效代码实现

​ 配置文件里面编写的路由规则,只有在重启应用系统才会生效,本部分主要讲解如何动态修改路由规则及时生效,而不用重启系统。在上一章节中的micro-app-gateway的pom.xml中添加nacos作为配置中心的类依赖。

       <dependency>
            <groupId>com.alibaba.cloudgroupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
        dependency>

​ 将上章讲解的配置文件全部移到bak文件夹中,新建一个bootstrap.yml让应用都从nacos中读取信息。bootstrap.yml文件的内容如下:

spring:
  profiles:
    active: nacos_gateway
  application:
    name: micro-app-gateway
  main:
    allow-bean-definition-overriding: true
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
        namespace: ${spring.profiles.active}
      config:
        server-addr: 127.0.0.1:8848
        namespace: ${spring.profiles.active}
        file-extension: yaml

# 这个地方独立配置, 是网关的数据, 代码 GatewayConfig.java 中读取被监听
nacos:
  gateway:
    route:
      config:
        data-id: dream21th-gateway-router
        group: DEFAULT_GROUP

server:
  port: 9999

​ 在nacos中新建一个nacos_gateway的命名空间

【微服务实战系列】 GateWay动态路由Nacos配置实时生效代码实现_第1张图片

​ 在命名空间nacos_gateway新增一个dream21th-gateway-router的配置文件,文件内容如下:

【微服务实战系列】 GateWay动态路由Nacos配置实时生效代码实现_第2张图片

[
  {
    "id": "micro-app-c",
    "predicates": [
      {
        "args": {
          "pattern": "/dream21th/**"
        },
        "name": "Path"
      }
    ],
    "uri": "lb://micro-app-c",
    "filters": [ 
      {
        "name": "StripPrefix",
        "args": {
          "parts": "1"
        }
      }
    ]
  }
]

​ 编写一个网关的nacos配置文件的类,代码如下,主要包含连接nacos的超时时间,服务器地址,命名空间,data-id等:

package com.dream21th.gateway.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

/**
 * 配置类, 读取 Nacos 相关的配置项, 用于配置监听器
 * */
@Configuration
public class GatewayConfig {

    /** 读取配置的超时时间 */
    public static final long DEFAULT_TIMEOUT = 30000;

    /** Nacos 服务器地址 */
    public static String NACOS_SERVER_ADDR;

    /** 命名空间 */
    public static String NACOS_NAMESPACE;

    /** data-id */
    public static String NACOS_ROUTE_DATA_ID;

    /** 分组 id */
    public static String NACOS_ROUTE_GROUP;

    @Value("${spring.cloud.nacos.discovery.server-addr}")
    public void setNacosServerAddr(String nacosServerAddr) {
        NACOS_SERVER_ADDR = nacosServerAddr;
    }

    @Value("${spring.cloud.nacos.discovery.namespace}")
    public void setNacosNamespace(String nacosNamespace) {
        NACOS_NAMESPACE = nacosNamespace;
    }

    @Value("${nacos.gateway.route.config.data-id}")
    public void setNacosRouteDataId(String nacosRouteDataId) {
        NACOS_ROUTE_DATA_ID = nacosRouteDataId;
    }

    @Value("${nacos.gateway.route.config.group}")
    public void setNacosRouteGroup(String nacosRouteGroup) {
        NACOS_ROUTE_GROUP = nacosRouteGroup;
    }
}

​ 编写一个gateway动态路由刷新类继承ApplicationEventPublisherAware接口,代码如下:

package com.dream21th.gateway.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import reactor.core.publisher.Mono;
import java.util.List;

/**
 * 事件推送 Aware: 动态更新路由网关 Service
 * */
@Slf4j
@Service
@SuppressWarnings("all")
public class DynamicRouteServiceImpl implements ApplicationEventPublisherAware {

    /** 写路由定义 */
    private final RouteDefinitionWriter routeDefinitionWriter;
    /** 获取路由定义 */
    private final RouteDefinitionLocator routeDefinitionLocator;

    /** 事件发布 */
    private ApplicationEventPublisher publisher;

    public DynamicRouteServiceImpl(RouteDefinitionWriter routeDefinitionWriter,
                                   RouteDefinitionLocator routeDefinitionLocator) {
        this.routeDefinitionWriter = routeDefinitionWriter;
        this.routeDefinitionLocator = routeDefinitionLocator;
    }

    @Override
    public void setApplicationEventPublisher(
            ApplicationEventPublisher applicationEventPublisher) {
        // 完成事件推送句柄的初始化
        this.publisher = applicationEventPublisher;
    }

    /**
     * 增加路由定义
     * */
    public String addRouteDefinition(RouteDefinition definition) {

        log.info("添加路由规则: {}", definition);

        // 保存路由配置并发布
        routeDefinitionWriter.save(Mono.just(definition)).subscribe();
        // 发布事件通知给 Gateway, 同步新增的路由定义
        this.publisher.publishEvent(new RefreshRoutesEvent(this));

        return "success";
    }

    /**
     * 更新路由
     * */
    public String updateList(List<RouteDefinition> definitions) {

        log.info("更新路由规则: {}", definitions);

        // 先拿到当前 Gateway 中存储的路由定义
        List<RouteDefinition> routeDefinitionsExits =
                routeDefinitionLocator.getRouteDefinitions().buffer().blockFirst();
        if (!CollectionUtils.isEmpty(routeDefinitionsExits)) {
            // 清除掉之前所有的 "旧的" 路由定义
            routeDefinitionsExits.forEach(rd -> {
                log.info("删除路由规则:{}", rd);
                deleteById(rd.getId());
            });
        }

        // 把更新的路由定义同步到 gateway 中
        definitions.forEach(definition -> updateByRouteDefinition(definition));
        return "success";
    }

    /**
     * 根据路由 id 删除路由配置
     * */
    private String deleteById(String id) {

        try {
            log.info("删除路由id: [{}]", id);
            this.routeDefinitionWriter.delete(Mono.just(id)).subscribe();
            // 发布事件通知给 gateway 更新路由定义
            this.publisher.publishEvent(new RefreshRoutesEvent(this));
            return "delete success";
        } catch (Exception ex) {
            log.error("路由删除失败: {}", ex.getMessage(), ex);
            return "delete fail";
        }
    }

    /**
     * 更新路由
     * 更新的实现策略比较简单: 删除 + 新增 = 更新
     * */
    private String updateByRouteDefinition(RouteDefinition definition) {

        try {
            log.info("更新路由规则: {}", definition);
            this.routeDefinitionWriter.delete(Mono.just(definition.getId()));
        } catch (Exception ex) {
            return "更新路由规则失败: " + definition.getId();
        }

        try {
            this.routeDefinitionWriter.save(Mono.just(definition)).subscribe();
            this.publisher.publishEvent(new RefreshRoutesEvent(this));
            return "success";
        } catch (Exception ex) {
            return "更新路由规则";
        }
    }
}

​ 编写一个从nacos中拉取网关的配置文件信息类,并刷新内存中的路由信息:

package com.dream21th.gateway.config;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.api.NacosFactory;
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.common.utils.CollectionUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Executor;

/**
 * 通过 nacos 下发动态路由配置, 监听 Nacos 中路由配置变更
 * */
@Slf4j
@Component
@DependsOn({"gatewayConfig"})
public class DynamicRouteServiceImplByNacos {

    /** Nacos 配置服务 */
    private ConfigService configService;

    private final DynamicRouteServiceImpl dynamicRouteService;

    public DynamicRouteServiceImplByNacos(DynamicRouteServiceImpl dynamicRouteService) {
        this.dynamicRouteService = dynamicRouteService;
    }

    /**
     * 

Bean 在容器中构造完成之后会执行 init 方法

* */
@PostConstruct public void init() { log.info("gateway route init...."); try { // 初始化 Nacos 配置客户端 configService = initConfigService(); if (null == configService) { log.error("init config service fail"); return; } // 通过 Nacos Config 并指定路由配置路径去获取路由配置 String configInfo = configService.getConfig( GatewayConfig.NACOS_ROUTE_DATA_ID, GatewayConfig.NACOS_ROUTE_GROUP, GatewayConfig.DEFAULT_TIMEOUT ); log.info("get current gateway config: [{}]", configInfo); List<RouteDefinition> definitionList = JSONObject.parseArray(configInfo, RouteDefinition.class); if (CollectionUtils.isNotEmpty(definitionList)) { for (RouteDefinition definition : definitionList) { log.info("init gateway config: [{}]", definition.toString()); dynamicRouteService.addRouteDefinition(definition); } } } catch (Exception ex) { log.error("gateway route init has some error: [{}]", ex.getMessage(), ex); } // 设置监听器 dynamicRouteByNacosListener(GatewayConfig.NACOS_ROUTE_DATA_ID, GatewayConfig.NACOS_ROUTE_GROUP); } /** * 初始化 Nacos Config * */ private ConfigService initConfigService() { try { Properties properties = new Properties(); properties.setProperty("serverAddr", GatewayConfig.NACOS_SERVER_ADDR); properties.setProperty("namespace", GatewayConfig.NACOS_NAMESPACE); return configService = NacosFactory.createConfigService(properties); } catch (Exception ex) { log.error("init gateway nacos config error: [{}]", ex.getMessage(), ex); return null; } } /** *

监听 Nacos 下发的动态路由配置

* */
private void dynamicRouteByNacosListener(String dataId, String group) { try { // 给 Nacos Config 客户端增加一个监听器 configService.addListener(dataId, group, new Listener() { /** * 自己提供线程池执行操作 * */ @Override public Executor getExecutor() { return null; } /** * 监听器收到配置更新 * @param configInfo Nacos 中最新的配置定义 * */ @Override public void receiveConfigInfo(String configInfo) { log.info("start to update config: [{}]", configInfo); List<RouteDefinition> definitionList = JSONObject.parseArray(configInfo, RouteDefinition.class); log.info("update route: [{}]", definitionList.toString()); dynamicRouteService.updateList(definitionList); } }); } catch (NacosException ex) { log.error("dynamic update gateway config error: [{}]", ex.getMessage(), ex); } } }

​ 完成上面编写的内容后,启动项目micro-app-gateway,micro-app-c(将启动分支设置为nacos_gateway)。在浏览器中调用http://127.0.0.1:9999/dream21th/dev/test,发现正常响应。

​ 打开nacos配置中心,修改dream21th-gateway-router,加入路由micro-app-c-1。

[
  {
    "id": "micro-app-c",
    "predicates": [
      {
        "args": {
          "pattern": "/dream21th/**"
        },
        "name": "Path"
      }
    ],
    "uri": "lb://micro-app-c",
    "filters": [ 
      {
        "name": "StripPrefix",
        "args": {
          "parts": "1"
        }
      }
    ]
  },
    {
    "id": "micro-app-c-1",
    "predicates": [
      {
        "args": {
          "pattern": "/app/**"
        },
        "name": "Path"
      }
    ],
    "uri": "lb://micro-app-c",
    "filters": [ 
      {
        "name": "StripPrefix",
        "args": {
          "parts": "1"
        }
      }
    ]
  }
]

​ 发布后,可以看到micro-app-gateway后台会重新拿到修改后的内容文件。接着在浏览器调用接口http://127.0.0.1:9999/app/dev/test,正常响应,说明动态添加的路由生效。

你可能感兴趣的:(微服务实战系列,微服务,gateway,java)