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

打印 上一主题 下一主题

主题 994|帖子 994|积分 2982

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
祝小同伴们每天开心 每天都能进步一点点
目录
1 网关路由
1.1 认识网关
什么是网关捏
网关实现方案
1.2  快速入门
(1)引入依靠
(2)配置路由
1.3 路由过滤
路由规则语法
常见属性
predicates(路由断言)
​2 网关登录校验
2.1 流程分析
​2.2 网关过滤器
2.2.1 思路分析
2.2.2 网关过滤器种类
2.2.3 GatewayFilter过滤器的使用
2.3 自界说过滤器
2.4 登录校验
2.4.1 JWT工具
2.4.2 界说登录校验过滤器
2.5 微服务获取用户
 2.5.1 保存用户到请求头
 2.5.2 拦截器获取用户
2.6 OpenFeign传递用户
3 配置管理
3.1 配置共享
3.2 配置热更新
3.2.1 添加配置到Nacos
3.2.2 配置热更新
 3.3 动态路由
 实现步骤
总结 
1. 实时监听配置文件的变化
2. 实时更新路由表
3. 无需重启网关
流程简化总结


1 网关路由

1.1 认识网关

什么是网关捏

网关就是网络的关口。数据在网络间传输,从一个网络传输到另一网络时就必要经过网关来做数据的路由和转发以及数据安全的校验
更普通的来讲,网关就像是园区转达室的大爷。

 
如今,微服务网关就起到同样的作用。前端请求不能直接访问微服务,而是要请求网关:
        网关可以做安全控制,也就是登录身份校验,校验通过才放行
        通过认证后,网关再根据请求判定应该访问哪个微服务,将请求转发过去
 

网关实现方案

        SpringCloudGateway:基于Spring的WebFlux技术,完全支持响应式编程,吞吐本领更强
1.2  快速入门

如何利用网关实现请求路由
新建一个微服务模块hm-gateway
(1)引入依靠

  1.         </dependency>
  2.         <!--网关-->
  3.         <dependency>
  4.             <groupId>org.springframework.cloud</groupId>
  5.             <artifactId>spring-cloud-starter-gateway</artifactId>
  6.         </dependency>
  7.         <!--nacos discovery-->
  8.         <dependency>
  9.             <groupId>com.alibaba.cloud</groupId>
  10.             <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  11.         </dependency>
  12.         <!--负载均衡-->
  13.         <dependency>
  14.             <groupId>org.springframework.cloud</groupId>
  15.             <artifactId>spring-cloud-starter-loadbalancer</artifactId>
  16.         </dependency>
复制代码
(2)配置路由

  1. server:
  2.   port: 8080
  3. spring:
  4.   application:
  5.     name: gateway
  6.   cloud:
  7.     nacos:
  8.       server-addr: 192.168.150.101:8848
  9.     gateway:
  10.       routes:
  11.         - id: item # 路由规则id,自定义,唯一
  12.           uri: lb://item-service # 路由的目标服务,lb代表负载均衡,会从注册中心拉取服务列表
  13.           predicates: # 路由断言,判断当前请求是否符合当前规则,符合则路由到目标服务
  14.             - Path=/items/**,/search/** # 这里是以请求路径作为判断规则
  15.         - id: cart
  16.           uri: lb://cart-service
  17.           predicates:
  18.             - Path=/carts/**
  19.         - id: user
  20.           uri: lb://user-service
  21.           predicates:
  22.             - Path=/users/**,/addresses/**
  23.         - id: trade
  24.           uri: lb://trade-service
  25.           predicates:
  26.             - Path=/orders/**
  27.         - id: pay
  28.           uri: lb://pay-service
  29.           predicates:
  30.             - Path=/pay-orders/**
复制代码
启动项目 由前端发起的请求网关会根据路由规则将请求转发到相应的微服务模块
1.3 路由过滤

路由规则语法

可以界说许多路由规则
  1. spring:
  2.   cloud:
  3.     gateway:
  4.       routes:
  5.         - id: item
  6.           uri: lb://item-service
  7.           predicates:
  8.             - Path=/items/**,/search/**
复制代码
常见属性

   id:路由的唯一标示
   predicates:路由断言,其实就是匹配条件
   filters:路由过滤条件
   uri:路由目标地址,lb://代表负载均衡,从注册中心获取目标微服务的实例列表,并且负载均衡选择一个访问。
predicates(路由断言)

SpringCloudGateway中支持的断言范例有许多
2 网关登录校验


2.1 流程分析

而微服务拆分后,每个微服务都独立部署,不再共享数据。也就意味着每个微服务都必要做登录校验,这显然是不可嘟
既然网关是全部微服务的入口,一切请求都必要先经过网关。我们完全可以把登录校验的工作放到网关去做
        只必要在网关和用户服务保存秘钥
        只必要在网关开辟登录校验功能
2.2 网关过滤器


2.2.1 思路分析

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

如图所示:

  • 客户端请求进入网关后由HandlerMapping对请求做判定,找到与当前请求匹配的路由规则(Route),然后将请求交给WebHandler去处理。
  • WebHandler则会加载当前路由下必要实行的过滤器链(Filter chain),然后按照顺序逐一实行过滤器(后面称为Filter)。
  • 图中Filter被虚线分为左右两部分,是因为Filter内部的逻辑分为pre和post两部分,分别会在请求路由到微服务之前之后被实行。
  • 只有全部Filter的pre逻辑都依次顺序实行通过后,请求才会被路由到微服务。
  • 微服务返回效果后,再倒序实行Filter的post逻辑。
  • 终极把响应效果返回。
终极请求转发是有一个名为NettyRoutingFilter的过滤器来实行的,而且这个过滤器是整个过滤器链中顺序最靠后的一个。如果我们能够界说一个过滤器,在其中实现登录校验逻辑,并且将过滤器实行顺序界说到NettyRoutingFilter之前,这就符合我们的需求了耶耶耶
 2.2.2 网关过滤器种类

网关过滤器链中的过滤器有两种:
    GatewayFilter:路由过滤器,作用范围比较机动,可以是恣意指定的路由Route.
    GlobalFilter:全局过滤器,作用范围是全部路由,不可配置。
GatewayFilter和GlobalFilter这两种过滤器的方法签名完全一致:
  1. /**
  2. * 处理请求并将其传递给下一个过滤器
  3. * @param exchange 当前请求的上下文,其中包含request、response等各种数据
  4. * @param chain 过滤器链,基于它向下传递请求
  5. * @return 根据返回值标记当前请求是否被完成或拦截,chain.filter(exchange)就放行了。
  6. */
  7. Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
复制代码
 2.2.3 GatewayFilter过滤器的使用

例:有一个过滤器叫做AddRequestHeaderGatewayFilterFacotry,顾明思议,就是添加请求头的过滤器,可以给请求添加一个请求头并传递到下游微服务。
只必要在配置文件中如许配置
  1. spring:
  2.   cloud:
  3.     gateway:
  4.       routes:
  5.       - id: test_route
  6.         uri: lb://test-service
  7.         predicates:
  8.           -Path=/test/**
  9.         filters:
  10.           - AddRequestHeader=key, value # 逗号之前是请求头的key,逗号之后是value
复制代码
如果想要让过滤器作用于全部的路由,则可以如许配置
  1. spring:
  2.   cloud:
  3.     gateway:
  4.       default-filters: # default-filters下的过滤器可以作用于所有路由
  5.         - AddRequestHeader=key, value
  6.       routes:
  7.       - id: test_route
  8.         uri: lb://test-service
  9.         predicates:
  10.           -Path=/test/**
复制代码
2.3 自界说过滤器

以自界说GlobalFilter为例
  1. @Component
  2. public class PrintAnyGlobalFilter implements GlobalFilter, Ordered {
  3.     @Override
  4.     public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
  5.         // 编写过滤器逻辑
  6.         System.out.println("未登录,无法访问");
  7.         // 放行
  8.         // return chain.filter(exchange);
  9.         // 拦截
  10.         ServerHttpResponse response = exchange.getResponse();
  11.         response.setRawStatusCode(401);
  12.         return response.setComplete();
  13.     }
  14.     @Override
  15.     public int getOrder() {
  16.         // 过滤器执行顺序,值越小,优先级越高
  17.         return 0;
  18.     }
  19. }
复制代码
2.4 登录校验

利用自界说的GlobalFilter来完成登录校验
2.4.1 JWT工具


具体作用如下:


  • AuthProperties:配置登录校验必要拦截的路径,因为不是全部的路径都必要登录才能访问
  • JwtProperties:界说与JWT工具有关的属性,比如秘钥文件位置
  • SecurityConfig:工具的自动装配
  • JwtTool:JWT工具,其中包罗了校验和解析token的功能
  • hmall.jks:秘钥文件
 其中AuthProperties和JwtProperties所需的属性要在application.yaml中配置:
  1. hm:
  2.   jwt:
  3.     location: classpath:hmall.jks # 秘钥地址
  4.     alias: hmall # 秘钥别名
  5.     password: hmall123 # 秘钥文件密码
  6.     tokenTTL: 30m # 登录有效期
  7.   auth:
  8.     excludePaths: # 无需登录校验的路径
  9.       - /search/**
  10.       - /users/login
  11.       - /items/**
复制代码
2.4.2 界说登录校验过滤器

  1. package com.hmall.gateway.filter;
  2. import com.hmall.common.exception.UnauthorizedException;
  3. import com.hmall.common.utils.CollUtils;
  4. import com.hmall.gateway.config.AuthProperties;
  5. import com.hmall.gateway.util.JwtTool;
  6. import lombok.RequiredArgsConstructor;
  7. import org.springframework.boot.context.properties.EnableConfigurationProperties;
  8. import org.springframework.cloud.gateway.filter.GatewayFilterChain;
  9. import org.springframework.cloud.gateway.filter.GlobalFilter;
  10. import org.springframework.core.Ordered;
  11. import org.springframework.http.server.reactive.ServerHttpRequest;
  12. import org.springframework.http.server.reactive.ServerHttpResponse;
  13. import org.springframework.stereotype.Component;
  14. import org.springframework.util.AntPathMatcher;
  15. import org.springframework.web.server.ServerWebExchange;
  16. import reactor.core.publisher.Mono;
  17. import java.util.List;
  18. @Component
  19. @RequiredArgsConstructor
  20. @EnableConfigurationProperties(AuthProperties.class)
  21. public class AuthGlobalFilter implements GlobalFilter, Ordered {
  22.     private final JwtTool jwtTool;
  23.     private final AuthProperties authProperties;
  24.     private final AntPathMatcher antPathMatcher = new AntPathMatcher();
  25.     @Override
  26.     public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
  27.         // 1.获取Request
  28.         ServerHttpRequest request = exchange.getRequest();
  29.         // 2.判断是否不需要拦截
  30.         if(isExclude(request.getPath().toString())){
  31.             // 无需拦截,直接放行
  32.             return chain.filter(exchange);
  33.         }
  34.         // 3.获取请求头中的token
  35.         String token = null;
  36.         List<String> headers = request.getHeaders().get("authorization");
  37.         if (!CollUtils.isEmpty(headers)) {
  38.             token = headers.get(0);
  39.         }
  40.         // 4.校验并解析token
  41.         Long userId = null;
  42.         try {
  43.             userId = jwtTool.parseToken(token);
  44.         } catch (UnauthorizedException e) {
  45.             // 如果无效,拦截
  46.             ServerHttpResponse response = exchange.getResponse();
  47.             response.setRawStatusCode(401);
  48.             return response.setComplete();
  49.         }
  50.         // TODO 5.如果有效,传递用户信息
  51.         System.out.println("userId = " + userId);
  52.         // 6.放行
  53.         return chain.filter(exchange);
  54.     }
  55.     private boolean isExclude(String antPath) {
  56.         for (String pathPattern : authProperties.getExcludePaths()) {
  57.             if(antPathMatcher.match(pathPattern, antPath)){
  58.                 return true;
  59.             }
  60.         }
  61.         return false;
  62.     }
  63.     @Override
  64.     public int getOrder() {
  65.         return 0;
  66.     }
  67. }
复制代码
2.5 微服务获取用户

网关已经可以完成登录校验并获取登任命户身份信息。但是当网关将请求转发到微服务时,微服务又该如何获取用户身份呢?
由于网关发送请求到微服务依然接纳的是Http请求,因此我们可以将用户信息以请求头的方式传递到下游微服务。然后微服务可以从请求头中获取登任命户信息。考虑到微服务内部可能许多地方都必要用到登任命户信息,因此我们可以利用SpringMVC的拦截器来实现登任命户信息获取,并存入ThreadLocal,方便后续使用。
神的流程图

 2.5.1 保存用户到请求头

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

 2.5.2 拦截器获取用户

由于每个微服务都有获取登任命户的需求,因此拦截器我们直接写在hm-common中,并写好自动装配。如许微服务只必要引入hm-common就可以直接具备拦截器功能,无需重复编写。
我们在hm-common模块下界说一个拦截器:
  1. package com.hmall.common.interceptor;
  2. import cn.hutool.core.util.StrUtil;
  3. import com.hmall.common.utils.UserContext;
  4. import org.springframework.web.servlet.HandlerInterceptor;
  5. import javax.servlet.http.HttpServletRequest;
  6. import javax.servlet.http.HttpServletResponse;
  7. public class UserInfoInterceptor implements HandlerInterceptor {
  8.     @Override
  9.     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  10.         // 1.获取请求头中的用户信息
  11.         String userInfo = request.getHeader("user-info");
  12.         // 2.判断是否为空
  13.         if (StrUtil.isNotBlank(userInfo)) {
  14.             // 不为空,保存到ThreadLocal
  15.                 UserContext.setUser(Long.valueOf(userInfo));
  16.         }
  17.         // 3.放行
  18.         return true;
  19.     }
  20.     @Override
  21.     public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
  22.         // 移除用户
  23.         UserContext.removeUser();
  24.     }
  25. }
复制代码
接着在hm-common模块下编写SpringMVC的配置类,配置登录拦截器:
  1. package com.hmall.common.config;
  2. import com.hmall.common.interceptors.UserInfoInterceptor;
  3. import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
  4. import org.springframework.context.annotation.Configuration;
  5. import org.springframework.web.servlet.DispatcherServlet;
  6. import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
  7. import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
  8. @Configuration
  9. @ConditionalOnClass(DispatcherServlet.class)
  10. public class MvcConfig implements WebMvcConfigurer {
  11.     @Override
  12.     public void addInterceptors(InterceptorRegistry registry) {
  13.         registry.addInterceptor(new UserInfoInterceptor());
  14.     }
  15. }
复制代码
不外,必要注意的是,这个配置类默认是不会生效的,因为它地点的包是com.hmall.common.config,与其它微服务的扫描包不一致,无法被扫描到,因此无法生效。
基于SpringBoot的自动装配原理,我们要将其添加到resources目录下的META-INF/spring.factories文件中:

 内容如下:
  1. org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  2.   com.hmall.common.config.MyBatisConfig,\
  3.   com.hmall.common.config.MvcConfig
复制代码
2.6 OpenFeign传递用户

由于微服务获取用户信息是通过拦截器在请求头中读取,因此要想实现微服务之间的用户信息传递,就必须在微服务发起调用时把用户信息存入请求头
微服务之间调用是基于OpenFeign来实现的,并不是我们自己发送的请求。我们如何才能让每一个由OpenFeign发起的请求自动携带登任命户信息呢,这里要借助Feign中提供的一个拦截器接口:feign.RequestInterceptor
  1. public interface RequestInterceptor {
  2.   /**
  3.    * Called for every request.
  4.    * Add data using methods on the supplied {@link RequestTemplate}.
  5.    */
  6.   void apply(RequestTemplate template);
  7. }
复制代码
我们只必要实现这个接口,然后实现apply方法,利用RequestTemplate类来添加请求头,将用户信息保存到请求头中。如许以来,每次OpenFeign发起请求的时候都会调用该方法,传递用户信息。
由于FeignClient全部都是在hm-api模块,因此我们在hm-api模块的com.hmall.api.config.DefaultFeignConfig中编写这个拦截器:
  1. @Bean
  2. public RequestInterceptor userInfoRequestInterceptor(){
  3.     return new RequestInterceptor() {
  4.         @Override
  5.         public void apply(RequestTemplate template) {
  6.             // 获取登录用户
  7.             Long userId = UserContext.getUser();
  8.             if(userId == null) {
  9.                 // 如果为空则直接跳过
  10.                 return;
  11.             }
  12.             // 如果不为空则放入请求头中,传递给下游微服务
  13.             template.header("user-info", userId.toString());
  14.         }
  15.     };
  16. }
复制代码
如今微服务之间通过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)引入依靠
  1.   <!--nacos配置管理-->
  2.   <dependency>
  3.       <groupId>com.alibaba.cloud</groupId>
  4.       <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
  5.   </dependency>
  6.   <!--读取bootstrap文件-->
  7.   <dependency>
  8.       <groupId>org.springframework.cloud</groupId>
  9.       <artifactId>spring-cloud-starter-bootstrap</artifactId>
  10.   </dependency>
复制代码
(2)新建bootstrap.yaml文件
  1. spring:
  2.   application:
  3.     name: cart-service # 服务名称
  4.   profiles:
  5.     active: dev
  6.   cloud:
  7.     nacos:
  8.       server-addr: 192.168.150.101 # nacos地址
  9.       config:
  10.         file-extension: yaml # 文件后缀名
  11.         shared-configs: # 共享配置
  12.           - dataId: shared-jdbc.yaml # 共享mybatis配置
  13.           - dataId: shared-log.yaml # 共享日志配置
  14.           - dataId: shared-swagger.yaml # 共享日志配置
复制代码
(3)修改application.yaml
有些配置挪到了bootstrap.yaml,因此就不用重复配置了。
3.2 配置热更新

 有许多的业务相关参数,将来可能会根据实际环境临时调整。比方购物车业务,购物车数目有一个上限,默认是10,这里购物车是写死的固定值,我们应该将其配置在配置文件中,方便后期修改。
但如今的问题是,即便写在配置文件中,修改了配置还是必要重新打包、重启服务才能生效。能不能不用重启,直接生效呢?这里就要用到Nacos配置热更新本领啦!
3.2.1 添加配置到Nacos

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

注意文件的dataId格式:
  1. [服务名]-[spring.active.profile].[后缀名]
复制代码
文件名称由三部分组成:


  • 服务名:我们是购物车服务,所以是cart-service
  • spring.active.profile:就是spring boot中的spring.active.profile,可以省略,则全部profile共享该配置
  • 后缀名:比方yaml
 这里直接使用cart-service.yaml这个名称,则不管是dev还是local环境都可以共享该配置。
 配置内容如下:
  1. hm:
  2.   cart:
  3.     maxAmount: 1 # 购物车商品数量上限
复制代码
3.2.2 配置热更新

在微服务中读取配置,实现配置热更新。
(1)在cart-service中新建一个属性读取类:

  1. package com.hmall.cart.config;
  2. import lombok.Data;
  3. import org.springframework.boot.context.properties.ConfigurationProperties;
  4. import org.springframework.stereotype.Component;
  5. @Data
  6. @Component
  7. @ConfigurationProperties(prefix = "hm.cart")
  8. public class CartProperties {
  9.     private Integer maxAmount;
  10. }
复制代码
(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 服务的地址及配置文件格式。
  1. spring:
  2.   application:
  3.     name: gateway
  4.   cloud:
  5.     nacos:
  6.       server-addr: 192.168.150.101 # Nacos 服务地址
  7.       config:
  8.         file-extension: yaml # 配置文件格式
  9.         shared-configs:
  10.           - dataId: shared-log.yaml # 如果有共享配置,可指定 dataId
复制代码
(3)初始化基础网关配置


  • 在 application.yml 文件中设置网关的基础配置,比如服务端口、JWT 配置、无需校验的路径等。
  1. server:
  2.   port: 8080 # 网关端口
  3. hm:
  4.   jwt:
  5.     location: classpath:hmall.jks # JWT 秘钥文件地址
  6.     alias: hmall
  7.     password: hmall123
  8.     tokenTTL: 30m
  9.   auth:
  10.     excludePaths: # 无需登录校验的路径
  11.       - /search/**
  12.       - /users/login
  13.       - /items/**
复制代码
(4)动态路由监听器


  • 创建动态路由加载器 DynamicRouteLoader,实现监听 Nacos 配置变化并实时更新路由表。

  1. package com.hmall.gateway.route;
  2. import cn.hutool.json.JSONUtil;
  3. import com.alibaba.cloud.nacos.NacosConfigManager;
  4. import com.alibaba.nacos.api.config.listener.Listener;
  5. import com.alibaba.nacos.api.exception.NacosException;
  6. import com.hmall.common.utils.CollUtils;
  7. import lombok.RequiredArgsConstructor;
  8. import lombok.extern.slf4j.Slf4j;
  9. import org.springframework.cloud.gateway.route.RouteDefinition;
  10. import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
  11. import org.springframework.stereotype.Component;
  12. import reactor.core.publisher.Mono;
  13. import javax.annotation.PostConstruct;
  14. import java.util.HashSet;
  15. import java.util.List;
  16. import java.util.Set;
  17. import java.util.concurrent.Executor;
  18. @Slf4j
  19. @Component
  20. @RequiredArgsConstructor
  21. public class DynamicRouteLoader {
  22.     private final RouteDefinitionWriter writer;
  23.     private final NacosConfigManager nacosConfigManager;
  24.     // 路由配置文件的id和分组
  25.     private final String dataId = "gateway-routes.json";
  26.     private final String group = "DEFAULT_GROUP";
  27.     // 保存更新过的路由id
  28.     private final Set<String> routeIds = new HashSet<>();
  29.     @PostConstruct
  30.     public void initRouteConfigListener() throws NacosException {
  31.         // 1.注册监听器并首次拉取配置
  32.         String configInfo = nacosConfigManager.getConfigService()
  33.                 .getConfigAndSignListener(dataId, group, 5000, new Listener() {
  34.                     @Override
  35.                     public Executor getExecutor() {
  36.                         return null;
  37.                     }
  38.                     @Override
  39.                     public void receiveConfigInfo(String configInfo) {
  40.                         updateConfigInfo(configInfo);
  41.                     }
  42.                 });
  43.         // 2.首次启动时,更新一次配置
  44.         updateConfigInfo(configInfo);
  45.     }
  46.     private void updateConfigInfo(String configInfo) {
  47.         log.debug("监听到路由配置变更,{}", configInfo);
  48.         // 1.反序列化
  49.         List<RouteDefinition> routeDefinitions = JSONUtil.toList(configInfo, RouteDefinition.class);
  50.         // 2.更新前先清空旧路由
  51.         // 2.1.清除旧路由
  52.         for (String routeId : routeIds) {
  53.             writer.delete(Mono.just(routeId)).subscribe();
  54.         }
  55.         routeIds.clear();
  56.         // 2.2.判断是否有新的路由要更新
  57.         if (CollUtils.isEmpty(routeDefinitions)) {
  58.             // 无新路由配置,直接结束
  59.             return;
  60.         }
  61.         // 3.更新路由
  62.         routeDefinitions.forEach(routeDefinition -> {
  63.             // 3.1.更新路由
  64.             writer.save(Mono.just(routeDefinition)).subscribe();
  65.             // 3.2.记录路由id,方便将来删除
  66.             routeIds.add(routeDefinition.getId());
  67.         });
  68.     }
  69. }
复制代码

  • 监听 Nacos 的路由配置文件 gateway-routes.json。
  • 首次启动时拉取路由配置。
  • 在 Nacos 配置变更时实时更新路由表。
  • 删除旧路由并添加新的路由规则。
 (5)在Nacos配置路由规则
由文件名为gateway-routes.json,范例为json:

  1. [
  2.     {
  3.         "id": "item",
  4.         "predicates": [{
  5.             "name": "Path",
  6.             "args": {"_genkey_0":"/items/**", "_genkey_1":"/search/**"}
  7.         }],
  8.         "filters": [],
  9.         "uri": "lb://item-service"
  10.     },
  11.     {
  12.         "id": "cart",
  13.         "predicates": [{
  14.             "name": "Path",
  15.             "args": {"_genkey_0":"/carts/**"}
  16.         }],
  17.         "filters": [],
  18.         "uri": "lb://cart-service"
  19.     },
  20.     {
  21.         "id": "user",
  22.         "predicates": [{
  23.             "name": "Path",
  24.             "args": {"_genkey_0":"/users/**", "_genkey_1":"/addresses/**"}
  25.         }],
  26.         "filters": [],
  27.         "uri": "lb://user-service"
  28.     },
  29.     {
  30.         "id": "trade",
  31.         "predicates": [{
  32.             "name": "Path",
  33.             "args": {"_genkey_0":"/orders/**"}
  34.         }],
  35.         "filters": [],
  36.         "uri": "lb://trade-service"
  37.     },
  38.     {
  39.         "id": "pay",
  40.         "predicates": [{
  41.             "name": "Path",
  42.             "args": {"_genkey_0":"/pay-orders/**"}
  43.         }],
  44.         "filters": [],
  45.         "uri": "lb://pay-service"
  46.     }
  47. ]
复制代码
总结 

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

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

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


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

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



  • Nacos监听 -> 检测到配置变更 -> 读取配置 -> 更新路由表(删除旧路由、添加新路由) -> 路由实时生效

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

一给

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表