# Sentinel:访问限流
回顾前面笔记中的 “关于 Sentinel 的使用方式” 章节,在这里,我们在服务的 “被调方” 使用 Sentinel 整合 Spring MVC 进行流量控制。
在这里,Sentinel 借助 Spring MVC 框架的 “拦截器” 机制整合进入 Spring MVC ,“抢先” 在 Controller 执行之前进行流控(和熔断)的判断,从而决定当前请求是否被放行至 Controller 。
# 1. Sentinel 和 Spring MVC 整合
添加依赖
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <!-- 其实真正起作用的是被关联引入的 sentinel-spring-webmvc-adapter 包 -->
添加配置(连接到 sentinel-dashboard)
spring: cloud: sentinel: transport: dashboard: 127.0.0.1:8858 # 日志设置 logging: level: root: INFO pattern: console: "${CONSOLE_LOG_PATTERN:\ %clr(${LOG_LEVEL_PATTERN:%5p}) \ %clr(|){faint} \ %clr(%-40.40logger{39}){cyan} \ %clr(:){faint} \ %m%n${LOG_EXCEPTION_CONVERSION_WORD:%wEx}}"
访问 sentinel-dashboard 可以看到类似如下页面:
只需要完成上述的配置,代码不需要有任何的调整,我们就可以通过实时监控查看服务内的流量 QPS 以及平均响应时长等信息。
警告
只有服务接口被访问的情况下,在 sentinel 里面才可以看到监控信息。这可能会让你『等』一段时间。
# 2. 几个概念和 name
上下文( Context )和 context-name
Context 代表调用链路上下文。在整个调用链路的开始处(即,前面章节所展示的 Sentinel 的执行流程的 ① 处),Sentinel 会创建上下文 Context 对象,并且为它指定一个 name 。
在 Sentinel 中,不同的调用链路可能使用同一个上下文 Context 对象。在这里( 和 Spring MVC 整合 ),我们的调用链路都是在 sentinel_spring_web_context 中:
资源(Resource)和 resource-name
在 Sentinel 中,被 Sentinel try-catch 起来的 “真·代码”( 即,前面章节中所展示的 Sentinel 的执行流程的 ③ 处 )在 Sentinel 看来就是所谓的 “资源”,对于每一份资源,Sentinel 会为赋予一个 name(或者你手动指定)
和 Spring MVC 整合时,Sentinel 使用的是 URI 来作为 Controller 方法的资源名( 在这里,Controller 方法就是资源 )。
# 3. 流控规则:QPS 限流
# 3.1 如何添加流控规则
在菜单左侧的 簇点链路
和流控规则都可以针对 服务接口
添加流控规则:
当我们的服务接口资源被访问的时候,就会出现在 簇点链路
列表中,我们可以针对该服务接口资源配置流程控制规则。
在流控规则页面也有 新增流控规则
按钮,添加完成之后的流控规则,出现在流控规则页面列表中。
# 3.2 QPS 流控
点击 新增流控规则
按钮之后,弹出如下的配置页面:
资源名称
表示我们针对哪个接口资源进行流控规则配置,如:
/departments/{id}
针对来源
表示针对哪一个服务访问当前接口资源的时候进行限流,default 表示不区分访问来源。
如填写服务名称:xxx-service,表示 xxx-service 访问前接口资源的时候进行限流,其他服务访问该接口资源的时候不限流。
阈值类型/单机阈值:QPS,每秒钟请求数量。上图配置表示每秒钟超过1次请求的时候进行限流。
流控模式
直接,当达到限流标准时就直接限流。
流控效果:
快速失败。很简单的说就是达到限流标准后,请求就被拦截,直接失败。(HTTP状态码:429 too many request)
是否集群:
默认情况下我们的限流策略都是针对单个服务的,sentinel 提供了集群限流的功能。
除非你的微服务规模特别大,一般不要使用集群模式。集群模式需要各节点与 token server 交互才可以,会增加网络交互次数,一定程度上会拖慢你的服务响应时间。
上面的限流规则用一句话说:对于任何来源的请求,当超过每秒 2 次的标准之后就直接限流,访问失败抛出 BlockException 异常!
# 4. 流控规则:线程数限流
资源名称
表示我们针对哪个接口资源进行流控规则配置,如:
/departments/{id}
针对来源
表示针对哪一个服务访问当前接口资源的时候进行限流,default 表示不区分访问来源。
如填写服务名称:xxxx-service ,表示 xxx-service 访问前接口资源的时候进行限流,其他服务访问该接口资源的时候不限流。
阈值类型/单机阈值
线程数。表示开启 n 个线程处理资源请求。
流控模式
直接,当所有线程都被占用时,新进来的请求就直接限流
流控效果
快速失败。很简单的说就是达到限流标准后,请求就被拦截,直接失败。(HTTP 状态码:429 too many request)
上面的限流规则用一句话说:对于任何来源的请求,department-service 服务端 /departments/{id}
资源接口的 2 个线程都被占用的时候,其他访问失败!
# 5. 关联限流
关联限流:/important
接口的重要程度要高于 /normal
接口,如果,/important
接口的访问压力很大,那么,可以『牺牲』掉 /normal
接口,全力保证 /important
接口的正常运行。
上述的配置的意思是,如果 /important
的访问压力达到了每秒 1 次,那么就对 /normal
就会被限流,把 /normal
接口停掉,以保证硬件资源全力供应 /important
服务。
注意
上述是对 /normal
的配置,不需要去配置 /important
即可生效,也就是说,/important
可以没有限流规则。
# 6. 链路限流
链路限流和关联限流的思路很像,假设运营认为 /important
接口的重要程度要高于 /normal
接口,而且,它俩如果又都调用了同一个 Service 的方法,那么,我们可以『站在 Service 的方法』的角度上进行设置:如果,是 /normal
接口在调用 Service 方法,那么就进行限流,而 /important
接口的调用就不限流,或设置为更宽松一些的流控。
在 Service 的方法上使用注解 @SentinelResource :
@SentinelResource("doSomething") public String doSomething() { return "hello world"; }
通过配置关闭 sentinel 的 URL 收敛功能:
spring: cloud: sentinel: web-context-unify: false
网上又资料显示该配置在低版本中无效,被认为是一个 bug ,不过现在高版本(spring-cloud-alibaba
2.2.2.RELEASE
)中修正了这个 bug 。接下来,你会在 Sentinel 中看到
/important
和/normal
两个 URI 下都有一个doSomething
。如下现在你可以在流控规则中为
doSomething
添加流控规则。注意,是doSomething
,而不是/important
或/normal
。分别触发
/important
和/normal
,你会发现/normal
有流量限制,而/important
则没有。
# 7. 流控效果
快速失败
直接失败,抛出异常,不做任何额外的处理,是最简单、最直接、最常用的效果。
Warm up
它从开始阈值到最大 QPS 阈值会有一个缓冲阶段,一开始的阈值是最大 QPS 阈值的 1/3 ,然后慢慢增长,直到达到最大阈值。
排队等待
让请求以均匀的速度通过,单机阈值为每秒通过数量,其余的排序等待。即,如果单机阈值为 N,那么没 1/N 秒有一个访问『资格』。没有『资格』时,排队等待,直到超时。
# 8. Sentinel 和 SpringMVC 整合原理
回顾一下我们之前所展示的 Sentinel 的底层执行流程:
1. 初始化上下文;
try {
2. 熔断、流控逻辑的判断,判断当前请求是否能继续执行;
3. 执行 “真·代码”;
} catch (BlockException e) {
4. 上述第 2 步未能通过,会抛出 BlockException ,表示请求被拒绝
return;
} catch (Exception e) {
5. 业务异常。记录、统计异常信息
throw e;
} finally {
6. 收尾工作:曾经创建的资源该回收的回收,该清除的清除
}
之前有提到过,Sentinel 和 Spring MVC 的整合利用了 Spring MVC 的拦截器机制。
Sentinel 实现了一个名为 SentinelWebInterceptor 的拦截器,其逻辑伪代码如下:
public SentinelWebInterceptor implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
try {
1. 初始化上下文;
2. 熔断、流控逻辑的判断,判断当前请求是否能继续执行;
return true; // 此时 Controller 方法会被调用。Controller 方法就是 3 。
} catch (BlockException e) {
4. 上述第 2 步未能通过,会抛出 BlockException ,表示请求被拒绝
return false;
}
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
if (发生了异常) {
5. 业务异常。记录、统计异常信息
}
6. 收尾工作:曾经创建的资源该回收的回收,该清除的清除
}
# 9. 自定义熔断返回信息
Sentinel 返回的默认信息是 Blocked by Sentinel (flow limiting)
,如果你对默认信息不满意,你可以自定义熔断返回信息。
Sentinel 提供了 BlockExceptionHandler 接口。当无论因何原因触发了 Sentinel 阻断用户的正常请求,Sentinel 都将『进入』到用户自定义的 BlockExceptionHandler 接口的实现类中,执行 handle 方法,并传入当前的请求、响应对象以及异常对象,并以 handle 方法的执行结果作为返回,回传给用户。
通过对 handle 方法的异常参数的判断,你可以直到当前发生了什么状况:
@Component
public class MyBlockExceptionHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, BlockException ex) throws Exception {
String msg = null;
if (ex instanceof FlowException) {
msg = "限流了";
} else if (ex instanceof DegradeException) {
msg = "熔断了";
} else {
msg = "其它原因";
// ParamFlowException "热点参数限流";
// SystemBlockException "系统规则(负载/...不满足要求)";
// AuthorityException "授权规则不通过";
}
// http 状态码
response.setStatus(500);
response.setCharacterEncoding("utf-8");
response.setHeader("Content-Type", "application/json;charset=utf-8");
response.setContentType("application/json;charset=utf-8");
// 利用 spring mvc 默认的 json 库 jackson
new ObjectMapper().writeValue(response.getWriter(), msg);
}
}
需要说明的是:不止因为熔断这一个原因会导致 BlockExceptionhandler 的 handle 方法的执行,因此,需要对 handle 方法的 BlockException 参数对象进行 instanceof 判断,熔断对应的异常类型正是 DegradeException 。