Spring Cloud GateWay动态路由配置

本文涉及的产品
RDS AI 助手,专业版
RDS MySQL DuckDB 分析主实例,集群系列 4核8GB
RDS MySQL DuckDB 分析主实例,基础系列 4核8GB
简介: Spring Cloud GateWay动态路由配置

目录


  • GateWay配置
  • 在mysql定义表gateway_define, 表结构如下面的GatewayDefine实体类:
  • 定义repository和service,采用JPA实现
  • 定义MysqlRouteDefinitionRepository类,实现RouteDefinitionRepository接口的getRouteDefinitions方法,获取从数据库里面装载的路由配置,当然还有save和delete其他方法。
  • 在启动类GatewayServiceApplication中添加两个Bean。 添加ApplicationStartup类,在Spring Boot启动时装载路由配置信息, 说明看注释:
  • 其他
  • 最后
  • 后记


Spring Cloud Gateway是由spring官方基于Spring5.0,Spring Boot2.0,Project Reactor等技术开发的网关,目的是代替原先版本中的Spring Cloud Netfilx Zuul。目前Netfilx已经开源了Zuul2.0,但Spring 没有考虑集成,而是推出了自己开发的Spring Cloud GateWay。


该项目提供了一个构建在Spring Ecosystem之上的API网关,旨在提供一种简单而有效的途径来发送API,并向他们提供交叉关注点:例如:安全性,监控/指标和弹性。在这里废话少说,直接把我实现动态自定义路由的方法托出,共大家参考。由于水平有限,难免有不当或者错误之处,请大家指正,谢谢。


GateWay配置


一般的,我们如果使用Spring Cloud GateWay进行配置,类似于下面的样子:


  1. spring:
  2.  cloud:
  3.    gateway:
  4.      discovery:
  5.        locator:
  6.          enabled: true
  7.      routes:
  8.      - id: sample-service-a
  9.        uri: lb://SAMPLE-SERVICE-A-HA
  10.        predicates:
  11.        - Path=/customeradd/**
  12.        filters:
  13.        - RewritePath=/customeradd,/customer/add


当我们要新增或者改变一个网关路由时,我们不得不停止网关服务,修改配置文件,保存再重新启动网关服务,这样才能让我们新的设置生效。


设想一样,如果是在生产环境,为了一个小小的路由变更,这样的停止再重启恐怕谁也受不了吧。接下来,看看我们怎么能做到动态配置网关路由,让网关路由配置在服务不需要重启的情况生效。


在mysql定义表gateway_define, 表结构如下面的GatewayDefine实体类:

  1. @Entity
  2. @Table(name = "gateway_define")
  3. public class GatewayDefine implements Serializable {
  4.   @Id
  5.   @GeneratedValue(strategy = GenerationType.AUTO)
  6.   private String id;

  7.   private String uri;

  8.   private String predicates;

  9.   private String filters;

  10.   public String getId() {
  11.       return id;
  12.   }

  13.   public void setId(String id) {
  14.       this.id = id;
  15.   }

  16.   public String getUri() {
  17.       return uri;
  18.   }
  19.   public void setUri(String uri) {
  20.       this.uri = uri;
  21.   }
  22.   public String getPredicates() {
  23.       return this.predicates;
  24.   }

  25.   public void setPredicates(String predicates) {
  26.       this.predicates = predicates;
  27.   }

  28.   public List<PredicateDefinition> getPredicateDefinition() {
  29.       if (this.predicates != null) {
  30.           List<PredicateDefinition> predicateDefinitionList = JSON.parseArray(this.predicates, PredicateDefinition.class);
  31.           return predicateDefinitionList;
  32.       } else {
  33.           return null;
  34.       }
  35.   }

  36.   public String getFilters() {
  37.       return filters;
  38.   }
  39.   public List<FilterDefinition> getFilterDefinition() {
  40.       if (this.filters != null) {
  41.           List<FilterDefinition> filterDefinitionList = JSON.parseArray(this.filters, FilterDefinition.class);
  42.           return filterDefinitionList;
  43.       } else {
  44.           return null;
  45.       }
  46.   }

  47.   public void setFilters(String filters) {
  48.       this.filters = filters;
  49.   }

  50.   @Override
  51.   public String toString() {
  52.       return "GatewayDefine{" +
  53.               "id='" + id + '\'' +
  54.               ", uri='" + uri + '\'' +
  55.               ", predicates='" + predicates + '\'' +
  56.               ", filters='" + filters + '\'' +
  57.               '}';
  58.   }
  59. }


其中:id为Eureka注册的服务名; uri、predicates、filters分别应上面配置文件片段中的predicates和filters(这两个保存的都是json)


定义repository和service,采用JPA实现

  1. @Repository
  2. public interface GatewayDefineRepository  extends JpaRepository<GatewayDefine, String> {

  3.  @Override
  4.  List<GatewayDefine> findAll();
  5.  @Override
  6.  GatewayDefine save(GatewayDefine gatewayDefine);
  7.  @Override
  8.  void deleteById(String id);
  9.  @Override
  10.  boolean existsById(String id);
  11. }

  12. public interface GatewayDefineService {
  13.  List<GatewayDefine> findAll() throws Exception;
  14.  String loadRouteDefinition() throws Exception;
  15.  GatewayDefine save(GatewayDefine gatewayDefine) throws Exception;
  16.  void deleteById(String id) throws Exception;
  17.  boolean existsById(String id)throws Exception;

  18. }

  19. @Service
  20. public class GatewayDefineServiceImpl implements GatewayDefineService {
  21.  @Autowired
  22.  GatewayDefineRepository gatewayDefineRepository;

  23.  @Autowired
  24.  private GatewayDefineService gatewayDefineService;

  25.  @Autowired
  26.  private RouteDefinitionWriter routeDefinitionWriter;

  27.  private ApplicationEventPublisher publisher;

  28.  @Override
  29.  public List<GatewayDefine> findAll() throws Exception {
  30.      return gatewayDefineRepository.findAll();
  31.  }

  32.  @Override
  33.  public String loadRouteDefinition() {
  34.      try {
  35.          List<GatewayDefine> gatewayDefineServiceAll = gatewayDefineService.findAll();
  36.          if (gatewayDefineServiceAll == null) {
  37.              return "none route defined";
  38.          }
  39.          for (GatewayDefine gatewayDefine : gatewayDefineServiceAll) {
  40.              RouteDefinition definition = new RouteDefinition();
  41.              definition.setId(gatewayDefine.getId());
  42.              definition.setUri(new URI(gatewayDefine.getUri()));
  43.              List<PredicateDefinition> predicateDefinitions = gatewayDefine.getPredicateDefinition();
  44.              if (predicateDefinitions != null) {
  45.                  definition.setPredicates(predicateDefinitions);
  46.              }
  47.              List<FilterDefinition> filterDefinitions = gatewayDefine.getFilterDefinition();
  48.              if (filterDefinitions != null) {
  49.                  definition.setFilters(filterDefinitions);
  50.              }
  51.              routeDefinitionWriter.save(Mono.just(definition)).subscribe();
  52.              this.publisher.publishEvent(new RefreshRoutesEvent(this));
  53.          }
  54.          return "success";
  55.      } catch (Exception e) {
  56.          e.printStackTrace();
  57.          return "failure";
  58.      }
  59.  }

  60.  @Override
  61.  public GatewayDefine save(GatewayDefine gatewayDefine) throws Exception {
  62.      gatewayDefineRepository.save(gatewayDefine);
  63.      return gatewayDefine;
  64.  }

  65.  @Override
  66.  public void deleteById(String id) throws Exception {
  67.      gatewayDefineRepository.deleteById(id);
  68.  }

  69.  @Override
  70.  public boolean existsById(String id) throws Exception {
  71.      return gatewayDefineRepository.existsById(id);
  72.  }
  73. }


注:


loadRouteDefinition是重点,它从数据库里获取动态定义的路由,最后封装成RouteDefinition 类实例,调用RouteDefinitionWriter 的save方法保存。

RouteDefinitionWriter是个接口,真正实现的是InMemoryRouteDefinitionRepository类,在InMemoryRouteDefinitionRepository定义了一个SynchronizedMap 类,所有的设置都在这儿保存。


定义MysqlRouteDefinitionRepository类,实现RouteDefinitionRepository接口的getRouteDefinitions方法,获取从数据库里面装载的路由配置,当然还有save和delete其他方法。


  1. public class MysqlRouteDefinitionRepository implements RouteDefinitionRepository {
  2.  @Autowired
  3.  private GatewayDefineService gatewayDefineService;
  4.  @Override
  5.  public Flux<RouteDefinition> getRouteDefinitions() {
  6.      try {
  7.          List<GatewayDefine> gatewayDefineList = gatewayDefineService.findAll();
  8.          Map<String, RouteDefinition> routes = new LinkedHashMap<String, RouteDefinition>();
  9.          for (GatewayDefine gatewayDefine: gatewayDefineList) {
  10.              RouteDefinition definition = new RouteDefinition();
  11.              definition.setId(gatewayDefine.getId());
  12.              definition.setUri(new URI(gatewayDefine.getUri()));
  13.              List<PredicateDefinition> predicateDefinitions = gatewayDefine.getPredicateDefinition();
  14.              if (predicateDefinitions != null) {
  15.                  definition.setPredicates(predicateDefinitions);
  16.              }
  17.              List<FilterDefinition> filterDefinitions = gatewayDefine.getFilterDefinition();
  18.              if (filterDefinitions != null) {
  19.                  definition.setFilters(filterDefinitions);
  20.              }
  21.              routes.put(definition.getId(), definition);

  22.          }
  23.          return Flux.fromIterable(routes.values());
  24.      } catch (Exception e) {
  25.          e.printStackTrace();
  26.          return Flux.empty();
  27.      }
  28.  }

  29.  @Override
  30.  public Mono<Void> save(Mono<RouteDefinition> route) {
  31.      return route.flatMap(r -> {
  32.          try {
  33.              GatewayDefine gatewayDefine = new GatewayDefine();
  34.              gatewayDefine.setId(r.getId());
  35.              gatewayDefine.setUri(r.getUri().toString());
  36.              gatewayDefine.setPredicates(JSON.toJSONString(r.getPredicates()));
  37.              gatewayDefine.setFilters(JSON.toJSONString(r.getFilters()));
  38.              gatewayDefineService.save(gatewayDefine);
  39.              return Mono.empty();

  40.          } catch (Exception e) {
  41.              e.printStackTrace();
  42.              return Mono.defer(() -> Mono.error(new NotFoundException("RouteDefinition save error: "+ r.getId())));
  43.          }

  44.      });
  45.  }

  46.  @Override
  47.  public Mono<Void> delete(Mono<String> routeId) {
  48.      return routeId.flatMap(id -> {
  49.          try {
  50.              gatewayDefineService.deleteById(id);
  51.              return Mono.empty();

  52.          } catch (Exception e) {
  53.              e.printStackTrace();
  54.              return Mono.defer(() -> Mono.error(new NotFoundException("RouteDefinition delete error: " + routeId)));
  55.          }            
  56.      });
  57.  }
  58. }


在启动类GatewayServiceApplication中添加两个Bean。


  1. @Bean
  2. public RouteDefinitionWriter routeDefinitionWriter() {
  3.     return new InMemoryRouteDefinitionRepository();
  4. }

  5. @Bean
  6. public MysqlRouteDefinitionRepository mysqlRouteDefinitionRepository() {
  7.     return new MysqlRouteDefinitionRepository();
  8. }


添加ApplicationStartup类,在Spring Boot启动时装载路由配置信息, 说明看注释:


  1. /**
  2. * 在Spring Boot程序启动后会检测程序中是否有CommandLineRunner
  3. * 和ApplicationRunner接口的实例,
  4. * 如果存在,则会执行对应实现类中的run()方法,而且只执行一次
  5. */
  6. public class ApplicationStartup implements ApplicationRunner {

  7.   @Autowired
  8.   private GatewayDefineService gatewayDefineService;

  9.   @Override
  10.   public void run(ApplicationArguments args) throws Exception {
  11.       gatewayDefineService.loadRouteDefinition();
  12.   }
  13. }


完成。


其他


我这里面没有界面设置路由,我是在配置文件中配置我要的路由,然后通过 /actuator/gateway/routes 获取所有路由的json, 也可以通过 /actuator/gateway/routes/{id} 获取单独一个路由的json.然后手工往数据库里面插入数据,再把网关服务停止,删除配置文件中的路由设定,再重新启动网关功能,通过 /actuator/gateway/routes 能够获取同样的路由json, 通过curl访问设置的路由同样生效。


当然完全可以独立开发一个应用,有界面来读取数据库中的路由配置,可以增加和修改路由信息。再通过Spring Cloud Config的配置来刷新多个网关路由的信息,实现多个网关服务的路由信息实时更新。反正有各种方法可供选择。


最后


完全是记录自己前一段时间的研究心得。水平有限,有什么不对的地方请大家指正。还有,Spring Cloud GateWay还不支持OAuth2, 所以想统一集成授权、认证等功能的还是使用ZUUL吧。下一次有时间,我会写一下ZUUL的动态路由功能实现以及避免频繁刷新路由信息。反正和GateWay相似,但是还是有区别的。


实现参考了网上很多人的源码和文章,在此表示感谢!也阅读了Spring Cloud GateWay 部分源代码,对Spring Cloud GateWay有了一定的认识,聊以自慰。


后记


最新的Spring Cloud Greenwich.RELEASE中Gateway 过滤器新增支持OAuth2,我觉得可以抛弃ZUUL了。

相关实践学习
自建数据库迁移到云数据库
本场景将引导您将网站的自建数据库平滑迁移至云数据库RDS。通过使用RDS,您可以获得稳定、可靠和安全的企业级数据库服务,可以更加专注于发展核心业务,无需过多担心数据库的管理和维护。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
相关文章
|
6月前
|
缓存 JSON NoSQL
别再手写过滤器!SpringCloud Gateway 内置30 个,少写 80% 重复代码
小富分享Spring Cloud Gateway内置30+过滤器,涵盖请求、响应、路径、安全等场景,无需重复造轮子。通过配置实现Header处理、限流、重试、熔断等功能,提升网关开发效率,避免代码冗余。
609 1
|
7月前
|
负载均衡 监控 Java
Spring Cloud Gateway 全解析:路由配置、断言规则与过滤器实战指南
本文详细介绍了 Spring Cloud Gateway 的核心功能与实践配置。首先讲解了网关模块的创建流程,包括依赖引入(gateway、nacos 服务发现、负载均衡)、端口与服务发现配置,以及路由规则的设置(需注意路径前缀重复与优先级 order)。接着深入解析路由断言,涵盖 After、Before、Path 等 12 种内置断言的参数、作用及配置示例,并说明了自定义断言的实现方法。随后重点阐述过滤器机制,区分路由过滤器(如 AddRequestHeader、RewritePath、RequestRateLimiter 等)与全局过滤器的作用范围与配置方式,提
Spring Cloud Gateway 全解析:路由配置、断言规则与过滤器实战指南
|
8月前
|
API
使用Gateway with Inference Extension路由外部MaaS服务
本文介绍如何通过Gateway with Inference Extension对接百炼服务,实现请求路由时自动添加API Key并重写路径,包含操作步骤及验证方法。
|
9月前
|
前端开发 Java API
Spring Cloud Gateway Server Web MVC报错“Unsupported transfer encoding: chunked”解决
本文解析了Spring Cloud Gateway中出现“Unsupported transfer encoding: chunked”错误的原因,指出该问题源于Feign依赖的HTTP客户端与服务端的`chunked`传输编码不兼容,并提供了具体的解决方案。通过规范Feign客户端接口的返回类型,可有效避免该异常,提升系统兼容性与稳定性。
629 0
|
10月前
|
缓存 监控 Java
说一说 SpringCloud Gateway 堆外内存溢出排查
我是小假 期待与你的下一次相遇 ~
1290 5
|
10月前
|
Java API Nacos
|
存储 人工智能 Kubernetes
ACK Gateway with AI Extension:面向Kubernetes大模型推理的智能路由实践
本文介绍了如何利用阿里云容器服务ACK推出的ACK Gateway with AI Extension组件,在Kubernetes环境中为大语言模型(LLM)推理服务提供智能路由和负载均衡能力。文章以部署和优化QwQ-32B模型为例,详细展示了从环境准备到性能测试的完整实践过程。
|
Cloud Native Java Nacos
springcloud/springboot集成NACOS 做注册和配置中心以及nacos源码分析
通过本文,我们详细介绍了如何在 Spring Cloud 和 Spring Boot 中集成 Nacos 进行服务注册和配置管理,并对 Nacos 的源码进行了初步分析。Nacos 作为一个强大的服务注册和配置管理平台,为微服务架构提供
4815 14
|
前端开发 Java Nacos
🛡️Spring Boot 3 整合 Spring Cloud Gateway 工程实践
本文介绍了如何使用Spring Cloud Alibaba 2023.0.0.0技术栈构建微服务网关,以应对微服务架构中流量治理与安全管控的复杂性。通过一个包含鉴权服务、文件服务和主服务的项目,详细讲解了网关的整合与功能开发。首先,通过统一路由配置,将所有请求集中到网关进行管理;其次,实现了限流防刷功能,防止恶意刷接口;最后,添加了登录鉴权机制,确保用户身份验证。整个过程结合Nacos注册中心,确保服务注册与配置管理的高效性。通过这些实践,帮助开发者更好地理解和应用微服务网关。
2290 0
🛡️Spring Boot 3 整合 Spring Cloud Gateway 工程实践
|
JSON Java API
利用Spring Cloud Gateway Predicate优化微服务路由策略
Spring Cloud Gateway 的路由配置中,`predicates`​(断言)用于定义哪些请求应该匹配特定的路由规则。 断言是Gateway在进行路由时,根据具体的请求信息如请求路径、请求方法、请求参数等进行匹配的规则。当一个请求的信息符合断言设置的条件时,Gateway就会将该请求路由到对应的服务上。
1405 69
利用Spring Cloud Gateway Predicate优化微服务路由策略

热门文章

最新文章