SpringGateway转发过程

2023-11-13

为什么写?

就想看看springgateway的限流咋做的?但是看着看着就想知道转发过程,然后就写了,总之:转发是通过重组请求头header、uri等信息建立netty客户端连接的访问过程。

Lettuce相较于Jedis有哪些优缺点?

Lettuce 和 Jedis 的定位都是Redis的client,所以他们当然可以直接连接redis server。

Jedis在实现上是直接连接的redis server,如果在多线程环境下是非线程安全的,这个时候只有使用连接池,为每个Jedis实例增加物理连接

Lettuce的连接是基于Netty的,连接实例(StatefulRedisConnection)可以在多个线程间并发访问,应为StatefulRedisConnection是线程安全的,所以一个连接实例(StatefulRedisConnection)就可以满足多线程环境下的并发访问,当然这个也是可伸缩的设计,一个连接实例不够的情况也可以按需增加连接实例。

SpringBoot2.0后之前的jedis已经改成Lettuce了

参见:org.springframework.boot.autoconfigure.data.redis.LettuceConnectionConfiguration,

生效注解:@ConditionalOnClass(RedisClient.class)

RedisURI采用静态内部类Builder进行建造者模式构建ip端口等连接参数,builder.build()实例化RedisURI,之后使用RedisClient.create(redisUri)进行RedisClient客户端实例化,但还未建立连接,之后在RedisClient调用connect方法的时候建立连接。

 lettuce超级链接

一些常用条件注解总结

参考:https://fangjian0423.github.io/2017/05/16/springboot-condition-annotation/

条件注解 对应的Condition处理类 处理逻辑
@ConditionalOnBean OnBeanCondition Spring容器中是否存在对应的实例。可以通过实例的类型、类名、注解、昵称去容器中查找(可以配置从当前容器中查找或者父容器中查找或者两者一起查找)这些属性都是数组,通过”与”的关系进行查找
@ConditionalOnClass OnClassCondition 类加载器中是否存在对应的类。可以通过Class指定(value属性)或者Class的全名指定(name属性)。如果是多个类或者多个类名的话,关系是”与”关系,也就是说这些类或者类名都必须同时在类加载器中存在
@ConditionalOnExpression OnExpressionCondition 判断SpEL 表达式是否成立
@ConditionalOnJava OnJavaCondition 指定Java版本是否符合要求。内部有2个属性value和range。value表示一个枚举的Java版本,range表示比这个老或者新于等于指定的Java版本(默认是新于等于)。内部会基于某些jdk版本特有的类去类加载器中查询,比如如果是jdk9,类加载器中需要存在java.security.cert.URICertStoreParameters;如果是jdk8,类加载器中需要存在java.util.function.Function;如果是jdk7,类加载器中需要存在java.nio.file.Files;如果是jdk6,类加载器中需要存在java.util.ServiceLoader
@ConditionalOnMissingBean OnBeanCondition Spring容器中是否缺少对应的实例。可以通过实例的类型、类名、注解、昵称去容器中查找(可以配置从当前容器中查找或者父容器中查找或者两者一起查找)这些属性都是数组,通过”与”的关系进行查找。还多了2个属性ignored(类名)和ignoredType(类名),匹配的过程中会忽略这些bean
@ConditionalOnMissingClass OnClassCondition 跟ConditionalOnClass的处理逻辑一样,只是条件相反,在类加载器中不存在对应的类
@ConditionalOnNotWebApplication OnWebApplicationCondition 应用程序是否是非Web程序,没有提供属性,只是一个标识。会从判断Web程序特有的类是否存在,环境是否是Servlet环境,容器是否是Web容器等
@ConditionalOnProperty OnPropertyCondition 应用环境中的屬性是否存在。提供prefix、name、havingValue以及matchIfMissing属性。prefix表示属性名的前缀,name是属性名,havingValue是具体的属性值,matchIfMissing是个boolean值,如果属性不存在,这个matchIfMissing为true的话,会继续验证下去,否则属性不存在的话直接就相当于匹配不成功
@ConditionalOnResource OnResourceCondition 是否存在指定的资源文件。只有一个属性resources,是个String数组。会从类加载器中去查询对应的资源文件是否存在
@ConditionalOnSingleCandidate OnBeanCondition Spring容器中是否存在且只存在一个对应的实例。只有3个属性value、type、search。跟ConditionalOnBean中的这3种属性值意义一样
@ConditionalOnWebApplication OnWebApplicationCondition 应用程序是否是Web程序,没有提供属性,只是一个标识。会从判断Web程序特有的类是否存在,环境是否是Servlet环境,容器是否是Web容器等
例子 例子意义
@ConditionalOnBean(javax.sql.DataSource.class) Spring容器或者所有父容器中需要存在至少一个javax.sql.DataSource类的实例
@ConditionalOnClass
({ Configuration.class,
FreeMarkerConfigurationFactory.class })
类加载器中必须存在Configuration和FreeMarkerConfigurationFactory这两个类
@ConditionalOnExpression
(“‘${server.host}’==’localhost’”)
server.host配置项的值需要是localhost
ConditionalOnJava(JavaVersion.EIGHT) Java版本至少是8
@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT) Spring当前容器中不存在ErrorController类型的bean
@ConditionalOnMissingClass
(“GenericObjectPool”)
类加载器中不能存在GenericObjectPool这个类
@ConditionalOnNotWebApplication 必须在非Web应用下才会生效
@ConditionalOnProperty(prefix = “spring.aop”, name = “auto”, havingValue = “true”, matchIfMissing = true) 应用程序的环境中必须有spring.aop.auto这项配置,且它的值是true或者环境中不存在spring.aop.auto配置(matchIfMissing为true)
@ConditionalOnResource
(resources=”mybatis.xml”)
类加载路径中必须存在mybatis.xml文件
@ConditionalOnSingleCandidate
(PlatformTransactionManager.class)
Spring当前或父容器中必须存在PlatformTransactionManager这个类型的实例,且只有一个实例
@ConditionalOnWebApplication 必须在Web应用下才会生效

路由分析


​​路由信息:

GatewayProperties成员变量private List<RouteDefinition> routes = new ArrayList<>();存储配置文件的基本路由信息,配置类GatewayAutoConfiguration实例化CachingRouteLocator,将路由信息routeLocators注入到CompositeRouteLocator中,CompositeRouteLocator构造参数是Fluw的Iterable创建的数据流,是反应是编程创建的一种,这里可以想到的是方便遍历路由信息,反应式编程常用操作。需要说明的是CachingRouteLocator是一个RefreshRoutesEvent的事件监听器。

@Bean
public RouteLocator cachedCompositeRouteLocator(List<RouteLocator> routeLocators) {
		return new CachingRouteLocator(
				new CompositeRouteLocator(Flux.fromIterable(routeLocators)));
	}

在Applicationcontext的refresh最后一步finishRefresh()过程中,调用监听器(观察者模式),而后触发了RefreshRoutesEvent事件,调用CachingRouteLocator的onApplicationEvent方法转换路由信息。保存到成员变量Flux<Route> routes中。

HadlerMapping信息:

把路由RouteLocator注入到HadlerMapping中,目的值当客户端访问的时候,通过路由信息匹配lookupRoute

@Bean
	public RoutePredicateHandlerMapping routePredicateHandlerMapping(
			FilteringWebHandler webHandler, RouteLocator routeLocator,
			GlobalCorsProperties globalCorsProperties, Environment environment) {
		return new RoutePredicateHandlerMapping(webHandler, routeLocator,
				globalCorsProperties, environment);
	}

访问过程:

访问的第一站是DispatcherHandler类,执行其中的getHandler获取合适的执行器执行invokeHandler方法

@Override
	public Mono<Void> handle(ServerWebExchange exchange) {
		if (this.handlerMappings == null) {
			return createNotFoundError();
		}
		return Flux.fromIterable(this.handlerMappings)
				.concatMap(mapping -> mapping.getHandler(exchange))
				.next()
				.switchIfEmpty(createNotFoundError())
				.flatMap(handler -> invokeHandler(exchange, handler))
				.flatMap(result -> handleResult(exchange, result));
	}
getHandler匹配路由信息
protected Mono<Route> lookupRoute(ServerWebExchange exchange) {
		return this.routeLocator.getRoutes()
				// individually filter routes so that filterWhen error delaying is not a
				// problem
				.concatMap(route -> Mono.just(route).filterWhen(r -> {
					// add the current route we are testing
					exchange.getAttributes().put(GATEWAY_PREDICATE_ROUTE_ATTR, r.getId());
					return r.getPredicate().apply(exchange);
				})
						// instead of immediately stopping main flux due to error, log and
						// swallow it
						.doOnError(e -> logger.error(
								"Error applying predicate for route: " + route.getId(),
								e))
						.onErrorResume(e -> Mono.empty()))
				// .defaultIfEmpty() put a static Route not found
				// or .switchIfEmpty()
				// .switchIfEmpty(Mono.<Route>empty().log("noroute"))
				.next()
				// TODO: error handling
				.map(route -> {
					if (logger.isDebugEnabled()) {
						logger.debug("Route matched: " + route.getId());
					}
					validateRoute(route, exchange);
					return route;
				});

	}
限流过滤器:


转发属性设置RouteToRequestUrlFilter,将执行exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, mergedUrl);
把转发的url设置的exchange属性里边,目的是执行到转发过滤器的时候,取出来重新赋值到request url。
@Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
		if (route == null) {
			return chain.filter(exchange);
		}
		log.trace("RouteToRequestUrlFilter start");
		URI uri = exchange.getRequest().getURI();
		boolean encoded = containsEncodedParts(uri);
		URI routeUri = route.getUri();

		// 省略。。。

		URI mergedUrl = UriComponentsBuilder.fromUri(uri)
				// .uri(routeUri)
				.scheme(routeUri.getScheme()).host(routeUri.getHost())
				.port(routeUri.getPort()).build(encoded).toUri();
		exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, mergedUrl);
		return chain.filter(exchange);
	}
转发路由NettyRoutingFilter:
@Override
	@SuppressWarnings("Duplicates")
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);

		// 省略。。。。
		final String url = requestUrl.toASCIIString();

		HttpHeaders filtered = filterRequest(getHeadersFilters(), exchange);

		final DefaultHttpHeaders httpHeaders = new DefaultHttpHeaders();
		filtered.forEach(httpHeaders::set);
 
		Flux<HttpClientResponse> responseFlux = getHttpClient(route, exchange)
				.headers(headers -> {
					headers.add(httpHeaders);
					// Will either be set below, or later by Netty
					headers.remove(HttpHeaders.HOST);
					if (preserveHost) {
						String host = request.getHeaders().getFirst(HttpHeaders.HOST);
						headers.add(HttpHeaders.HOST, host);
					}
				}).request(method)
                    // 重新设置uri
                .uri(url).send((req, nettyOutbound) -> {
					// 省略。。。。
		return responseFlux.then(chain.filter(exchange));
	}


时序图:

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SpringGateway转发过程 的相关文章

随机推荐

  • Echarts 渐变色

    series i line itemStyle normal color Color Function default 自适应 图形的颜色 默认从全局调色盘 option color 获取颜色 颜色可以使用 RGB 表示 比如 rgb 12
  • Java技术栈,从入门到放弃,废了废了

    Java技术路线 应用框架 后端 Spring家族 Spring IoC AOP Spring MVC Spring Boot 自动配置 开箱即用 整合Web 整合数据库 事务问题 整合权限 Shiro Spring Security 整合
  • 开放集识别

    0 摘要 1 到目前为止 在计算机视觉中 几乎所有基于机器学习的识别算法的实验评估都采用了封闭集识别的形式 即在训练时已知所有测试类 对于视觉应用来说 一个更现实的场景是开放集识别 在训练时存在不完整的世界知识 在测试时未知的类可以提交给算
  • Vscode 打开文件注释中文乱码解决如下

    安装插件 ext install gbktoutf8 搜索encoding
  • 【LINUX计算机大白平凡学习linux之路】

    计算机大白平凡学习 之路 千里之行 始于足上 只有基础扎实 思路清析 写脚本才没有问题 多看一些牛人大咖写的脚本 看人家的思路与结构 会收益良多 一起努力学习吧 Linux是Torvalds先生所开发出来的 基于GPL的版权宣告之下 可以在
  • 神经网络优化(初始化权重)

    使隐藏层饱和了 跟之前我们说的输出层饱和问题相似 对于输出层 我们用改进的cost函数 比如cross entropy 但是对于隐藏层 我们无法通过cost函数来改进 更好的方法来初始化权重 因为传统的初始化权重问题是用标准正态分布 均值为
  • CentOS7.6 MySql 5.7.34安装部署

    一 卸载Mysql 查看系统是否安装默认的mysql rpm qa grep i mysql 如有 进行卸操作 rpm e nodeps 确认是否卸载完成 whereis mysql cd find name mysql cat etc p
  • 一种新型神经网络正在帮助物理学家应对数据分析的艰巨挑战

    来源 ScienceAI 本文约3000字 建议阅读5分钟 现在 在计算端 计算机科学往往处于领先地位 假设你有一本一千页的书 但每一页只有一行文字 你使用扫描仪提取书中包含的信息 这个特定的扫描仪系统地扫描每一页 一次扫描一平方英寸 要花
  • crmeb v4.3部署流程

    运行环境 CRMEB v4支持Lunix windows服务器环境 需要PHP7 1 7 3 版本支持 可运行于包括Apache和nginx在内的多种WEB服务器和模式 支持Mysql数据库 引擎用InnoDB 框架本身没有什么特别模块要求
  • Pyinstaller 打包.py生成.exe的方法和报错总结

    Pyinstaller 打包 py生成 exe的方法和报错总结 简介 有时候自己写了个python脚本觉得挺好用想要分享给小伙伴 但是每次都要帮他们的电脑装个python环境 虽然说装一下也快 但是相对来说效率还是不高 要是能将python
  • 【AI &Data Science】第 1 章分析性思维与 人工智能驱动的企业

    大家好 我是Sonhhxg 柒 希望你看完之后 能对你有所帮助 不足请指正 共同学习交流 个人主页 Sonhhxg 柒的博客 CSDN博客 欢迎各位 点赞 收藏 留言 系列专栏 机器学习 ML 自然语言处理 NLP 深度学习 DL fore
  • DHCP设置之起始地址与结束地址

    路由器设置ip地址 subnet mask dhcpstart dhcpend时 后台应该如何判断 get data ipstart websGetVar wp T start T ipend websGetVar wp T end T i
  • do while(0)的作用

    在嵌入式开发的过程中 我们经常可以在一些优秀开源代码的头文件里发现一些宏定义使用了do while 0 语句 也许你会疑惑do while 0 中的代码不就是只执行一次吗 为什么还要多此一举使用do while 0 循环结构去包裹呢 实际上
  • 卷积、池化、激励函数的顺序

    以下内容为个人的看法 顺序 卷积 池化 激励函数 我们知道卷积肯定是在第一层 毕竟 wx b wx b 就是卷积操作 那为什么池化要在激励函数之前呢 原因解析 假设激励函数是 relu 激励函数 并假设我们卷积后的值为 3 2 1 2 对于
  • 【总结】Typescript 结合Vue3的写法

    目录 前言 一 结合写法 1 ref 2 reactive 3 defineProps 4 defineEmits 5 computed 6 事件处理函数 7 html元素引用 8 组件实例 二 拓展补充 1 keyof 操作符 2 typ
  • 把代码做成笔记——Jupyter Notebook

    此文章首发于微信公众号Python for Finance 链接 https mp weixin qq com s KDCmpgwPbvrkRIuojtLpNg 什么是Jupyter Notebook Spyder Spyder代码编辑区
  • [算法]最大子串和

    题目描述 首先有一串数字 共有n个 从n个数中找到连续子序列和的最大值 解法一 暴力破解法 看到这个问题时第一时间想到的就是暴力破解法 遍历所有子序列 最终得到最大值 e g 1 2 3 4 5 6 共有六个数 所有组合为 1 2 3 4
  • Linux Shell编程之数组及参数传递

    1 Shell数组 Bash Shell 只支持一维数组 不支持多维数组 初始化时不需要定义数组大小 数组元素的下标由 0 开始 下标可以是整数或算术表达式 其值应大于或等于 0 1 1 定义数组 语法 数组名 值1 值2 值n 或者 数组
  • vue cli npm run build打生产环境包报错Cannot read property ‘pop‘ of undefined

    问题出在webpack配置的代码拆分splitChunks 解决办法 每个cacheGroups中配置enforce true
  • SpringGateway转发过程

    为什么写 就想看看springgateway的限流咋做的 但是看着看着就想知道转发过程 然后就写了 总之 转发是通过重组请求头header uri等信息建立netty客户端连接的访问过程 Lettuce相较于Jedis有哪些优缺点 Lett