随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
流量是非常随机性的、不可预测的。前一秒可能还风平浪静,后一秒可能就出现流量洪峰了(例如双十一零点的场景)。然而我们系统的容量总是有限的,如果突然而来的流量超过了系统的承受能力,就可能会导致请求处理不过来,堆积的请求处理缓慢,CPUILoad飙高,最后导致系统崩溃。因此,我们需要针对这种突发的流量来进行限制,在尽可能处理请求的同时来保障服务不被打垮,这就是流量控制。
一个服务常常会调用别的模块,可能是另外的一个远程服务、数据库,或者第三方API等。例如,支付的时候,可能需要远程调用银联提供的API;查询某个商品的价格,可能需要进行数据库查询。然而,这个被依赖服务的稳定性是不能保证的。如果依赖的服务出现了不稳定的情况,请求的响应时间变长,那么调用服务的方法的响应时间也会变长,线程会产生堆积,最终可能耗尽业务自身的线程池,服务本身也变得不可用。见下图:
现代微服务架构都是分布式的,由非常多的服务组成。不同服务之间相互调用,组成复杂的调用链路。以上的问题在链路调用中会产生放大的效果。复杂链路上的某一环不稳定,就可能会层层级联,最终导致整个链路都不可用。因此我们需要对不稳定的弱依赖服务进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。
在服务提供方( Service Provider )的场景下,我们需要保护服务提供方自身不被流量洪峰打垮。这时候通常根据服务提供方的服务能力进行流量控制,或针对特定的服务调用方进行限制。我们可以结合前期压测评估核心接口的承受能力,配置QPS模式的限流,当每秒的请求量超过设定的阈值时,会自动拒绝多余的请求。
为了避免调用其他服务时被不稳定的服务拖垮自身,我们需要在服务调用端( ServiceConsumer )对不稳定服务依赖进行隔离和熔断。
手段包括信号量隔离、异常比例降级、RT降级等多种手段。
当系统长期处于低水位的情况下,流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。这时候我们可以借助Sentinel 的 WarmUp 流控模式控制通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,而不是在一瞬间全部放行。这样可以给冷系统一个预热的时间,避免冷系统被压垮。
利用Sentinel的匀速排队模式进行“削峰填谷”,把请求突刺均摊到一段时间内,让系统负载保持在请求处理水位之内,同时尽可能地处理更多请求。
利用Sentinel 的网关流控特性,在网关入口处进行流量防护,或限制API的调用频率。
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>com.xl.projectsgroupId>
<artifactId>xl-springcloud-parent-pomartifactId>
<version>1.0.0version>
parent>
<artifactId>xl-sentinel-001artifactId>
<dependencies>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
dependencies>
project>
server:
port: 8086
spring:
application:
name: sentinel-001
cloud:
sentinel:
transport:
port: 8719
dashboard: localhost:8080
启动项目,刷新控制台,发现仍然是空白,这是因为Sentinel控制台采用的 懒加载 策略:需要访问一次项目,Sentinel才会出现对应的项目信息。
编写一个Controller
package com.xl.projects.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.xl.projects.service.SentinelService;
import com.xl.projects.service.impl.SentinelServiceImpl;
@RestController
public class SentinelController {
@Autowired
private SentinelService SentinelService;
@RequestMapping("/sentinel")
public String test() {
return "thisSentinel.";
}
@RequestMapping("/flow_control")
public String test1() {
return SentinelService.flowControl("thisIsFlowControl");
}
}
访问:http://localhost:8086/sentinel
再次查看Sentinel控制台
下面会通过项目分别演示上图中红框中的部分规则的配置和效果
什么是资源
资源是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容
,例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码。在接下来的文档中,我们都会用资源来描述代码块。
围绕资源的实时状态设定的规则,可以包括流量控制规则、熔断降级规则以及系统保护规则。所有规则可以动态实时调整。 在Sentinel控制台配置的内容。
参考官网:https://sentinelguard.io/zh-cn/docs/introduction.html
连续请求http://localhost:8086/sentinel地址后,实时监控界面就会显示刚才请求的一些信息:
定义资源
java代码硬编码方式(不推荐,略)
@SentinelResource注解方式(推荐):
@SentinelResource注解官方文档
在ServiceImpl实现类里面的加上注解@SentinelResource
重启项目,在Sentinel控制台查看,
什么是流量控制?
流量控制,原理是监控应用流量的 QPS 或 并发线程数 等指标,当达到指定阈值时对流量进行控制,避免系统被瞬时的流量高峰冲垮,保障应用高可用性。同一个资源可以创建多条限流规则,一条限流规则由以下属性组成:
① resource: 资源名,即限流规则的作用对象,默认请求路径。
② limitApp: 流控针对的调用来源,若为 default 则不区分调用来源,默认值default
③ count: 限流阈值
④ grade: 限流阈值类型(1代表 QPS,0 代表并发线程数),默认值QPS
QPS: 当 QPS 超过某个阈值的时候,则采取措施进行流量控制。
QPS,每秒请求数,即在不断向服务器发送请求的情况下,服务器每秒能够处理的请求数量。
并发线程数: 线程数限流用于保护业务线程数不被耗尽。例如,当应用所依赖的下游应用由于某种原因导致服务不稳定、响应延迟增加,对于调用者来说,意味着吞吐量下降和更多的线程数占用,极端情况下甚至导致线程池耗尽。为应对高线程占用的情况,业内有使用隔离的方案,
比如通过不同业务逻辑使用不同线程池来隔离业务自身之间的资源争抢(线程池隔离),或者使用信号量来控制同时请求的个数(信号量隔离)。
这种隔离方案虽然能够控制线程数量,但无法控制请求排队时间。当请求过多时排队也是无益的,直接拒绝能够迅速降低系统压力。
Sentinel线程数限流不负责创建和管理线程池,而是简单统计当前请求上下文的线程个数,如果超出阈值,新的请求会被立即拒绝。
⑤ strategy: 流控模式
直接拒绝(默认): 接口达到限流条件时,直接限流
关联: 当关联的资源达到阈值时,就限流自己(适合做应用让步):
当两个资源之间具有资源争抢或者依赖关系的时候,这两个资源便具有了关联。比如对数据库同一个字段的读操作和写操作存在争抢,读的速度过高会影响写得速度,写的速度过高会影响读的速度。如果放任读写操作争抢资源,则争抢本身带来的开销会降低整体的吞吐量。可使用关联限流来避免具有关联关系的资源之间过度的争抢,举例来说,read_db 和 write_db 这两个资源分别代表数据库读写,我们可以给 read_db 设置限流规则来达到写优先的目的:设置 FlowRule.strategy 为 RuleConstant.RELATE 同时设置 FlowRule.ref_identity 为 write_db。这样当写库操作过于频繁时,读数据的请求会被限流。
链路:
解释1:
只记录指定链路上的流量,指定资源从入口资源进来的流量,如果达到阈值,就可以限流。
解释2:
该流控模式针对资源链路上的接口进行限流,例如:A、B两个接口都调用某一资源C,A -> C、B -> C 可以看成两个简单的链路,此时可以针对C配置链路限流,比如限制A调用C,而B调用C则不受影响,它的功能有点类似于针对来源配置项,但链路流控是针对上级接口,它的粒度更细。
解释3:
NodeSelectorSlot 中记录了资源之间的调用链路,这些资源通过调用关系,相互之间构成一棵调用树。这棵树的根节点是一个名字为 machine-root 的虚拟节点,调用链的入口都是这个虚节点的子节点。
一棵典型的调用树如下图所示:
上图中来自入口 Entrance1 和 Entrance2 的请求都调用到了资源 NodeA,Sentinel 允许只根据某个入口的统计信息对资源限流。比如我们可以设置 FlowRule.strategy 为 RuleConstant.CHAIN,同时设置 FlowRule.ref_identity 为 Entrance1 来表示只有从入口 Entrance1 的调用才会记录到 NodeA 的限流统计当中,而对来自 Entrance2 的调用漠不关心。
调用链的入口是通过 API 方法 ContextUtil.enter(name) 定义的。
⑥ controlBehavior: 流控效果
快速失败(默认): 当 QPS 超过任意规则的阈值后,新的请求就会被立即拒绝,拒绝方式为抛出FlowException。这种方式适用于对系统处理能力确切已知的情况下,比如通过压测确定了系统的准确水位时
排队等待: 这种方式严格控制了请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。
注意:这一效果只针对QPS流控,并发线程数流控不支持。
Warm Up:
该方式主要用于系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮的情况。预热底层是根据令牌桶算法实现的。
注意:该方式只针对 QPS 流控,对并发线程数流控不支持
预热底层是根据令牌桶算法实现的,源码对应得类在 com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController 中,算法中有一个冷却因子coldFactor,默认值是3,即请求 QPS 从 threshold(阈值) / 3 开始,经预热时长逐渐升至设定的 QPS 阈值。
比如通过 sentinel-dashboard 设定 testWarmUP 资源的 QPS 阈值为,流控效果为 warm up,预热时长为5秒,如下图所示,testWarmUP 资源刚开始限流的阈值为 20/3=6,但经过10秒的预热后,慢慢将阈值升至20, 如下图所示:
以上几种属性在 sentinel-dashboard 控制台对应的规则如下图:
以上参考:
1、
Sentinel官网
2、
https://blog.csdn.net/m0_71777195/article/details/126460960
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>com.xl.projectsgroupId>
<artifactId>xl-springcloud-parent-pomartifactId>
<version>1.0.0version>
parent>
<artifactId>xl-sentinel-001artifactId>
<dependencies>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
dependencies>
project>
application.yml
server:
port: 8086
spring:
application:
name: sentinel-001
cloud:
sentinel:
transport:
port: 8719
dashboard: localhost:8080
web-context-unify: false
主启动类
package com.xl.projects;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Sentinel001Applicaiton {
public static void main(String[] args) {
SpringApplication.run(Sentinel001Applicaiton.class, args);
}
}
两个Controller
package com.xl.projects.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.xl.projects.service.SentinelService;
@RestController
public class FlowLimitController {
@Autowired
private SentinelService SentinelService;
@RequestMapping("/front_limit")
public String front() {
return "后面的流程达到了阈值,前面的流程front被关联限流了";
}
@RequestMapping("/rear_reach")
public String rear() {
return SentinelService.flowControl("thisIsFlowControl");
}
}
package com.xl.projects.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.xl.projects.service.SentinelService;
import com.xl.projects.service.impl.SentinelServiceImpl;
@RestController
public class SentinelController {
@Autowired
private SentinelService SentinelService;
@RequestMapping("/sentinel")
public String test() {
return "thisSentinel.";
}
@RequestMapping("/relative_source")
public String test2() {
return SentinelService.flowControl("this is entry 1 : config flow limit");
}
@RequestMapping("/flow_control")
public String test1() {
return SentinelService.flowControl("this is entry 2");
}
}
两个Service:一个接口,一个实现
package com.xl.projects.service;
public interface SentinelService {
String flowControl(String flowController);
}
package com.xl.projects.service.impl;
import java.util.Date;
import org.springframework.stereotype.Service;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.xl.projects.service.SentinelService;
import com.xl.projects.utils.ExceptionUtil;
@Service
public class SentinelServiceImpl implements SentinelService {
@Override
// @SentinelResource(value="service_flow_l",blockHandler="handleException",blockHandlerClass=ExceptionUtil.class)
@SentinelResource(value="service_flow_l")
public String flowControl(String flowController) {
System.out.println(System.currentTimeMillis()+ ":+++++++++"+Thread.currentThread().getName());
try {
Thread.currentThread().sleep(3000);
} catch (Exception e) {
// Do Nothing
}
return flowController;
}
public String handleException(String flowController, BlockException ex) {
return "this param is " +flowController+" ;Ops! Exception Occurs, ex: "+ ex.getMessage();
}
}
一个工具类:后面的@SentinelResource注解需要用到
package com.xl.projects.utils;
import com.alibaba.csp.sentinel.slots.block.BlockException;
public class ExceptionUtil {
public static String handleException (String flowController, BlockException ex) {
return "single alone CLASS for handling exception...";
}
}
定义,规则 一:QPS ---> 直接 ---> 快速失败
, 流控模式为: 直接定义,规则二:QPS ---> 关联 ---> 快速失败,
流控模式为: 关联
解释: 如果关联的资源 /relative_resource 的QPS超过了1 ,那么资源 /sentinel 将会被限流
资源
测试,使用 postman 的并发测试:
点击 《Run 并发测试》 之前,先看下访问资源 /sentinel的结果:
点击 《Run 并发测试》 之后,再看下访问资源 /sentinel的结果:
上图,符合关联资源的流量控制预期!
定义,规则三:QPS ---> 链路 ---> 快速失败,
流控模式为: 链路*入口1的链路:http://192.168.8.6:8086/relative_source ,访问被限流*
入口2的链路:http://localhost:8086/flow_control 访问正常
定义,规则四:并发线程数--->直接
; 流控模式为 “直接”定义:超过3个线程,资源/relative_source就会被限流。
资源
测试: 使用JMeter测试,配置为 1秒内发送10个线程,如下
注:用浏览器也可,模拟并发测试,快速的连续刷新即可,但还是没有JMeter好使
定义,规则五:并发线程数--->关联
;流控模式为关联演示:略,参考规则二
定义,规则六:并发线程数--->关联
;流控模式为链路演示:略,参考规则三
定义,规则七:QPS--->直接---> Warm Up
流控效果为 Warm Up
资源:配置 /sentinel 即可
测试
定义,规则八:QPS--->直接---> 排队等待
其他规则还有:
Sentinel 提供以下几种熔断策略:
请求的响应时间大于该值则统计为慢调用
。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值
,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。当单位统计时长内的异常数目超过阈值之后会自动进行熔断。
经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。注意! 异常降级仅针对业务异常,对 Sentinel 限流降级本身的异常(BlockException)不生效。
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 引入) |
测试
但是,熔断5s后,访问带参数的地址http://localhost:8086/slow?p=5 (这个不是慢调用),则不会重新开始熔断,一切正常。
略。注意!异常降级仅针对业务异常,对 Sentinel 限流降级本身的异常(BlockException)不生效。
略.注意!异常降级仅针对业务异常,对 Sentinel 限流降级本身的异常(BlockException)不生效。
TODO…
很多时候,我们需要根据调用方来限制资源是否通过,这时候可以使用 Sentinel 的黑白名单控制的功能。黑白名单根据资源的请求来源(origin)限制资源是否通过,若配置白名单则只有请求来源位于白名单内时才可通过;若配置黑名单则请求来源位于黑名单时不通过,其余的请求通过。
简单说,就是允许哪些客户端(调用方)访问,禁止哪些客户端(调用方)访问
黑白名单规则(AuthorityRule)非常简单,主要有以下配置项:
有个问题是:在这里配置的流控应用名称(limitApp), 在客户端(调用方)程序如何提供??
调用方需要在请求的Header里面添加一个字段用于存放应用的名称!
那么,对应的, 在Sentinel授权规则应用里面需要获取调用方请求中的应用名称!
1.Sentinel提供了如下接口
可以解析 请求的原始信息:IP、 用户、 应用名称,应为是个接口所以需要实现它:
package com.xl.projects.utils;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
import com.alibaba.cloud.commons.lang.StringUtils;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;
@Component
public class SentinelParseHeader implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest request) {
String origin = request.getHeader("appName");
if(StringUtils.isEmpty(origin))
origin = "blank";
return origin;
}
}
自定义异常:
参见: https://blog.csdn.net/weixin_43715214/article/details/128859907
遗留问题: 如果集成至Nacos直接使注册的服务名称当作limitApp,这样不知道是否可行,待验证。。。
TODO…
注意!阈值类型为 Load 时(仅对 Linux/Unix-like 机器生效)
TODO…
没搞明白
@SentinelResource 注解的使用
参考官网:
https://sentinelguard.io/zh-cn/docs/annotation-support.html
public class TestService {
// 对应的 `handleException` 函数需要位于 `ExceptionUtil` 类中,并且必须为 static 函数.
@SentinelResource(value = "test", blockHandler = "handleException", blockHandlerClass = {ExceptionUtil.class})
public void test() {
System.out.println("Test");
}
// 原函数
@SentinelResource(value = "hello", blockHandler = "exceptionHandler", fallback = "helloFallback")
public String hello(long s) {
return String.format("Hello at %d", s);
}
// Fallback 函数,函数签名与原函数一致或加一个 Throwable 类型的参数.
public String helloFallback(long s) {
return String.format("Halooooo %d", s);
}
// Block 异常处理函数,参数最后多一个 BlockException,其余与原函数一致.
public String exceptionHandler(long s, BlockException ex) {
// Do some log here.
ex.printStackTrace();
return "Oops, error occurred at " + s;
}
}
直接在 Sentinel 控制台配置的规则,重启应用后,所有的规则都消失了,这显然是不行的。 原因:Sentinel 控制台配置的规则是存在内存中的。
解决: 需要将文件持久化到 文件、数据库、注册中心等。
参考:
官网说明:动态规则扩展
有多种方案,下面演示其中一种:将规则持久化到Nacos注册中心
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>com.xl.projectsgroupId>
<artifactId>xl-springcloud-parent-pomartifactId>
<version>1.0.0version>
parent>
<artifactId>xl-sentinel-001artifactId>
<dependencies>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-datasource-nacosartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
dependencies>
project>
server:
port: 8086
spring:
application:
name: sentinel-001
cloud:
sentinel:
transport:
port: 8719
dashboard: localhost:8080
web-context-unify: false
datasource:
ds1:
nacos:
server-addr: localhost:8848
dataId: ${spring.application.name}-sentinel
groupId: DEFAULT_GROUP
rule-type: flow # flow代表“流控规则”,其他的规则可通过枚举类org.springframework.cloud.alibaba.sentinel.datasource.RuleType来查看
ds2:
nacos:
server-addr: localhost:8848
dataId: ${spring.application.name}-sentinel-degrade
groupId: DEFAULT_GROUP
rule-type: degrade # degrade代表“熔断规则”,其他的规则可通过枚举类org.springframework.cloud.alibaba.sentinel.datasource.RuleType来查看
先配置Nacos再修改application.yml文件,效果也是一样的;只要保证两个地方的配置是一致的,如dataId等
在Nacos上修改其中一个配置:流控规则, 在Sentinel中验证是否被动态加载了
重启后,规则仍然存在。验证成功!