SpringCloud系列教程:微服务的未来(十七)监听Nacos配置变更、更新路由、实现动态路由

前言

在微服务架构中,API 网关是各个服务之间的入口点,承担着路由、负载均衡、安全认证等重要功能。为了实现动态的路由配置管理,通常需要通过中心化的配置管理系统来实现灵活的路由更新,而无需重启网关服务。Nacos 作为一个开源的动态服务发现与配置管理平台,可以方便地实现这一目标。本文将介绍如何利用 Nacos 配置中心来动态更新 Spring Cloud Gateway 的路由配置,确保路由信息的实时更新,并提升系统的可维护性和灵活性。


动态路由

监听Nacos配置变更

要实现动态路由首先要将路由配置保存到Nacos,当Nacos中的路由配置变更时,推送最新配置到网关,实时更新网关中的路由信息。

有两件事需要做:

  1. 监听Nacos配置变更的消息
  2. 当配置变更时,将最新的路由信息更新到网关路由表

在Nacos官网中给出了手动监听Nacos配置变更的SDK:(详情在文档里面)
Nacos文档

如果希望 Nacos 推送配置变更,可以使用 Nacos 动态监听配置接口来实现。

java 复制代码
public void addListener(String dataId, String group, Listener listener) 
参数名 参数类型 描述
dataId String 配置 ID,采用类似 package.class(如com.taobao.tc.refund.log.level)的命名规则保证全局唯一性,class 部分建议是配置的业务含义。 全部字符小写。只允许英文字符和 4 种特殊字符("."、":"、"-"、"_")。不超过 256 字节。
group String 配置分组,建议填写产品名:模块名(如 Nacos:Test)保证唯一性。 只允许英文字符和4种特殊字符("."、":"、"-"、"_"),不超过128字节。
listener Listener 监听器,配置变更进入监听器的回调函数。

示例代码:

java 复制代码
String serverAddr = "{serverAddr}";
String dataId = "{dataId}";
String group = "{group}";
Properties properties = new Properties();
properties.put("serverAddr", serverAddr);
//服务器地址与nacos服务做连接
ConfigService configService = NacosFactory.createConfigService(properties);
//读取配置
String content = configService.getConfig(dataId, group, 5000);
System.out.println(content);
configService.addListener(dataId, group, new Listener() {
	@Override
	public void receiveConfigInfo(String configInfo) {
		System.out.println("recieve1:" + configInfo);
	}
	@Override
	public Executor getExecutor() {
		return null;
	}
});

// 测试让主线程不退出,因为订阅配置是守护线程,主线程退出守护线程就会退出。 正式代码中无需下面代码
while (true) {
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}



ConfigService类中

因此我们想要拿到ConfigService,只需要拿到NacosConfigManager即可。

最终代码如下:

java 复制代码
private final NacosconfigManagernacosconfigManager;
public void initRouteConfigListener()throws NacosException {
	//1.注册监听器并首次拉取配置
	String configInfo = nacosConfigManager.getConfigService().getConfigAndSignListener(dataId,group,5000,new Listener(){
	@0verride
	public Executor getExecutor(){
	return null;
	}
	@0verride
	public void receiveConfigInfo(string configInfo){
	//TOD0 监听到配置变更,更新一次配置
	});
	//TOD0 2.首次启动时,更新一次配置
}

在hm-gateway服务中导入依赖

对应的bootstarp文件内容如下:

java 复制代码
spring:
  application:
    name: gateway
  profiles:
    active: dev
  cloud:
    nacos:
      server-addr: 192.168.244.134:8848
      config:
        file-extension: yaml
        shared-configs: #共享配置
          - dataId: shared-log.yaml # 共享日志配置

更新路由

监听到路由信息后,可以利用RouteDefinitionWriter来更新路由表

java 复制代码
/**
* @author Spencer Gibb
**/
public interface RouteDefinitionWriter{
	//更新路由到路由表,如果路由id重复,则会覆盖旧的路由		   	
	Mono<Void>save(Mono<RouteDefinition> route);
	// 根据路由id删除某个路由
	Mono<Void>delete(Mono<String>routeId);
}

为了方便解析从Nacos读取到的路由配置,推荐使用json格式的路由配置,模板如下:

json 复制代码
{
	"id": "item",
	"uri": "lb://item-service",
	"predicates":[{"name": "Path",
	"args":{
			"_genkey_0":"/items/**",
			"_gènkey_1":"/search/**"
	}],
	"filters": []
}

实现动态路由

xml 复制代码
spring:
  cloud:
    gateway:
      routes:
        - id: item-service
          uri: lb://item-service
          predicates:
            - Path=/items/**,/search/**

最终创建的DynamicRouteLoader类的内容如下:

java 复制代码
package com.hmall.gateway.routers;

import cn.hutool.json.JSONUtil;
import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

import javax.annotation.PostConstruct;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;

@Slf4j
@Component
@AllArgsConstructor
public class DynamicRouteLoader {
    private final NacosConfigManager nacosConfigManager;

    private final RouteDefinitionWriter routeDefinitionWriter;

    private final String dataId = "gateway-routes.json";
    private final String group = "DEFAULT_GROUP";

    private final Set<String> routeIds = new HashSet<>();


    @PostConstruct
    public void initRouteConfigListener() throws NacosException {
        //1.项目启动时,先拉取一次配置,并且添加配置监听器
        String configInfo = nacosConfigManager.getConfigService()
                .getConfigAndSignListener(dataId, group, 5000, new Listener() {
                    @Override
                    public Executor getExecutor() {
                        return null;
                    }

                    @Override
                    public void receiveConfigInfo(String configInfo) {
                        //2.监听到配置变更,需要去更新路由表
                        updateConfigInfo(configInfo);
                    }
                });
        //3.第一次读取到配置,也需要更新到路由表
        updateConfigInfo(configInfo);

    }
    public void updateConfigInfo(String configInfo){
        log.debug("监听到路由配置信息:{}",configInfo);
        //1.解析配置信息,转为RouteDefinition
        List<RouteDefinition> routeDefinitions = JSONUtil.toList(configInfo, RouteDefinition.class);
        //2.删除旧的路由表
        for (String routeId : routeIds) {
            routeDefinitionWriter.delete(Mono.just(routeId)).subscribe();
        }
        routeIds.clear();
        //3.更新路由表
        for (RouteDefinition routeDefinition : routeDefinitions) {
            //3.1更新路由表
            routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
            //3.2记录路由id,便于下一次更新的时候删除
            routeIds.add(routeDefinition.getId());

        }
    }
}
  • NacosConfigManager:获取 Nacos 配置并监听配置变化。
  • RouteDefinitionWriter:用于操作 Spring Cloud Gateway 的路由配置(如添加、删除、更新路由)。
  • dataId:Nacos 配置的标识,用于获取路由配置的 JSON 数据。
  • group:Nacos 配置的分组。
  • routeIds集合用于记录已经加载的路由 ID,方便在更新时删除旧路由。
  • 使用 @PostConstruct 注解来确保类初始化后执行该方法。
  • 通过 nacosConfigManager.getConfigService().getConfigAndSignListener 从Nacos 拉取初始配置,并设置一个配置监听器。
    1. getConfigAndSignListener:获取配置信息并注册监听器,监听配置的变化。
    2. 如果配置发生变化,receiveConfigInfo 方法将被调用来更新路由配置。
  • 配置监听器初始化后,updateConfigInfo(configInfo) 被调用,用来处理第一次拉取到的路由配置信息。
  • 解析配置信息:使用 JSONUtil.toList(configInfo, RouteDefinition.class) 将拉取到的 JSON 配置转换成 RouteDefinition 列表。
  • 删除旧路由:遍历之前保存的 routeIds,使用 routeDefinitionWriter.delete 删除已经存在的路由,并清空 routeIds 集合。
  • 添加新路由:遍历新的 routeDefinitions,使用 routeDefinitionWriter.save 保存新的路由配置,同时将新的路由 ID 加入 routeIds 以便下一次更新时删除旧路由。

在nacos的配置列表中添加配置gateway-routes.json

具体内容如下:

json 复制代码
[
    {
        "id": "item",
        "predicates": [{
            "name": "Path",
            "args": {"_genkey_0":"/items/**", "_genkey_1":"/search/**"}
        }],
        "filters": [],
        "uri": "lb://item-service"
    },
    {
        "id": "cart",
        "predicates": [{
            "name": "Path",
            "args": {"_genkey_0":"/carts/**"}
        }],
        "filters": [],
        "uri": "lb://cart-service"
    },
    {
        "id": "user",
        "predicates": [{
            "name": "Path",
            "args": {"_genkey_0":"/users/**", "_genkey_1":"/addresses/**"}
        }],
        "filters": [],
        "uri": "lb://user-service"
    },
    {
        "id": "trade",
        "predicates": [{
            "name": "Path",
            "args": {"_genkey_0":"/orders/**"}
        }],
        "filters": [],
        "uri": "lb://trade-service"
    },
    {
        "id": "pay",
        "predicates": [{
            "name": "Path",
            "args": {"_genkey_0":"/pay-orders/**"}
        }],
        "filters": [],
        "uri": "lb://pay-service"
    }
]

总结

本文通过示例代码展示了如何使用 Nacos 配置中心监听配置变更,并自动更新 Spring Cloud Gateway 的路由配置。通过这种方式,我们能够实现动态的路由更新,避免了传统的重启服务方式。利用 Nacos 作为配置中心,能够使得微服务架构中的 API 网关具有更高的灵活性和扩展性,提升系统的整体效率。

相关推荐
无关868811 分钟前
Spring Boot 项目标准化部署打包实战
java·spring boot·后端
jay神16 分钟前
基于微信小程序课外创新实践学分认定系统
java·spring boot·小程序·vue·毕业设计
Gauss松鼠会34 分钟前
GaussDB(DWS) GUC参数修改、查看
java·数据库·sql·数据库开发·gaussdb
AIFQuant36 分钟前
Java 对接全球股票实时报价:高可用架构与异常处理
java·开发语言·websocket·金融·架构·股票api
未若君雅裁37 分钟前
Spring Bean 作用域、线程安全与生命周期
java·安全·spring
奋斗的小乌龟1 小时前
langchain4j笔记-智能体系统01
java·笔记
wh_xia_jun1 小时前
用pom 的test 配置 与 jacoco
java·ide·intellij-idea
阿丰资源1 小时前
基于Spring Boot的酒店客房管理系统
java·spring boot·后端
无籽西瓜a1 小时前
【西瓜带你学Kafka | 第八期】 Kafka的主从同步、消息可靠性、流处理与顺序消费(文含图解)
java·分布式·后端·kafka·消息队列·mq
布吉岛的石头1 小时前
Java 程序员第 18 阶段:实战Agent工作流:Java搭建自动化业务智能体
java·python·自动化