当前位置: 首页 > news >正文

Spring Cloud Gateway动态路由及路由插件实现方案

前言

sim-framework之前使用Zuul作为网关,结合Eureka实现了动态路由及灰度路由,但是存在以下几个问题:

  • 性能问题:Zuul基于线程隔离,一个请求需要一个线程处理,而Gateway基于事件驱动,少量线程即可支持大量并发(仅仅是并发度和吞吐量,并不能提高业务处理效率),在性能不是很好的服务器上,太多的线程数反而会降低并发度。
  • 流行度:Zuul在后期的SpringCloud版本中将不会再继续集成,所以有必要更换为Gateway。
  • 版本问题:除了Zuul在SpringCloud版本中的集成问题,另一个是因为SpringBoot对Java版本的支持问题,SpringBoot3.x后仅支持JDK17及以后的版本,而早期的Zuul并不能使用上JDK17后的新特性;其次后续也可以以更小的成本升级到JDK21,使用它的虚拟线程,进一步提高性能。

因此对网关进行了重构,更新了基础依赖版本,引入了Gateway和Nacos,移除了Eureka。

动态路由实现

所谓动态路由,是指在不停机的情况下,动态的配置路由地址。之前使用Zuul时,通过扩展ZuulProperties.ZuulRoute实现了一个DatabaseRouteLocator[源码],通过从数据库中加载路由配置,然后发布RoutesRefreshedEvent 事件去触发路由更新,整体流程如下:
在这里插入图片描述
更换为Gateway后,整体流程如下:
在这里插入图片描述

其中最主要的是增加了使用redis监听消息来更新路由,相比于定时任务,提高了配置变更的实时性,并且在网关重启的时候,会从redis中拉取全量的路由配置,并加载到内存中,同时也保留了定时任务刷新配置,因为redis的发布订阅是不可靠的,可能会因为连接异常或者网关节点停机等原因,导致无法收到消息,无法触发路由更新,所以仍然需要定时任务定时刷新作为补偿。这里为什么不适用消息队列呢?因为作为一个轻量级的框架,就尽可能的少引入外部组件,所以这里暂且使用redis作为路由配置更新通知的中间件。

Gateway的动态路由实现方式很简单,Gateway中有一个RouteDefinitionRepository,它保存着路由的配置信息,并且它提供了savedelete方法,我们只需要调用这两个方法更新路由后,再发布RefreshRoutesEvent事件即可。

public Mono<Void> add(RouteDefinition route) {log.info("Add route: {}", route);return routeDefinitionRepository.save(Mono.just(route)).thenEmpty((v) -> publisher.publishEvent(new RefreshRoutesEvent(this)));}public Mono<Void> update(RouteDefinition route) {log.info("Update route: {}", route);return routeDefinitionRepository.save(Mono.just(route)).thenEmpty((v) -> publisher.publishEvent(new RefreshRoutesEvent(this)));}public Mono<Void> delete(String id) {log.info("Delete route, Route ID: {}", id);return this.routeDefinitionRepository.delete(Mono.just(id)).onErrorResume(NotFoundException.class, e -> Mono.empty()).thenEmpty((v) -> publisher.publishEvent(new RefreshRoutesEvent(this)));}

sim-framework提供了可视化的路由配置,再不重启网关的情况下可实现动态路由配置。
在这里插入图片描述

路由插件实现

Gateway提供了很多的PredicateFilter,但是总有一些场景无法满足我们,所以我们可以通过自定义插件的方式,将通用的、可复用的、无强业务相关性的流程,封装为一个网关插件,比如鉴权、限流、流量分发等等。

Gateway提供了AbstractRoutePredicateFactoryAbstractGatewayFilterFactory,分别对应路由的匹配器和过滤器,我们可以通过继承它来实现一个自定义的匹配器或者过滤器。

以下,实现一个自定义的过滤器,它的作用是将获取到的权限信息,再经过权限验证后,转发到下游服务时,将它移除掉,避免暴露过多的信息给下游服务:

@Component
public class RemovePermissionGatewayFilterFactory extends AbstractGatewayFilterFactory<RemovePermissionGatewayFilterFactory.Config> implements GatewayFilterSupport {public RemovePermissionGatewayFilterFactory(AuthProperties properties) {super(Config.class);this.properties = properties;}private final AuthProperties properties;@Overridepublic GatewayFilter apply(Config config) {return (exchange, chain) -> {ServerHttpRequest request = exchange.getRequest();if (!shouldFilter(request, properties)) {return chain.filter(exchange);}return exchange.getPrincipal().map(principal -> (UserPrincipal) principal).doOnNext(user -> {user.setPermissions(null);exchange.mutate().principal(Mono.justOrEmpty(user));}).then(chain.filter(exchange));};}public static class Config {//ignore}
}

apply方法中,我可以拿到exchange,通过它可以获取到请求对象和响应对象,从而可以对请求以及响应做任何处理。

将自定义的过滤器注册为Bean即可在配置文件中使用它:

spring:cloud:gateway:routes:- id: sim-service-adminuri: lb://sim-service-adminpredicates:- Path=/server/**filters:- StripPrefix=1- Authentication- Permission- RemovePermission

注意命名规范:SpringCloud建议,自定义过滤器类名以GatewayFilterFactory结尾,我们应当遵循该规范,避免日后出现问题。同理匹配器也一样,建议以RoutePredicateFactory结尾。

以上,是对于路由的静态扩展(提前编写好插件代码),考虑到作为一个网关,承载了整个系统的流量入口,并不能频繁的重启,所以需要提供动态扩展的能力,也就是动态加载插件的能力。对于动态扩展,我们目前使用动态加载jar包的形式,利用Spring的Bean动态注册,将自定义的插件注册为Bean后即可配置使用(该部分目前正在开发中)。

在这里插入图片描述
对于插件这部分,我们将其抽象为网关组件,并提供组件管理功能,可以注册自定义组件,也可以新增内置组件(对网关二次开发),并且支持组件的版本管理和回退。

整体大致流程如下:
在这里插入图片描述
这部分的实现细节,后续单独补充。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Isaac Sim仿真平台学习(2)基础知识
  • ‌前端列表展示1000条大量数据时,后端通常需要进行一定的处理。‌
  • 视频美颜SDK与直播美颜工具的架构设计与性能优化
  • STM32之点亮LED灯
  • 大数据量实现滚动分页-vue3+element-plus实现方式
  • docker升级docker pull mysql:5.7.37异常
  • C++ 11---lambda表达式与包装器
  • 整体思想以及取模
  • Spring @Async注解【总结记录】
  • 点对点专线的带宽管理和控制功能解析
  • 【AI趋势9】开源普惠
  • c语言练习题1
  • APP 整改要求 “未清晰明示高德SDK处理IP地址、SSID、BSSID的目的、方式和范围。”
  • 【QT】——1_QT学习笔记
  • 学懂C++(三十九):网络编程——深入详解 TCP 和 UDP 的区别和应用场景
  • [译] React v16.8: 含有Hooks的版本
  • 【React系列】如何构建React应用程序
  • Facebook AccountKit 接入的坑点
  • Gradle 5.0 正式版发布
  • iBatis和MyBatis在使用ResultMap对应关系时的区别
  • IDEA 插件开发入门教程
  • JavaScript 事件——“事件类型”中“HTML5事件”的注意要点
  • JavaScript 无符号位移运算符 三个大于号 的使用方法
  • JavaScript对象详解
  • Javascript设计模式学习之Observer(观察者)模式
  • jquery ajax学习笔记
  • js中的正则表达式入门
  • ng6--错误信息小结(持续更新)
  • python学习笔记 - ThreadLocal
  • python学习笔记-类对象的信息
  • SegmentFault 技术周刊 Vol.27 - Git 学习宝典:程序员走江湖必备
  • Spring Boot快速入门(一):Hello Spring Boot
  • WebSocket使用
  • 后端_ThinkPHP5
  • 简单数学运算程序(不定期更新)
  • 那些年我们用过的显示性能指标
  • 如何设计一个比特币钱包服务
  • 三栏布局总结
  • 实现简单的正则表达式引擎
  • 提升用户体验的利器——使用Vue-Occupy实现占位效果
  • 好程序员大数据教程Hadoop全分布安装(非HA)
  • 教程:使用iPhone相机和openCV来完成3D重建(第一部分) ...
  • #微信小程序:微信小程序常见的配置传旨
  • #我与虚拟机的故事#连载20:周志明虚拟机第 3 版:到底值不值得买?
  • #在 README.md 中生成项目目录结构
  • (2022版)一套教程搞定k8s安装到实战 | RBAC
  • (CPU/GPU)粒子继承贴图颜色发射
  • (Demo分享)利用原生JavaScript-随机数-实现做一个烟花案例
  • (html5)在移动端input输入搜索项后 输入法下面为什么不想百度那样出现前往? 而我的出现的是换行...
  • (LeetCode C++)盛最多水的容器
  • (Mirage系列之二)VMware Horizon Mirage的经典用户用例及真实案例分析
  • (zt)基于Facebook和Flash平台的应用架构解析
  • (读书笔记)Javascript高级程序设计---ECMAScript基础
  • (二) Windows 下 Sublime Text 3 安装离线插件 Anaconda
  • (二)springcloud实战之config配置中心