一、流控规则
流量控制(flow control),其原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。一条限流规则主要由下面几个因素组成,我们可以组合这些元素来实现不同的限流效果:
Field | 说明 | 默认值 |
---|---|---|
resource | 资源名,即限流规则的作用对象 | |
count | 限流阈值 | |
grade | 限流阈值类型(QPS 或并发线程数) | QPS 模式 |
limitApp | 流控针对的调用来源 | default,代表不区分调用来源 |
strategy | 调用关系限流策略:直接、链路、关联 | 直接 |
controlBehavior | 流量控制效果(直接拒绝、Warm Up、匀速排队) | 直接拒绝 |
clusterMode | 是否集群限流 | 否 |
流量控制,其原理是监控应用流量的QPS(每秒查询率) 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。
第1步: 点击【簇点链路】,我们就可以看到访问过的接口地址,然后点击对应的【流控】按钮,进入流控规则配置页面。新增流控规则界面如下:
-
资源名:唯一名称,默认是请求路径,可自定义。
-
针对来源:Sentinel可以针对调用者进行限流,填写【微服务名】,指定对哪个微服务进行限流 ,默认default(不区分来源,全部限制)。
-
阈值类型/单机阈值:
- QPS(每秒请求数量): 当调用该接口的QPS达到阈值的时候,进行限流。
- 线程数:当调用该接口的线程数达到阈值的时候,进行限流。
-
是否集群:暂不需要集群
接下来我们以QPS为例来研究限流规则的配置。
1. QPS配置
- 编写QPS代码
/*** QPS测试* @return*/@GetMapping("QPSTest")public String QPSTest(){return "QPSTestSuccess";}
-
配置规则:
-
快速访问 QPSTest 接口,观察效果。
此时发现,当QPS > 1的时候,服务就不能正常响应,而是返回Blocked by Sentinel (flow limiting)结果。
2. 线程数配置
并发数控制用于保护业务线程池不被慢调用耗尽。例如,当应用所依赖的下游应用由于某种原因导致服务不稳定、响应延迟增加,对于调用者来说,意味着吞吐量下降和更多的线程数占用,极端情况下甚至导致线程池耗尽。
【为应对太多线程占用的情况,业内有使用隔离的方案,比如通过不同业务逻辑使用不同线程池来隔离业务自身之间的资源争抢(线程池隔离)。这种隔离方案虽然隔离性比较好,但是代价就是线程数目太多,线程上下文切换的 overhead 比较大,特别是对低延时的调用有比较大的影响。】
Sentinel 并发控制不负责创建和管理线程池,而是简单统计当前请求上下文的线程数目(正在执行的调用数目),如果超出阈值,新的请求会被立即拒绝,效果类似于信号量隔离。并发数控制通常在调用端进行配置。
-
编写java代码
/*** 线程数测试* @return*/@GetMapping("threadTest")public String threadTest(){return "threadTest";}
-
定义流控规则
-
快速访问观察效果
3 流控模式
点击上面设置流控规则的**【编辑】按钮,然后在编辑页面点击【高级选项】**,会看到有流控模式一栏。
sentinel共有三种流控模式,分别是:
- 直接(默认):指定来源对于该资源的访问达到限流条件时,开启限流。
- 关联:当与该资源设置了关联的资源达到限流条件(来源+阈值类型+单机阈值)时,开启限流 [适合做应用让步]
- 链路:当从某个上游资源接口访问过来的流量达到限流条件时,开启限流。
下面呢分别演示三种模式:
3.1 直接流控模式
直接流控模式是最简单的模式,当指定的接口达到限流条件时开启限流。上面案例使用的就是直接流控模式。
3.2 关联
当两个资源之间具有资源争抢或者依赖关系的时候,这两个资源便具有了关联。比如对数据库同一个字段的读操作和写操作存在争抢,读的速度过高会影响写得速度,写的速度过高会影响读的速度。如果放任读写操作争抢资源,则争抢本身带来的开销会降低整体的吞吐量。可使用关联限流来避免具有关联关系的资源之间过度的争抢,举例来说,read_db
和 write_db
这两个资源分别代表数据库读写,我们可以给 read_db
设置限流规则来达到写优先的目的:设置 strategy
为 RuleConstant.STRATEGY_RELATE
同时设置 refResource
为 write_db
。这样当写库操作过于频繁时,读数据的请求会被限流。
规则:
代码:
@GetMapping("wirteDB")public String wirteDB() throws InterruptedException {return "wirteDBsuccess";}@GetMapping("readDB")public String readDB() throws InterruptedException {return "readDBsuccess";}
演示一下 当 wirteDB写资源 大于单机阈值,我们再去请求 读资源 就直接给我们限流了
。
3.3 链路
链路流控模式指的是,当从某个接口过来的资源达到限流条件时,开启限流。它的功能有点类似于针对 来源配置项,区别在于:针对来源是针对上级微服务,而链路流控是针对上级接口,也就是说它的粒度更细。
规则:
-
编写UserService
@SentinelResource(value = "getUser",fallback = "fallback", fallbackClass = CommonException.class,blockHandler = "handleException", blockHandlerClass = CommonException.class ) public String getUser() {return "获取到用户信息"; } // --------------------------------编写异常信息/*** 注意 SentinelResource 注解 如果你的方法 无参数 那么就会找 无参数的 有参数的就找 有参数* 必须要保证你的 回调异常 和 方法的参数类型 都一样* @param e* @return*/ public static String handleException(BlockException e) {e.printStackTrace();log.error("触发限流机制");return "====触发限流机制=="; }public static String fallback(Throwable e) {e.printStackTrace();log.error("出现业务异常");return "===业务异常=="; }
-
编写controller信息
// ===================================================链路模式限流@GetMapping("/linkModel/test1") public String test1() {String serviceUser = userService.getUser();return serviceUser; }@GetMapping("/linkModel/test2") public String test2() {String userServiceUser = userService.getUser();return userServiceUser; }
-
配置文件修改配置
-
测试
test1接口
test2接口
我们可以看到当调用 几次 test1 接口就限流了,而test2 不限流。
4. 流控效果
4.1 快速失败
当QPS超过任意规则的阈值后,新的请求就会被立即拒绝,拒绝方式为抛出FlowException。
这种方式适用于对系统处理能力确切已知的情况下,比如通过压测确定了系统的准确水位时。
4.2 Warm Up
它从开始阈值到最大QPS阈值会有一个缓冲阶段,一开始的阈值是最大QPS阈值的1/3,然后慢慢增长,直到最大阈值。
即预热/冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。
冷加载因子: codeFactor 默认是3,即请求 QPS 从 threshold / 3 开始,经预热时长逐渐升至设定的 QPS 阈值。
通常冷启动的过程系统允许通过的 QPS 曲线如下图所示:
规则:
结果图:
查看你会发现首先会达到3 (阈值的1/3)然后等3秒后会达到阈值10
- 代码
// ================================================= 预热@RequestMapping("/warmup")public String testWarmUp(){try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}return "测试流控效果===热启动";}
4.3 排队等待
深刻理解下面这句话
让请求以均匀的速度通过,单机阈值为每秒通过数量,其余的排队等待; 它还会让设置一个超时时间,当请求超过超时间时间还未处理,则会被丢弃。
该方式的作用如下图所示:
这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。
注意:匀速排队模式暂时不支持 QPS > 1000 的场景。
【因为他的单位毫秒,所以最多是1秒通过一个,也就是1000qps】
-
规则:
-
java代码
@GetMapping("/rateLimiter")public String testRateLimiter(){try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("通过了一次请求 = " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));return "测试流控效果===匀速排队";}
3.效果图
这个是按1秒来算,qps超了 1000 ,就不生效了。。
二、降级规则
除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。一个服务常常会调用别的模块,可能是另外的一个远程服务、数据库,或者第三方 API 等。例如,支付的时候,可能需要远程调用银联提供的 API;查询某个商品的价格,可能需要进行数据库查询。然而,这个被依赖服务的稳定性是不能保证的。如果依赖的服务出现了不稳定的情况,请求的响应时间变长,那么调用服务的方法的响应时间也会变长,线程会产生堆积,最终可能耗尽业务自身的线程池,服务本身也变得不可用。
熔断降级规则说明熔断降级规则(DegradeRule)包含下面几个重要的属性:
Field | 说明 | 默认值 |
---|---|---|
resource | 资源名,即规则的作用对象 | |
grade | 熔断策略,支持慢调用比例/异常比例/异常数策略 | 慢调用比例 |
count | 最小请求数,慢调用比例模式下为慢调用临界 RT(超出该值计为慢调用);异常比例/异常数模式下为对应的阈值 | |
timeWindow | 熔断时长,单位为 s | |
minRequestAmount | 最小请求数,熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断(1.7.0 引入) | 5 |
statIntervalMs | 统计时长,(单位为 ms),如 60*1000 代表分钟级(1.8.0 引入) | 1000 ms |
slowRatioThreshold | 比例阈值,慢调用比例阈值,仅慢调用比例模式有效(1.8.0 引入) |
降级规则就是设置当满足什么条件的时候,对服务进行降级。Sentinel提供了三个衡量条件:平均响应时间、异常比例、异常数。
1.慢调用比例
慢调用比例 (SLOW_REQUEST_RATIO
):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间)
,请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs
)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
-
规则:
-
java代码
// ======================================================慢调用比例@RequestMapping("/slowRequestRatio")public String slowRequestRatio(){try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}return "熔断降级==慢调用比例";}
-
效果图
可以看到它熔断了3秒,然后尝试调用了1次,然后又触发
2.异常比例
当单位统计时长(statIntervalMs
)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0]
,代表 0% - 100%。
- 规则:
3.异常数
当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
注意:异常降级仅针对业务异常,对 Sentinel 限流降级本身的异常(BlockException
)不生效。
-
规则:
-
java代码
@GetMapping("abnormalProportion")public String abnormalProportion() throws Exception {int i = atomicInteger.incrementAndGet();if (i % 2 == 0) {//模拟异常和异常比率int a = 1 / 0;}System.out.println("通过了一次请求 = " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));return "success";}
-
效果图
这个异常数 和 异常比例 不是很准确。
三、热点规则
热点参数流控规则是一种更细粒度的流控规则, 它允许将规则具体到参数上。
何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:
- 商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制
- 用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
注意:
- 热点规则需要使用@SentinelResource(“resourceName”)注解,否则不生效
- 参数必须是7种基本数据类型才会生效
规则:
注意: 资源名必须是@SentinelResource(value=“资源名”)中 配置的资源名,热点规则依赖于注解
这里有个坑,经过上面的配置只是对接口的限流,如果具体到参数需要再热点规则中重新设置参数
具体到参数值限流,配置参数值为1,限流阈值为2
效果图:
四、系统规则
系统保护规则是从应用级别的入口流量进行控制,从单台机器的 load、CPU 使用率、平均 RT、入口 QPS 和并发线程数等几个维度监控应用指标,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效。入口流量指的是进入应用的流量(EntryType.IN
),比如 Web 服务或 Dubbo 服务端接收的请求,都属于入口流量。
系统规则支持以下的模式:
- Load 自适应(仅对 Linux/Unix-like 机器生效):系统的 load1 作为启发指标,进行自适应系统保护。当系统 load1 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的
maxQps * minRt
估算得出。设定参考值一般是CPU cores * 2.5
。 - CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.0-1.0),比较灵敏。
- 平均 RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
- 并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
- 入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
这些不是很好测试,我们可以此时一下QPS
-
规则:
3. java代码:
@RequestMapping("/systemRule")public Result systemRule(){return Result.ok("测试系统规则QPS");}
五、授权规则
很多时候,我们需要根据调用来源来判断该次请求是否允许放行,这时候可以使用 Sentinel 的来源访问控制的功能。来源访问控制根据资源的请求来源(origin)限制资源是否通过:
- 若配置白名单,则只有请求来源位于白名单内时才可通过;
- 若配置黑名单,则请求来源位于黑名单时不通过,其余的请求通过。
例如:活动和订单都会调用用户系统获取用户信息,我们可以将活动设置为黑名单。
规则:
-
在我们的 user-service 服务里面添加 对应的 方法
@GetMapping("getUserInfo") public String getUserInfo(){return "userinfo - 1111"; }
-
实现 sentinel的一个接口,接口的返回值,就是请求到你这个服务来,你把请求的名称返回给他
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser; import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest;@Component public class RequestOriginParserDefinition implements RequestOriginParser {@Overridepublic String parseOrigin(HttpServletRequest request) {// 从请求头里面 获取到 服务名称 返回给 sentinelString applicationName = request.getHeader("applicationName");return applicationName;} }
-
编写请求的服务,我们在 cloud-sentincl服务 调用 user-service服务
// ============================================= 授权规则@GetMapping("getUserInfo")public String getUserInfo() {return userFeign.getUserInfo();}
-
编写 fegin拦截器,添加自定义的请求头信息
public class FeignInterceptor implements RequestInterceptor {private String applicationName;public FeignInterceptor(String applicationName) {this.applicationName = applicationName;}@Overridepublic void apply(RequestTemplate requestTemplate) {//在请求头添加 一个信息requestTemplate.header("applicationName", applicationName);}public String getApplicationName() {return applicationName;} }
-
将上面的类 交给spring进行管理
@Value("${spring.application.name}")public String applicationName;@Beanpublic RequestInterceptor feignInterceptor() {return new FeignInterceptor(applicationName);}
-
结果:
给我们限流了