Spring Cloud Gateway Nacos 实现动态路由

news2025/7/13 2:31:06

微服务都是互相独立的,假如我们的网关和其他服务都在线上已经运行了好久,这个时候增加了一个微服务,这个时候要通过网关访问的话需要通过修改配置文件来增加路由规则,并且需要重启项目,所以我们需要实现动态路由

方式一

1、创建路由配置接口

新建路由发布接口

/**
 * 路由配置服务
 * @author : jiagang
 * @date : Created in 2022/7/20 11:07
 */
public interface RouteService {
    /**
     * 更新路由配置
     *
     * @param routeDefinition
     */
    void update(RouteDefinition routeDefinition);

    /**
     * 添加路由配置
     *
     * @param routeDefinition
     */
    void add(RouteDefinition routeDefinition);
}

实现类如下

package com.mdx.gateway.service.impl;

import com.mdx.gateway.service.RouteService;
import lombok.extern.slf4j.Slf4j;
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.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;

/**
 * @author : jiagang
 * @date : Created in 2022/7/20 11:10
 */
@Service
@Slf4j
public class RouteServiceImpl implements RouteService, ApplicationEventPublisherAware {

    @Autowired
    private RouteDefinitionWriter routeDefinitionWriter;

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

    @Override
    public void update(RouteDefinition routeDefinition) {
        log.info("更新路由配置项:{}", routeDefinition);
        this.routeDefinitionWriter.delete(Mono.just(routeDefinition.getId()));
        routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
    }

    @Override
    public void add(RouteDefinition routeDefinition) {
        log.info("新增路由配置项:{}", routeDefinition);
        routeDefinitionWriter.save(Mono.just(routeDefinition)).subscribe();
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.publisher = applicationEventPublisher;
    }
}

其中:

  • RouteDefinitionWriter:提供了对路由的增加删除等操作
  • ApplicationEventPublisher: 是ApplicationContext的父接口之一,他的功能就是发布事件,也就是把某个事件告诉所有与这个事件相关的监听器

2、在nacos创建gateway-routes配置文件

将路由信息放到nacos的配置文件下
新建配置文件,并将order服务的路由添加到配置文件
在这里插入图片描述
配置路由如下:

[
    {
        "predicates":[
            {
                "args":{
                    "pattern":"/order/**"
                },
                "name":"Path"
            }
        ],
        "id":"mdx-shop-order",
        "filters":[
            {
                "args":{
                    "parts":1
                },
                "name":"StripPrefix"
            }
        ],
        "uri":"lb://mdx-shop-order",
        "order":1
    }
]

这个路由配置对应的就是gateway中的RouteDefinition类

3、在本地配置文件下配置路由的data-id和group和命名空间

gateway:
  routes:
    config:
      data-id: gateway-routes  #动态路由
      group: shop
      namespace: mdx

完整配置文件(删除或者注释掉之前配置在本地文件的路由)

server:
  port: 9010

spring:
  application:
    name: mdx-shop-gateway
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
        namespace: mdx
        group: mdx
    gateway:
      discovery:
        locator:
          enabled: true  #开启通过服务中心的自动根据 serviceId 创建路由的功能
  main:
    web-application-type: reactive

gateway:
  routes:
    config:
      data-id: gateway-routes  #动态路由
      group: shop
      namespace: mdx

4、创建路由相关配置类

创建配置类引入配置

/**
 * @author : jiagang
 * @date : Created in 2022/7/20 14:44
 */
@ConfigurationProperties(prefix = "gateway.routes.config")
@Component
@Data
public class GatewayRouteConfigProperties {

    private String dataId;
    private String group;
	private String namespace;
}

5、实例化nacos的ConfigService,交由springbean管理

ConfigService 这个类是nacos的分布式配置接口,主要是用来获取配置和添加监听器

由NacosFactory来创建ConfigService

/**
 * 将configService交由spring管理
 * @author : jiagang
 * @date : Created in 2022/7/20 15:27
 */
@Configuration
public class GatewayConfigServiceConfig {

    @Autowired
    private GatewayRouteConfigProperties configProperties;

    @Autowired
    private NacosConfigProperties nacosConfigProperties;

    @Bean
    public ConfigService configService() throws NacosException {
        Properties properties = new Properties();
        properties.setProperty(PropertyKeyConst.SERVER_ADDR, nacosConfigProperties.getServerAddr());
        properties.setProperty(PropertyKeyConst.NAMESPACE, configProperties.getNamespace());
        return NacosFactory.createConfigService(properties);
    }
}

6、动态路由主要实现

项目启动时会加载这个类
@PostConstruc 注解的作用,在spring bean的生命周期依赖注入完成后被调用的方法

package com.mdx.gateway.route;
import com.alibaba.cloud.nacos.NacosConfigProperties;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mdx.common.utils.StringUtils;
import com.mdx.gateway.service.RouteService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;

/**
 * @author : jiagang
 * @date : Created in 2022/7/20 15:04
 */
@Component
@Slf4j
@RefreshScope
public class GatewayRouteInitConfig {

    @Autowired
    private GatewayRouteConfigProperties configProperties;

    @Autowired
    private NacosConfigProperties nacosConfigProperties;

    @Autowired
    private RouteService routeService;
    /**
     * nacos 配置服务
     */
    @Autowired
    private ConfigService configService;
    /**
     * JSON 转换对象
     */
    private final ObjectMapper objectMapper = new ObjectMapper();

    @PostConstruct
    public void init() {
        log.info("开始网关动态路由初始化...");
        try {
            // getConfigAndSignListener()方法 发起长轮询和对dataId数据变更注册监听的操作
            // getConfig 只是发送普通的HTTP请求
            // String getConfig(String dataId, String group, long timeoutMs) throws NacosException;
            String initConfigInfo = configService.getConfigAndSignListener(configProperties.getDataId(), configProperties.getGroup(), nacosConfigProperties.getTimeout(), new Listener() {
                @Override
                public Executor getExecutor() {
                    return null;
                }

                @Override
                public void receiveConfigInfo(String configInfo) {
                    if (StringUtils.isNotEmpty(configInfo)) {
                        log.info("接收到网关路由更新配置:\r\n{}", configInfo);
                        List<RouteDefinition> routeDefinitions = null;
                        try {
                            routeDefinitions = objectMapper.readValue(configInfo, new TypeReference<List<RouteDefinition>>() {
                            });
                        } catch (JsonProcessingException e) {
                            log.error("解析路由配置出错," + e.getMessage(), e);
                        }
                        for (RouteDefinition definition : Objects.requireNonNull(routeDefinitions)) {
                            routeService.update(definition);
                        }
                    } else {
                        log.warn("当前网关无动态路由相关配置");
                    }
                }
            });
            log.info("获取网关当前动态路由配置:\r\n{}", initConfigInfo);
            if (StringUtils.isNotEmpty(initConfigInfo)) {
                List<RouteDefinition> routeDefinitions = objectMapper.readValue(initConfigInfo, new TypeReference<List<RouteDefinition>>() {
                });
                for (RouteDefinition definition : routeDefinitions) {
                    routeService.add(definition);
                }
            } else {
                log.warn("当前网关无动态路由相关配置");
            }
            log.info("结束网关动态路由初始化...");
        } catch (Exception e) {
            log.error("初始化网关路由时发生错误", e);
        }
    }

}

如果项目启动时,在发布路由的时候卡在 this.publisher.publishEvent(new RefreshRoutesEvent(this)); 这个地方走不下去,请在GatewayRouteInitConfig这个类加@RefreshScope注解

7、测试动态路由

前面我们已经把本地的yml中的路由注释掉了,现在我们来通过gateway服务来掉一个order服务的接口
接口地址:http://localhost:9010/mdx-shop-order/order/lb
其中9010是网关端口
可以看到路由成功
在这里插入图片描述
然后我们再在nacos配置中心加一个user服务的路由

[
    {
        "predicates":[
            {
                "args":{
                    "pattern":"/mdx-shop-order/**"
                },
                "name":"Path"
            }
        ],
        "id":"mdx-shop-order",
        "filters":[
            {
                "args":{
                    "parts":1
                },
                "name":"StripPrefix"
            }
        ],
        "uri":"lb://mdx-shop-order",
        "order":1
    },
    {
        "predicates":[
            {
                "args":{
                    "pattern":"/mdx-shop-user/**"
                },
                "name":"Path"
            }
        ],
        "id":"mdx-shop-user",
        "filters":[
            {
                "args":{
                    "parts":1
                },
                "name":"StripPrefix"
            }
        ],
        "uri":"lb://mdx-shop-user",
        "order":2
    }
]

然后点发布
可以看到gateway的监听器已经监听到配置的改动
在这里插入图片描述
不重新启动gateway的情况下再来通过网关访问下user服务

接口地址:http://localhost:9010/mdx-shop-user/user/getOrderNo?userId=mdx123456
其中9010是网关端口
可以看到成功路由
在这里插入图片描述
到这里gateway的使用和nacos动态路由就结束了~

方式二

方式二与方式一实现方式类似,就是nacos 监听方式不一样,简单记录下

import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.InMemoryRouteDefinitionRepository;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;

import java.lang.reflect.Field;
import java.util.Map;

@Service
@Slf4j
public class DynamicRouteServiceImpl implements ApplicationEventPublisherAware {

    @Autowired
    private RouteDefinitionWriter routeDefinitionWriter;
    private ApplicationEventPublisher publisher;

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        this.publisher = applicationEventPublisher;
    }

    //增加路由
    public String add(RouteDefinition definition) {
        routeDefinitionWriter.save(Mono.just(definition)).subscribe();
        this.publisher.publishEvent(new RefreshRoutesEvent(this));
        return "success";
    }

    //更新路由
    public String update(RouteDefinition definition) {
        try {
            delete(definition.getId());
        } catch (Exception e) {
            log.error("删除路由异常", e);
            return "update fail,not find route  routeId: " + definition.getId();
        }
        try {
            routeDefinitionWriter.save(Mono.just(definition)).subscribe();
            this.publisher.publishEvent(new RefreshRoutesEvent(this));
            return "success";
        } catch (Exception e) {
            log.error("更新路由异常", e);
            return "update route  fail";
        }
    }

    //删除路由
    public String delete(String id) {
        StringBuilder sb = new StringBuilder();
        this.routeDefinitionWriter.delete(Mono.just(id))
                .doOnError(e -> {
                    String s = String.format("删除路由%s失败", id);
                    log.error(s);
                    sb.append(s);
                }).doOnSuccess(e -> {
                    String s = String.format("删除路由%s成功", id);
                    log.error(s);
                    sb.append(s);
        }).subscribe();
        return sb.toString();
    }

    public Map<String, RouteDefinition> list() {
        Map<String, RouteDefinition> routes = Maps.newHashMap();
        InMemoryRouteDefinitionRepository in = (InMemoryRouteDefinitionRepository) routeDefinitionWriter;

        try {
            Field f = InMemoryRouteDefinitionRepository.class.getDeclaredField("routes");
            f.setAccessible(true);
            routes = (Map<String, RouteDefinition>) f.get(in);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return routes;
    }
}
import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.api.config.listener.AbstractListener;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.stereotype.Component;

import java.net.URI;
import java.util.List;
import java.util.Map;

@Component
@Slf4j
public class NacosDynamicSupport implements InitializingBean {

    @Value("#{'${spring.application.name}'.concat('.route')}")
    private String route;
    
    @Autowired
    private DynamicRouteServiceImpl dynamicRouteService;

    @Autowired
    private NacosConfigManager nacosConfigManager;

    @Override
    public void afterPropertiesSet() throws Exception {
        nacosConfigManager.getConfigService().addListener(route, Constants.DEFAULT_GROUP, new AbstractListener() {
            @Override
            public void receiveConfigInfo(String config) {
                log.error("mgateway:{}", config);
                if(StringUtils.isBlank(config)) {
                    return;
                }
                List<RouteDefinition> routeDefinitions =  JSON.parseArray(config, RouteDefinition.class);

                Map<String, RouteDefinition> oldRouteMap = dynamicRouteService.list();
                Map<String, RouteDefinition> newRouteMap = Maps.newHashMap();

                for(RouteDefinition definition: routeDefinitions) {
                    String id = definition.getId();
                    newRouteMap.put(id, definition);

                    if(!oldRouteMap.containsKey(id)) {
                        dynamicRouteService.add(definition);
                    } else if(!definition.equals(oldRouteMap.get(id))) {
                        dynamicRouteService.update(definition);
                    }
                }

                oldRouteMap.forEach((k,v) -> {
                    if(!newRouteMap.containsKey(k)) {
                       log.error(dynamicRouteService.delete(k));
                    }
                });
            }
        });

        String routeJson = nacosConfigManager.getConfigService().getConfig(route, Constants.DEFAULT_GROUP, 5000);
        if(StringUtils.isNotBlank(routeJson)) {
            List<RouteDefinition> routeDefinitions = JSON.parseArray(routeJson, RouteDefinition.class);
            for (RouteDefinition definition : routeDefinitions) {
                this.dynamicRouteService.add(definition);
            }
        }
    }
}

参考:https://blog.csdn.net/qq_38374397/article/details/125874882

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/362836.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

34. 应用监控【监控端点配置】

当一个 Spring Boot 项目运行时&#xff0c;开发者需要对 Spring Boot 项目进行实时监控来获取项目的运行情况&#xff0c;在项目出错时能够实现自动报警等。 Spring Boot 提供了actuator 来帮助开发者获取应用程序的实时运行数据。开发者可以选择使用 HTTP 端点或JMX来管理和监…

九龙证券|银行资本管理办法迎“大修” 信用风险权重法调整优化

1年期AAA中债商业银行同业存单到期收益率 日前迎来“大修”的商业银行本钱办理方法&#xff0c;在债券商场激起“涟漪”——债券商场一改此前平静态势&#xff0c;连续两日跌落。 2月21日&#xff0c;10年期国债收益率较上星期五上行2.9个基点&#xff0c;至2.919%&#xff1b…

记录charles手机端配置https的成功过程

1.百度 https://www.likecs.com/show-204025787.html https://blog.csdn.net/enthan809882/article/details/117572094?spm1001.2101.3001.6650.6&utm_mediumdistribute.pc_relevant.none-task-blog-2defaultBlogCommendFromBaiduRate-6-117572094-blog-122959902.pc_rele…

余承东:问界就是华为生态汽车,不涉及20万以下车型

今天界面新闻发布了一篇余承东的专访文章&#xff0c;谈到了外界对华为造车的质疑&#xff0c;以及回应了与赛力斯的合作&#xff0c;后续HI模式与智选车模式如何推进的话题。摘录重点如下&#xff1a;1.首先&#xff0c;继续「不造车」“华为没有必要自己下场造车。”在他看来…

Spring MVC 源码 - HandlerMapping 组件(四)之 AbstractUrlHandlerMapping

HandlerMapping 组件HandlerMapping 组件&#xff0c;请求的处理器匹配器&#xff0c;负责为请求找到合适的 HandlerExecutionChain 处理器执行链&#xff0c;包含处理器&#xff08;handler&#xff09;和拦截器们&#xff08;interceptors&#xff09;handler 处理器是 Objec…

【GO】k8s 管理系统项目[前端部分–Header]

【GO】k8s 管理系统项目[前端部分–Header] 1. 实现功能 面包屑展开关闭按钮用户信息(退出按钮) 2. 代码部分 src/layout/Layout.vue 在之前预留header位置补上 <!-- header --><el-header class"header"><el-row :gutter"20"><e…

python基于vue健身房课程预约平台

可定制框架:ssm/Springboot/vue/python/PHP/小程序/安卓均可开发 目录 1 绪论 1 1.1课题背景 1 1.2课题研究现状 1 1.3初步设计方法与实施方案 2 1.4本文研究内容 2 2 系统开发3 2.2MyEclipse环境配置 4 2.3 B/S结构简介 4 2.4MySQL数据库 5 2.5 django框架 5 3 系统分析 6 3.1…

Sqoop利用Sql将mysql表_导入数据到Hive---大数据之Apache Sqoop工作笔记004

然后来看一下把数据导入到hive中去 可以去官网去看看文档,有什么参数 这里用的是hive-import对吧,然后 这里hive-overwrite是覆盖 这个hive-table staff_hive 是创建 一个hive的表是 staff_hive 然后我们先去启动一下hive 启动以后,然后我们去查一下,shot tables 可以看到 里…

Ubuntu 22.04.2 LTS安装Apollo8.0

本人硬件环境&#xff1a; CPU&#xff1a;Intel Core i7 6700 显卡&#xff08;GPU&#xff09;&#xff1a;NVIDIA GTX 3080 10G 内存&#xff1a;SAMSUNG DDR4 32GB 硬盘&#xff1a;双SSD系统盘 2T,双系统&#xff08;windows,ubuntu&#xff09; 一、安装Ubuntu 22.04…

【极海APM32替代笔记】HAL库中的SPI传输(可利用中断或DMA进行连续传输)

【极海APM32替代笔记】HAL库中的SPI传输&#xff08;可利用中断或DMA进行连续传输&#xff09; SPI 是英语Serial Peripheral interface的缩写&#xff0c;顾名思义就是串行外围设备接口。是Motorola(摩托罗拉)首先在其MC68HCXX系列处理器上定义的。 SPI&#xff0c;是一种高…

Ssh协议绑定Git服务器

1、起因在公司开发一般使用gitlab部署公司git代码管理&#xff0c;个人的代码存储在公司gitlab上就显得不合时宜了&#xff0c;所以找了gitee上来存储代码。2、经过搜索了下github.com,gitee.com 其他当然有阿里云的云效等&#xff0c;个人使用优先国内git服务器&#xff0c;理…

【Netty系列・扫盲篇】Netty从入门到学废

文章目录1. 概述1.1 Netty 的地位1.2 Netty 的优势2. Hello World2.1 目标2.2 服务器端2.3 客户端2.4 流程梳理&#x1f4a1; 提示3. 组件3.1 EventLoop&#x1f4a1; 优雅关闭演示 NioEventLoop 处理 io 事件&#x1f4a1; handler 执行中如何换人&#xff1f;演示 NioEventLo…

C# 业务单据号生成器(定义规则、获取编号、流水号)

系列文章 C#底层库–数据库访问帮助类&#xff08;MySQL版&#xff09; 本文链接&#xff1a;https://blog.csdn.net/youcheng_ge/article/details/126886379 C#底层库–JSON帮助类_详细&#xff08;序列化、反序列化、list、datatable&#xff09; 本文链接&#xff1a;htt…

GEE学习笔记 六十:GEE中生成GIF动画

生成GIF动画这个是GEE新增加的功能之一&#xff0c;这一篇文章我会简单介绍一下如何使用GEE来制作GIF动画。 相关API如下&#xff1a; 参数含义&#xff1a; params&#xff1a;设置GIF动画显示参数&#xff0c;详细的参数可以参考ee.data.getMapId() callback&#xff1a;回调…

基于RK3399地面测试台多参数记录仪测试平台软件设计

随着高科技技术在现代化战争中日益重要作用&#xff0c;飞行装备的研制亦从单元体制发展 到多元体制。航空装置系统在设计过程中&#xff0c;需要大量测试工作&#xff0c;尤其是需要把系统研制 和飞行试验中各部分工作状态参数实时记录&#xff0c;用以分析、改进设计。记录仪…

uniApp使用uni.chooseAddress()获取微信收货地址

获取微信收货地址 使用uniapp或者原生微信小程序获取微信的收货地址 1、需要在开发平台申请权限 在【开发】-【开发管理】-【接口设置】-【获取用户收货地址】–申请该权限&#xff0c;审核通过后方可使用。 2、在源码上添加配置 2.1 在uniapp上开发配置 打开manifest.js…

ECharts在vue中使用 与 图表自适应

目录 使用思路&#xff1a; Echarts在vue中使用 引入 ECharts 绘制图表实例&#xff08;复杂&#xff09; 实例效果&#xff1a; 官方入门实例&#xff08;简单&#xff09; 官方入门实例效果 ​编辑 图表自适应 ECharts 的功能十分强大,可以生成多种形式的图表,配置…

xilinx FPGA在线调试方法总结(vivado+ila+vio)

本文主要介绍xilinx FPGA开发过程中常用的调试方法&#xff0c;包括ILA、VIO和TCL命令等等&#xff0c;详细介绍了如何使用。一、FPGA调试基本原则根据实际的输出结果表现&#xff0c;来推测可能的原因&#xff0c;再在模块中加ILA信号&#xff0c;设置抓信号条件&#xff0c;逐…

Java 比较器Comparable与Comparator的List集合排序使用

一、Collections类 Collections则是集合类的一个工具类/帮助类&#xff0c;其中提供了一系列静态方法&#xff0c;用于对集合中元素进行排序、搜索以及线程安全等各种操作。 用于集合排序的sort方法&#xff0c;此方法主要是通过Comparable或Comparator来实现排序。 (1) 根据其…

如何30天零基础入门网络安全?自学网络安全有哪些缺点?

网络安全的前景如何&#xff0c;盾叔已经说过很多遍了&#xff0c;今天专题是替一些想入门网络安全&#xff0c;但还迷茫不知所措的同学解一解惑。想30天零基础入门网络安全&#xff0c;这些你一定要搞清楚。 一、学习网络安全容易造成的误区 1、把编程当作目的&#xff0c;忽…