微服务-网关、配置热更新、动态路由

祝小伙伴们每天开心 每天都能进步一点点

目录

[1 网关路由](#1 网关路由)

[1.1 认识网关](#1.1 认识网关)

什么是网关捏

网关实现方案

[1.2 快速入门](#1.2 快速入门)

(1)引入依赖

(2)配置路由

[1.3 路由过滤](#1.3 路由过滤)

路由规则语法

常见属性

predicates(路由断言)

[​2 网关登录校验](#2 网关登录校验)

[2.1 流程分析](#2.1 流程分析)

[​2.2 网关过滤器](#2.2 网关过滤器)

[2.2.1 思路分析](#2.2.1 思路分析)

[2.2.2 网关过滤器种类](#2.2.2 网关过滤器种类)

[2.2.3 GatewayFilter过滤器的使用](#2.2.3 GatewayFilter过滤器的使用)

[2.3 自定义过滤器](#2.3 自定义过滤器)

[2.4 登录校验](#2.4 登录校验)

[2.4.1 JWT工具](#2.4.1 JWT工具)

[2.4.2 定义登录校验过滤器](#2.4.2 定义登录校验过滤器)

[2.5 微服务获取用户](#2.5 微服务获取用户)

[2.5.1 保存用户到请求头](#2.5.1 保存用户到请求头)

[2.5.2 拦截器获取用户](#2.5.2 拦截器获取用户)

[2.6 OpenFeign传递用户](#2.6 OpenFeign传递用户)

[3 配置管理](#3 配置管理)

[3.1 配置共享](#3.1 配置共享)

[3.2 配置热更新](#3.2 配置热更新)

[3.2.1 添加配置到Nacos](#3.2.1 添加配置到Nacos)

[3.2.2 配置热更新](#3.2.2 配置热更新)

[3.3 动态路由](#3.3 动态路由)

实现步骤

总结

[1. 实时监听配置文件的变化](#1. 实时监听配置文件的变化)

[2. 实时更新路由表](#2. 实时更新路由表)

[3. 无需重启网关](#3. 无需重启网关)

流程简化总结


1 网关路由

1.1 认识网关

什么是网关捏

网关就是网络的关口。数据在网络间传输,从一个网络传输到另一网络时就需要经过网关来做数据的路由 和转发以及数据安全的校验。

更通俗的来讲,网关就像是园区传达室的大爷。

现在,微服务网关就起到同样的作用。前端请求不能直接访问微服务,而是要请求网关:

网关可以做安全控制,也就是登录身份校验,校验通过才放行

通过认证后,网关再根据请求判断应该访问哪个微服务,将请求转发过去

网关实现方案

SpringCloudGateway:基于Spring的WebFlux技术,完全支持响应式编程,吞吐能力更强

1.2 快速入门

如何利用网关实现请求路由

新建一个微服务模块**hm-gateway**

(1)引入依赖

复制代码
        </dependency>
        <!--网关-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <!--nacos discovery-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!--负载均衡-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>

(2)配置路由

复制代码
server:
  port: 8080
spring:
  application:
    name: gateway
  cloud:
    nacos:
      server-addr: 192.168.150.101:8848
    gateway:
      routes:
        - id: item # 路由规则id,自定义,唯一
          uri: lb://item-service # 路由的目标服务,lb代表负载均衡,会从注册中心拉取服务列表
          predicates: # 路由断言,判断当前请求是否符合当前规则,符合则路由到目标服务
            - Path=/items/**,/search/** # 这里是以请求路径作为判断规则
        - id: cart
          uri: lb://cart-service
          predicates:
            - Path=/carts/**
        - id: user
          uri: lb://user-service
          predicates:
            - Path=/users/**,/addresses/**
        - id: trade
          uri: lb://trade-service
          predicates:
            - Path=/orders/**
        - id: pay
          uri: lb://pay-service
          predicates:
            - Path=/pay-orders/**

启动项目 由前端发起的请求网关会根据路由规则将请求转发到相应的微服务模块

1.3 路由过滤

路由规则语法

可以定义很多路由规则

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

常见属性

id:路由的唯一标示

predicates:路由断言,其实就是匹配条件

filters:路由过滤条件

uri:路由目标地址,lb://代表负载均衡,从注册中心获取目标微服务的实例列表,并且负载均衡选择一个访问。

predicates(路由断言)

SpringCloudGateway中支持的断言类型有很多

2 网关登录校验

2.1 流程分析

而微服务拆分后,每个微服务都独立部署,不再共享数据。也就意味着每个微服务都需要做登录校验,这显然是不行嘟

既然网关是所有微服务的入口,一切请求都需要先经过网关。我们完全可以把登录校验的工作放到网关去做

只需要在网关和用户服务保存秘钥

只需要在网关开发登录校验功能

2.2 网关过滤器

2.2.1 思路分析

登录校验必须在请求转发到微服务之前做,否则就失去了意义。而网关的请求转发是Gateway内部代码实现的,要想在请求转发之前做登录校验,就必须了解Gateway内部工作的基本原理。

如图所示:

  1. 客户端请求进入网关后由HandlerMapping对请求做判断,找到与当前请求匹配的路由规则(Route ),然后将请求交给WebHandler去处理。

  2. WebHandler则会加载当前路由下需要执行的过滤器链(Filter chain ),然后按照顺序逐一执行过滤器(后面称为**Filter**)。

  3. 图中Filter被虚线分为左右两部分,是因为Filter内部的逻辑分为pre和post两部分,分别会在请求路由到微服务之前 和之后被执行。

  4. 只有所有Filter的pre逻辑都依次顺序执行通过后,请求才会被路由到微服务。

  5. 微服务返回结果后,再倒序执行Filter的post逻辑。

  6. 最终把响应结果返回。

最终请求转发是有一个名为NettyRoutingFilter的过滤器来执行的,而且这个过滤器是整个过滤器链中顺序最靠后的一个。如果我们能够定义一个过滤器,在其中实现登录校验逻辑,并且将过滤器执行顺序定义到 NettyRoutingFilter 之前,这就符合我们的需求了耶耶耶

2.2.2 网关过滤器种类

网关过滤器链中的过滤器有两种:

GatewayFilter :路由过滤器,作用范围比较灵活,可以是任意指定的路由Route.

GlobalFilter:全局过滤器,作用范围是所有路由,不可配置。

GatewayFilter和GlobalFilter这两种过滤器的方法签名完全一致:

复制代码
/**
 * 处理请求并将其传递给下一个过滤器
 * @param exchange 当前请求的上下文,其中包含request、response等各种数据
 * @param chain 过滤器链,基于它向下传递请求
 * @return 根据返回值标记当前请求是否被完成或拦截,chain.filter(exchange)就放行了。
 */
Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);

2.2.3 GatewayFilter过滤器的使用

例:有一个过滤器叫做AddRequestHeaderGatewayFilterFacotry,顾明思议,就是添加请求头的过滤器,可以给请求添加一个请求头并传递到下游微服务。

只需要在配置文件中这样配置

复制代码
spring:
  cloud:
    gateway:
      routes:
      - id: test_route
        uri: lb://test-service
        predicates:
          -Path=/test/**
        filters:
          - AddRequestHeader=key, value # 逗号之前是请求头的key,逗号之后是value

如果想要让过滤器作用于所有的路由,则可以这样配置

复制代码
spring:
  cloud:
    gateway:
      default-filters: # default-filters下的过滤器可以作用于所有路由
        - AddRequestHeader=key, value
      routes:
      - id: test_route
        uri: lb://test-service
        predicates:
          -Path=/test/**

2.3 自定义过滤器

以自定义GlobalFilter为例

复制代码
@Component
public class PrintAnyGlobalFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 编写过滤器逻辑
        System.out.println("未登录,无法访问");
        // 放行
        // return chain.filter(exchange);

        // 拦截
        ServerHttpResponse response = exchange.getResponse();
        response.setRawStatusCode(401);
        return response.setComplete();
    }

    @Override
    public int getOrder() {
        // 过滤器执行顺序,值越小,优先级越高
        return 0;
    }
}

2.4 登录校验

利用自定义的GlobalFilter来完成登录校验

2.4.1 JWT工具

具体作用如下:

  • AuthProperties:配置登录校验需要拦截的路径,因为不是所有的路径都需要登录才能访问

  • JwtProperties:定义与JWT工具有关的属性,比如秘钥文件位置

  • SecurityConfig:工具的自动装配

  • JwtTool:JWT工具,其中包含了校验和解析token的功能

  • hmall.jks:秘钥文件

其中AuthProperties和JwtProperties所需的属性要在application.yaml中配置:

复制代码
hm:
  jwt:
    location: classpath:hmall.jks # 秘钥地址
    alias: hmall # 秘钥别名
    password: hmall123 # 秘钥文件密码
    tokenTTL: 30m # 登录有效期
  auth:
    excludePaths: # 无需登录校验的路径
      - /search/**
      - /users/login
      - /items/**

2.4.2 定义登录校验过滤器

复制代码
package com.hmall.gateway.filter;

import com.hmall.common.exception.UnauthorizedException;
import com.hmall.common.utils.CollUtils;
import com.hmall.gateway.config.AuthProperties;
import com.hmall.gateway.util.JwtTool;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.List;

@Component
@RequiredArgsConstructor
@EnableConfigurationProperties(AuthProperties.class)
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    private final JwtTool jwtTool;

    private final AuthProperties authProperties;

    private final AntPathMatcher antPathMatcher = new AntPathMatcher();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1.获取Request
        ServerHttpRequest request = exchange.getRequest();
        // 2.判断是否不需要拦截
        if(isExclude(request.getPath().toString())){
            // 无需拦截,直接放行
            return chain.filter(exchange);
        }
        // 3.获取请求头中的token
        String token = null;
        List<String> headers = request.getHeaders().get("authorization");
        if (!CollUtils.isEmpty(headers)) {
            token = headers.get(0);
        }
        // 4.校验并解析token
        Long userId = null;
        try {
            userId = jwtTool.parseToken(token);
        } catch (UnauthorizedException e) {
            // 如果无效,拦截
            ServerHttpResponse response = exchange.getResponse();
            response.setRawStatusCode(401);
            return response.setComplete();
        }

        // TODO 5.如果有效,传递用户信息
        System.out.println("userId = " + userId);
        // 6.放行
        return chain.filter(exchange);
    }

    private boolean isExclude(String antPath) {
        for (String pathPattern : authProperties.getExcludePaths()) {
            if(antPathMatcher.match(pathPattern, antPath)){
                return true;
            }
        }
        return false;
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

2.5 微服务获取用户

网关已经可以完成登录校验并获取登录用户身份信息。但是当网关将请求转发到微服务时,微服务又该如何获取用户身份呢?

由于网关发送请求到微服务依然采用的是Http请求,因此我们可以将用户信息以请求头的方式传递到下游微服务。然后微服务可以从请求头中获取登录用户信息。考虑到微服务内部可能很多地方都需要用到登录用户信息,因此我们可以利用SpringMVC的拦截器来实现登录用户信息获取,并存入ThreadLocal,方便后续使用。

神的流程图

2.5.1 保存用户到请求头

修改登录校验拦截器的处理逻辑,保存用户信息到请求头中

2.5.2 拦截器获取用户

由于每个微服务都有获取登录用户的需求,因此拦截器我们直接写在hm-common中,并写好自动装配。这样微服务只需要引入hm-common就可以直接具备拦截器功能,无需重复编写。

我们在hm-common模块下定义一个拦截器:

复制代码
package com.hmall.common.interceptor;

import cn.hutool.core.util.StrUtil;
import com.hmall.common.utils.UserContext;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class UserInfoInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1.获取请求头中的用户信息
        String userInfo = request.getHeader("user-info");
        // 2.判断是否为空
        if (StrUtil.isNotBlank(userInfo)) {
            // 不为空,保存到ThreadLocal
                UserContext.setUser(Long.valueOf(userInfo));
        }
        // 3.放行
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 移除用户
        UserContext.removeUser();
    }
}

接着在hm-common模块下编写SpringMVC的配置类,配置登录拦截器:

复制代码
package com.hmall.common.config;

import com.hmall.common.interceptors.UserInfoInterceptor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
@ConditionalOnClass(DispatcherServlet.class)
public class MvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new UserInfoInterceptor());
    }
}

不过,需要注意的是,这个配置类默认是不会生效的,因为它所在的包是com.hmall.common.config,与其它微服务的扫描包不一致,无法被扫描到,因此无法生效。

基于SpringBoot的自动装配原理,我们要将其添加到resources目录下的META-INF/spring.factories文件中:

内容如下:

复制代码
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.hmall.common.config.MyBatisConfig,\
  com.hmall.common.config.MvcConfig

2.6 OpenFeign传递用户

由于微服务获取用户信息是通过拦截器在请求头中读取,因此要想实现微服务之间的用户信息传递,就必须在微服务发起调用时把用户信息存入请求头。

微服务之间调用是基于OpenFeign来实现的,并不是我们自己发送的请求。我们如何才能让每一个由OpenFeign发起的请求自动携带登录用户信息呢,这里要借助Feign中提供的一个拦截器接口:feign.RequestInterceptor

复制代码
public interface RequestInterceptor {

  /**
   * Called for every request. 
   * Add data using methods on the supplied {@link RequestTemplate}.
   */
  void apply(RequestTemplate template);
}

我们只需要实现这个接口,然后实现apply方法,利用RequestTemplate类来添加请求头,将用户信息保存到请求头中。这样以来,每次OpenFeign发起请求的时候都会调用该方法,传递用户信息。

由于FeignClient全部都是在hm-api模块,因此我们在hm-api模块的com.hmall.api.config.DefaultFeignConfig中编写这个拦截器:

复制代码
@Bean
public RequestInterceptor userInfoRequestInterceptor(){
    return new RequestInterceptor() {
        @Override
        public void apply(RequestTemplate template) {
            // 获取登录用户
            Long userId = UserContext.getUser();
            if(userId == null) {
                // 如果为空则直接跳过
                return;
            }
            // 如果不为空则放入请求头中,传递给下游微服务
            template.header("user-info", userId.toString());
        }
    };
}

现在微服务之间通过OpenFeign调用时也会传递登录用户信息了。

3 配置管理

Nacos不仅仅具备注册中心功能,也具备配置管理的功能

微服务共享的配置可以统一交给Nacos保存和管理,在Nacos控制台修改配置后,Nacos会将配置变更推送给相关的微服务,并且无需重启即可生效,实现配置热更新。

网关的路由同样是配置,因此同样可以基于这个功能实现动态路由功能,无需重启网关即可修改路由配置。

3.1 配置共享

我们可以把微服务共享的配置抽取到Nacos中统一管理,这样就不需要每个微服务都重复配置了。分为两步:

在Nacos中添加共享配置

微服务拉取配置

添加共享配置

在nacos控制台分别添加这些重复的配置。

拉取共享配置

读取Nacos配置是SpringCloud上下文(ApplicationContext)初始化时处理的,发生在项目的引导阶段。然后才会初始化SpringBoot上下文,去读取application.yaml。

也就是说引导阶段,application.yaml文件尚未读取,根本不知道nacos 地址,因此加载不了nacos中的配置

SpringCloud在初始化上下文的时候会先读取一个名为bootstrap.yaml(或者bootstrap.properties)的文件,如果我们将nacos地址配置到bootstrap.yaml中,那么在项目引导阶段就可以读取nacos中的配置了。

微服务整合Nacos配置管理的步骤如下:

(1)引入依赖

复制代码
  <!--nacos配置管理-->
  <dependency>
      <groupId>com.alibaba.cloud</groupId>
      <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
  </dependency>
  <!--读取bootstrap文件-->
  <dependency>
      <groupId>org.springframework.cloud</groupId>
      <artifactId>spring-cloud-starter-bootstrap</artifactId>
  </dependency>

(2)新建bootstrap.yaml文件

复制代码
spring:
  application:
    name: cart-service # 服务名称
  profiles:
    active: dev
  cloud:
    nacos:
      server-addr: 192.168.150.101 # nacos地址
      config:
        file-extension: yaml # 文件后缀名
        shared-configs: # 共享配置
          - dataId: shared-jdbc.yaml # 共享mybatis配置
          - dataId: shared-log.yaml # 共享日志配置
          - dataId: shared-swagger.yaml # 共享日志配置

(3)修改application.yaml

有些配置挪到了bootstrap.yaml,因此就不用重复配置了。

3.2 配置热更新

有很多的业务相关参数,将来可能会根据实际情况临时调整。例如购物车业务,购物车数量有一个上限,默认是10,这里购物车是写死的固定值,我们应该将其配置在配置文件中,方便后期修改。

但现在的问题是,即便写在配置文件中,修改了配置还是需要重新打包、重启服务才能生效。能不能不用重启,直接生效呢?这里就要用到Nacos配置热更新能力啦!

3.2.1 添加配置到Nacos

首先,我们在nacos中添加一个配置文件,将购物车的上限数量添加到配置中:

注意文件的dataId格式:

复制代码
[服务名]-[spring.active.profile].[后缀名]

文件名称由三部分组成:

  • 服务名 :我们是购物车服务,所以是cart-service

  • spring.active.profile :就是spring boot中的spring.active.profile,可以省略,则所有profile共享该配置

  • 后缀名:例如yaml

这里直接使用cart-service.yaml这个名称,则不管是dev还是local环境都可以共享该配置。

配置内容如下:

复制代码
hm:
  cart:
    maxAmount: 1 # 购物车商品数量上限

3.2.2 配置热更新

在微服务中读取配置,实现配置热更新。

(1)在cart-service中新建一个属性读取类:

复制代码
package com.hmall.cart.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Data
@Component
@ConfigurationProperties(prefix = "hm.cart")
public class CartProperties {
    private Integer maxAmount;
}

(2)在业务中使用该属性加载类:

3.3 动态路由

Spring Cloud Gateway 的路由配置在项目启动时加载并缓存到内存(路由表)中,启动后无法更新。如果路由规则变化(如新服务上线、新路径配置),必须重启服务,非常不灵活。

动态路由通过监听 Nacos 配置变更,实时更新网关的路由表,无需重启。

实现步骤

(1)在 pom.xml 文件中引入 Nacos 和 Spring Cloud 相关依赖。

  • spring-cloud-starter-alibaba-nacos-config 用于连接 Nacos 配置中心。
  • spring-cloud-starter-bootstrap 用于加载启动时的配置信息。

(2)配置Nacos服务地址

  • 创建 bootstrap.yml 文件,配置 Nacos 服务的地址及配置文件格式。

    spring:
    application:
    name: gateway
    cloud:
    nacos:
    server-addr: 192.168.150.101 # Nacos 服务地址
    config:
    file-extension: yaml # 配置文件格式
    shared-configs:
    - dataId: shared-log.yaml # 如果有共享配置,可指定 dataId

(3)初始化基础网关配置

  • 在 application.yml 文件中设置网关的基础配置,比如服务端口、JWT 配置、无需校验的路径等。

    server:
    port: 8080 # 网关端口
    hm:
    jwt:
    location: classpath:hmall.jks # JWT 秘钥文件地址
    alias: hmall
    password: hmall123
    tokenTTL: 30m
    auth:
    excludePaths: # 无需登录校验的路径
    - /search/**
    - /users/login
    - /items/**

(4)动态路由监听器

  • 创建动态路由加载器 DynamicRouteLoader,实现监听 Nacos 配置变化并实时更新路由表。
复制代码
package com.hmall.gateway.route;

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 com.hmall.common.utils.CollUtils;
import lombok.RequiredArgsConstructor;
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
@RequiredArgsConstructor
public class DynamicRouteLoader {

    private final RouteDefinitionWriter writer;
    private final NacosConfigManager nacosConfigManager;

    // 路由配置文件的id和分组
    private final String dataId = "gateway-routes.json";
    private final String group = "DEFAULT_GROUP";
    // 保存更新过的路由id
    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) {
                        updateConfigInfo(configInfo);
                    }
                });
        // 2.首次启动时,更新一次配置
        updateConfigInfo(configInfo);
    }

    private void updateConfigInfo(String configInfo) {
        log.debug("监听到路由配置变更,{}", configInfo);
        // 1.反序列化
        List<RouteDefinition> routeDefinitions = JSONUtil.toList(configInfo, RouteDefinition.class);
        // 2.更新前先清空旧路由
        // 2.1.清除旧路由
        for (String routeId : routeIds) {
            writer.delete(Mono.just(routeId)).subscribe();
        }
        routeIds.clear();
        // 2.2.判断是否有新的路由要更新
        if (CollUtils.isEmpty(routeDefinitions)) {
            // 无新路由配置,直接结束
            return;
        }
        // 3.更新路由
        routeDefinitions.forEach(routeDefinition -> {
            // 3.1.更新路由
            writer.save(Mono.just(routeDefinition)).subscribe();
            // 3.2.记录路由id,方便将来删除
            routeIds.add(routeDefinition.getId());
        });
    }
}
  1. 监听 Nacos 的路由配置文件 gateway-routes.json。
  2. 首次启动时拉取路由配置。
  3. 在 Nacos 配置变更时实时更新路由表。
  4. 删除旧路由并添加新的路由规则。

(5)在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"
    }
]

总结

通过动态路由的实现,我们利用 NacosConfigManager 监听 Nacos 配置文件的变化,在路由更新时,通过 RouteDefinitionWriter 清空旧路由并添加新路由,实现动态更新路由表的功能。这样,无需重启网关服务,只需修改 Nacos 配置文件,路由即可实时生效,提升了网关的灵活性和可维护性。

1. 实时监听配置文件的变化

通过 NacosConfigManager 提供的 getConfigAndSignListener 方法,我们动态监听了 Nacos 配置中心的路由配置文件。当配置文件发生变化时,Nacos 会通过回调机制通知网关服务。这个通知会触发路由表的更新逻辑。

2. 实时更新路由表

在监听到配置文件变化后:

  • 清空旧路由表 :通过 RouteDefinitionWriter.delete() 删除现有的路由配置。
  • 写入新路由表 :通过 RouteDefinitionWriter.save() 添加新的路由配置(从变更后的 Nacos 配置文件中读取)。
3. 无需重启网关

由于网关的路由更新是直接操作内存中的路由表,而不是重新加载服务或依赖硬编码的配置文件,因此只要监听到变化并更新路由表,新的路由规则就会立即生效,用户无需重启网关。

流程简化总结
  • Nacos监听 -> 检测到配置变更 -> 读取配置 -> 更新路由表(删除旧路由、添加新路由) -> 路由实时生效。
相关推荐
杨云龙UP12 分钟前
PostgreSQL 18.4 数据库整库迁移实战:pg_dump 备份与 pg_restore 恢复完整操作手册
运维·服务器·数据库·postgresql·pg_dump·pg_restore
土星云SaturnCloud2 小时前
土星云智慧服务区方案:AI视觉+边缘计算+视频存管一体化的全场景智能化落地解析
服务器·人工智能·ai·边缘计算
liqingdi4373 小时前
[SWPUCTF 2023 秋季新生赛]Easy SMC
服务器·前端·javascript
自养Agent4 小时前
MCP Server Boundary Audit Report
linux·服务器·人工智能·windows·大模型·llm
派小心.5 小时前
网站统计采样数据的误差核对
大数据·服务器·前端·数据库·人工智能·小程序·数据分析
A心有千千结5 小时前
通过 SSH 反向代理让服务器借用本机网络访问 GitHub 的最佳实践
linux·服务器·网络·git·ssh·github
fengkai45456 小时前
十三、Zabbix 监控
运维·服务器·zabbix·监控·网络设备
数字新视界6 小时前
机房动环监控助力智能管理
大数据·服务器·数据库·动环监控系统·动力与环境监控系统
算力小木6 小时前
#标题:5090 vs 4090D 到底差多少?我拿八卡实测了一周
服务器
数据中心的那点事儿6 小时前
艾特网能:GPU算力服务器冷却系统配套设计介绍
运维·服务器·gpu算力