内容简介:作者:亦山札记 ID:louluan_note笔者最近打算使用
作者:亦山札记 ID:louluan_note
0.前言
笔者最近打算使用 Sentinel
替换掉之前的 Hystrix
作为微服务架构的熔断/断路组件。整体上, Sentinel
的设计比 Hystrix
要易用很多。在实际使用的过程中,也存在了一些问题。本文将介绍Sentinel 在处理RESTful 风格的web项目过程中存在的问题。
问题描述 :
在Spring Cloud架构下,如果Http请求格式是按照RESTful风格设计的,当大规模的Http请求访问系统集群, Sentinel Dashboard
的 实时监控
和 簇点链路
的记录数非常多;看到的资源名已经飙到了 几千条之多
!另外虽然 资源名
数量庞大,但是监控的TPS和并发数却非常低,甚至很多资源名仅仅被访问过一次。想从这么多资源名中找到动态调控 TPS
或者 并发数
的 资源名
,是非常困难的。另外由于统计量级的问题,也会导致 sentinel
控制机器往 Sentinel Dashboard
传输的统计数据也非常大,整体请求下来,会导致整体服务的质量变得非常差。
1.RESTful接口问题重现:
1.1 在服务端定义一个RESTful风格的URL接口:
1.2 项目中引入 spring-cloud-starter-alibaba-sentinel
依赖
1.3 模拟客户端,请求Http服务,随机生成请求访问
1.4 在 Sentinel Dashboard
上的 簇点链路
和 实时跟踪
已经飚到上千个 资源名
问题分析 :
通过上述的例子中,可以看到, Sentinel
将每一个Http 请求的URL当成了一个唯一的 资源名
,用来做流控限制,这显然是非常不合适的。当大并发过来时,已经影响到了流控组件的性能消耗。
接下来我们将分析 sentinel
的工作机制,通过分析,来找到恰当的解决方案。
2. 当前Sentinel对于Web请求的处理原理
如果Spring Cloud 项目中,引入了 spring-cloud-starter-alibaba-sentinel
,那么该组件将自动创建一个拦截器,拦截所有的Http请求。当每一个请求进入系统后,该拦截器会获取到当前Http请求的 URL路径
,并将该 URL路径
作为 资源名
,用来做sentinel 基于资源名的流控操作。整体请求的的行为如下图所示:
Sentinel 对Web请求流控的抽象
工作流程大概如下:
-
Http请求经过sentinel自定义的拦截器
Sentinel CommonFilter
,该拦截器从请求中提取URL
, 然后将此当做流控资源名
; -
提取
流控资源名
之后,进入Sentinel 核心的代码段entry.enter
和entry.exit
包裹处理; -
包裹后,开始执行Spring MVC自身的HandlerMapping映射处理机制,从上下文中挑选合适的
HandlerMethod
,即某一个合适的Controller的@RequestMapping
方法上 -
执行Controller方法,返回结果给
Sentinel CommonFilter
,根据结果执行entry.exit
,完成单次资源访问控制逻辑
其拦截器的实现也非常简单,如下所示:
通过上面的流程来看,问题就出现在 Sentinel CommonFilter
上。那么,既然Request URL不适合做 资源名
,那什么适合做资源名控制呢?
3. RESTful 风格的请求,应当怎么定义 Sentinel
的资源名?
首先应当明确的是: 资源名
的选取,要具备实际的可控制的意义,按照上面所示的RESTful接口而言:
GET /api/v1/{tenantId}/order/{orderId}/basic
顾名思义,上述的URL表意是:获取某一个 租户
的某一个 订单
的基本信息,请求中存在两个PathVariable变量;
基于上述的定义,我们可以有如下几种方式挑选:
-
选取方式1 :上述的这个
RequestMapping
实际上是和对应的Controller的某一个方法是一一映射的,如果我们将资源名
的定义界定为对某一个类的某一个方法的调用时,我们就可以选取/api/v1/{tenantId}/order/{orderId}/basic
作为资源名。 这种维度是比较粗的,从定义上来看,我们限制的是当前系统内,对某一个Controller类的某一个方法的调用。 -
选取方式2 : 根据自己的需要,决定
PathVariable
的值是否可以作为资源创建
的参数 。实际上,如果定义成了方式1
的资源名,通过系统的角度上而言,我可以控制系统内对这个资源的访问;而不能细化到某一个特定租户
的请求访问;基于租户做流控限制这种需求很常见,假设在一个多租户的系统内,当某一个租户的请求猛增时,如果不加限制,可能会影响到其他租户的正常使用。所以从这个角度上,我希望的资源定义可以根据住户编号的不同,分别创建资源名
,如下所示:
资源名 | 解释 |
---|---|
/api/v1/ 12345678 /order/{orderId}/basic |
租户 12345678 的订单查询流控限制 |
/api/v1/ 88888888 /order/{orderId}/basic |
租户 88888888 的订单查询流控限制 |
/api/v1/ 99999999 /order/{orderId}/basic |
租户 99999999 的订单查询流控限制 |
-
选取方式3 : 完全使用
Pathvariable
的真实值来构造资源名
,这种方式的结果就和问题举例一样,会导致sentinel
性能极差。
上述三种方案,从产生的资源名数量来看 : 方式1
< 方式2
< 方式3
,所以我们的策略应该尽可能往 方式1
和 方式2
上靠。
另外,定义 资源名
时,应道考虑其 可流控性 ,像 /api/v1/99999999/order/3344455666/basic
这种查询某一个特定订单基本信息的请求,在实际的系统中,其并发访问实际上时非常低的,这样的就不具备 可流控性
,而 /api/v1/99999999/order/{orderId}/basic
请求,可以限定某一个的租户的所有查询订单请求,这个就具备 可流控性
4.如何在过滤器(Filter)层获取到当前请求对应的 HandlerMethod
?
为了实现上述的 方式1
、 方式2
,则需要有一个能力:需要在过 滤器层
就能知道当前 请求
应当被哪一个Controller的哪一个方法执行,即能够找到对应的 HandlerMethod
。而在目前的SpringMVC框架模式下,请求的调用关系是先经过拦截器,然后才能通过 DispatchServlet
的机制找到对应 HandlerMethod
,并处理。
那问题来了,怎么在 Filter
中提前感知到对应请求的 HandlerMethod
呢?
Spring MVC是如何为一个请求找对应的 HandlerMethod
的?
4.1 Spring MVC是如何为一个请求找对应的 HandlerMethod
的?
如下代码时 SpringMVC
的 DispatchServlet
核心实现逻辑。整体流程会包含如下几步:
-
步骤1:根据当前
请求
,查找HandlerExecutionChain,该对象内部包含HandlerMethod -
步骤2:根据当前HandlerExecutionChain 找到合适的HandlerAdaptor,用于处理请求
-
步骤3:调用HandlerAdaptor,处理请求
-
步骤4:返回ModelAndView
很明显,在 步骤1 的时候,我们已经可以获取到 HandlerExecutionChain
,该对象就包含了 HandlerMethod
。我们再来看这一步骤是怎么实现的:
到这个代码处,我们已经能够获取到合适的 HandlerMethod
了,我们可以在拦截器内,执行上面的这段核心逻辑代码 ,这样我们就能狗在拦截器初期获取到 HandlerMethod
了。
4.2 修改Filter,使其支持在拦截器内部可以获取到 HandlerMethod
对默认的Sentinel CommonFilter
进行拓展,仿照 DispatchServlet
的实现逻辑,获取 HandlerMethod
,然后根据反射机制,获取到对应的Controller 方法调用引用上声明的 @RequestMapping
注解,然后将注解内容组合成资源名,并返回。
4.3. 覆盖掉 spring-cloud-starter-alibaba-sentinel
的默认实现
想要覆盖掉默认的 spring-cloud-starter-alibaba-sentinel
实现,需要版本 >= 0.2.1.RELEASE,低版本不能通过配置的方式实现覆盖。
## 关闭默认实现 spring.cloud.sentinel.filter.enabled = false
自定义 Configuration
:
上述的变更,能够满足我们可以获取到Controller 方法上的注解表示作为 资源名
了。
4.4 优化结果展示
如下图所示,优化过的结果可以看到 大批量的URL请求已经成为 静态的资源名表示,和Controller注解上的@ReuqestMapping表示完全相同: /api/v1/{tenantId}/order/{orderId}/basic
实时监控
簇点链路
5. RESTful参数化进一步优化
上面的流程上,可以看到,我们已经将 RESTful
的PathVariable问题解决了。而实际上,我们可能是希望将部分的PathVariable替换掉,那我们应该怎么做?
如下图所示,如果希望可以根据特定的擦除规则,我们可以拓展一下 UrlCleaner
来实现这方面的定制,来完成对特定 PathVariable
的擦除:
通过这种改造方式,最终的效果如下图所示:
以上所述就是小编给大家介绍的《Alibaba Sentinel RESTful 接口流控处理优化》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 接口请求熔断处理机制
- axios封装以及前端接口处理策略
- 重构大型业务型写接口-并行处理注意点
- HQChart 1.9933 版本发布,增加网络异常处理接口
- cmdr 03 - 用流式接口定义命令行参数处理选项
- yii2 开发 api 接口时优雅的处理全局异常
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Design for Hackers
David Kadavy / Wiley / 2011-10-18 / USD 39.99
Discover the techniques behind beautiful design?by deconstructing designs to understand them The term ?hacker? has been redefined to consist of anyone who has an insatiable curiosity as to how thin......一起来看看 《Design for Hackers》 这本书的介绍吧!