作者:手机用户2602926791 | 来源:互联网 | 2023-02-04 11:26
为了进一步增强网络安全防备工作,近期对网关服务做了相干的平安降级,其中变动最大的一点是,网关不再提供URI含糊匹配的模式,形如apiv1apporder**这样的配置曾经不在反对,置信很多小伙伴曾经感触到了日常开发上线的不便,然而须要了解的是,随着公司的体量的迅速倒退,各方面越来越规范化,平安方面增强管控显
为了进一步增强网络安全防备工作,近期对网关服务做了相干的平安降级,其中变动最大的一点是,网关不再提供URI含糊匹配的模式,形如 /api/v1/app/order/** 这样的配置曾经不在反对,置信很多小伙伴曾经感触到了日常开发上线的不便,然而须要了解的是,随着公司的体量的迅速倒退,各方面越来越规范化,平安方面增强管控显然还是十分必要的。
首先看下得物流量传递的根本门路:
APP网关流量门路:四层高防 –> 阿里云SLB –> Gateway –> 业务服务/业务网关(提供协定转化 &接口聚合)
通常来说与业务方打交道的最多的是gateway 服务,很多萌新可能不是很了解网关具体在干啥,这里做个简要阐明,网关最大的作用是提供流量散发,同时具备流量管控,防爬,黑白名单,根本鉴权,接口超时,灰度等常见性能;小伙伴们日常开发中最长用到的就是流量转发,比方新起一个服务须要对外网裸露接口,此时就须要在网关的路由治理上进行配置。
所以Spring gateway 的路由匹配就成了一个十分外围的要害性能,这里咱们翻阅一下 Spring gateway 的源码。
因为Spring gateway 应用webflux 技术,整体的代码格调较为诡异。
这里简略介绍下webflux的基本概念:
flux 示意 1~N 数据元素
mono ,示意 0~1 个数据元素,
针对数据流的所有操作,在没有订阅之前都不会被触发,只有调用了 subscribe办法后才会理论触发。
图一*
这里咱们看下 DispatcherHandler 的handle 办法,该办法会进行webHandler 的适配,对于网关来说这里次要匹配的是RoutePredicateHandlerMapping 这一对象,咱们能够从 hadlerMappings 对象中看到:
RoutePredicateHandlerMapping 中的webHandle为 FilteringWebHandler 该handle 中蕴含了gateway 自带的以及网关自定义的共28个 GolobalFilter
讲到这里很多小伙伴可能会好奇,这个路由匹配到底是在哪儿做的呢,别急,咱们缓缓开趴!
依照图一所示,选中mapping后会获取Handler,而获取handler 后优会调用 invokeHandler 办法,那么我么无妨先到 getHandler 办法中看看,点开 RoutePredicateHandlerMapping 源码,咱们郁闷的发现并没有 getHandler 办法,而只有getHandlerInternal 办法,认真看下 RoutePredicateHandlerMapping 的继承关系发现该类继承了 AbstractHandlerMapping, 而AbstractHandlerMapping 中 getHandler 办法早已存在,实现了HandlerMapping 接口同时也做了局部实现 ;废话不多说,源码底下无内鬼!!
原来getHandlerInternal 是在 getHandler 办法中被调用的。这就解释得通了,
仔细观察了 getHandler 中的逻辑,并没有路由匹配的逻辑,此时嫌疑最大的当属 RoutePredicateHandlerMapping 的 getHandlerInternal 了!
不出所料,lookupRoute 没跑了!!
lookupRoute的代码很简略,外围逻辑为简略的匹配,同时增加错误处理,在匹配胜利的状况下会把路由信息增加到 ServerWebExchange 中的attributes 中,代码如下:
察看filterWhen,咱们会发现这是一个for循环匹配,也就是说,效率为O n, 在路由信息比拟多的状况下十分蹩脚,当然这不能怪Spring,毕竟gateway 设计之初,是反对各种正则,含糊匹配的,这种要求下,做到O 1的效率并不事实 ,然而联合得物以后的应用场景,咱们能够做进一步的优化:
因为新的路由增加为准确模式,也就是每个接口对应一个路由,这种前提下,咱们很显然的想到了HASH 算法,因为对于pathVariable模式的path也不再反对(小伙伴们能够思考下,这种接口有什么毛病) ,在收到申请的时候间接提取path局部,通过hash的形式获取到对应的路由信息,革新后的路由查找逻辑如下所示:
findRoute()办法中的逻辑非常简单的:
为了保障并发平安,这里的pathRouteMap 为 ConcurrentHashMap ,其实批改为HashMap 也是能够的,因为路由匹配时,对map是只读操作,更新时候是整体map 援用替换:这里附上刷新路由缓存信息的代码
因为更新路由信息的操作属于高危且外围的操作,对于一个批次的更新最好可能原子性实现,这里咱们引入了Copy on write 的思路,批改的时候,先批改bakMap ,等到bakMap中的全副路由信息更新实现后,咱们将理论应用的map 援用指向bakMap, 同时将bakMap设置为空。此外更新路由的操作一般来说都是事件触发异步实现,因而对于性能要求并不高,这里加上锁进一步保障路由更新完整性,避免在多个线程调用时,map与bakMap之间呈现不匹配的状况!
须要指出的是 gateway 的路由查找逻辑依赖于 CachingRouteLocator, 该类监听路由更新事件,理论的路由刷新通过公布事件的形式实现。察看源码,咱们发现解决路由刷新事件时调用了fetch 办法;
同时在初始化阶段以及缓存命中生效阶段时也调用了 fetch办法(这里缓存是gateway自带的缓存机制,而非咱们增加的Map缓存)
因而咱们能够在fetch 办法中退出 refreshPathRouteMap() 办法;
在lookupRoute 办法中的 this.routeLocator.getRoutes() 理论调用的是CachingRouteLocator#getRoutes()办法。此办法间接返回被缓存的的信息,这里的缓存指的是 gateway 自带的
routes = CacheFlux.lookup(cache, CACHE_KEY, Route.class).onCacheMissResume(this::fetch);
逻辑简略翻译一下,就是如果缓存命中失败会调用fetch办法从新加载路由信息。
至此,路由匹配的逻辑大抵剖析实现!其实对于之前的 /api/v1/app/order/* 这种模式的路由也能够通过hash形式进行减速,只须要将 * 去掉,作为map的key,在解决申请的时候,尝试获取申请的前缀进行匹配即可!
最终咱们实战察看一下改良的实际效果:
能够发现,理论的CPU占用从原来的均匀24% –> 12% ,比原先降落了一半左右!
文/簌语
关注得物技术,携手走向技术的云端