随着微服务的流行,服务和服务之间的稳定性变得越来越重要。
一、Sentinel 能干什么
Sentinel 是面向分布式服务架构的轻量级流量控制产品,主要以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度来帮助您保护服务的稳定性。
二、Sentinel 基本概念
2.1 资源
资源是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容,例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码。在接下来的文档中,我们都会用资源来描述代码块。
只要通过 Sentinel API 定义的代码,就是资源,能够被 Sentinel 保护起来。大部分情况下,可以使用方法签名,URL,甚至服务名称作为资源名来标示资源。
2.2 规则
围绕资源的实时状态设定的规则,可以包括流量控制规则、熔断降级规则以及系统保护规则。所有规则可以动态实时调整。
三、Sentinel 是如何工作的
Sentinel 的主要工作机制如下:
对主流框架提供适配或者显示的 API,来定义需要保护的资源,并提供设施对资源进行实时统计和调用链路分析。
根据预设的规则,结合对资源的实时统计信息,对流量进行控制。同时,Sentinel 提供开放的接口,方便您定义及改变规则。
Sentinel 提供实时的监控系统,方便您快速了解目前系统的状态。
四、Sentinel 功能和设计理念
4.1 流量控制
什么是流量控制
流量控制在网络传输中是一个常用的概念,它用于调整网络包的发送数据。然而,从系统稳定性角度考虑,在处理请求的速度上,也有非常多的讲究。任意时间到来的请求往往是随机不可控的,而系统的处理能力是有限的。我们需要根据系统的处理能力对流量进行控制。Sentinel 作为一个调配器,可以根据需要把随机的请求调整成合适的形状,如下图所示:
流量控制设计理念
流量控制有以下几个角度:
- 资源的调用关系,例如资源的调用链路,资源和资源之间的关系;
- 运行指标,例如 QPS、线程池、系统负载等;
- 控制的效果,例如直接限流、冷启动、排队等。
Sentinel 的设计理念是让您自由选择控制的角度,并进行灵活组合,从而达到想要的效果。
4.2 熔断降级
什么是熔断降级
除了流量控制以外,降低调用链路中的不稳定资源也是 Sentinel 的使命之一。由于调用关系的复杂性,如果调用链路中的某个资源出现了不稳定,最终会导致请求发生堆积。这个问题和 Hystrix 里面描述的问题是一样的。
Sentinel 和 Hystrix 的原则是一致的: 当调用链路中某个资源出现不稳定,例如,表现为 timeout,异常比例升高的时候,则对这个资源的调用进行限制,并让请求快速失败,避免影响到其它的资源,最终产生雪崩的效果。
熔断降级设计理念
在限制的手段上,Sentinel 和 Hystrix 采取了完全不一样的方法。
Hystrix 通过线程池的方式,来对依赖(在我们的概念中对应资源)进行了隔离。这样做的好处是资源和资源之间做到了最彻底的隔离。缺点是除了增加了线程切换的成本,还需要预先给各个资源做线程池大小的分配。
Sentinel 对这个问题采取了两种手段:
通过并发线程数进行限制
和资源池隔离的方法不同,Sentinel 通过限制资源并发线程的数量,来减少不稳定资源对其它资源的影响。这样不但没有线程切换的损耗,也不需要您预先分配线程池的大小。当某个资源出现不稳定的情况下,例如响应时间变长,对资源的直接影响就是会造成线程数的逐步堆积。当线程数在特定资源上堆积到一定的数量之后,对该资源的新请求就会被拒绝。堆积的线程完成任务后才开始继续接收请求。通过响应时间对资源进行降级
除了对并发线程数进行控制以外,Sentinel 还可以通过响应时间来快速降级不稳定的资源。当依赖的资源出现响应时间过长后,所有对该资源的访问都会被直接拒绝,直到过了指定的时间窗口之后才重新恢复。
4.3 系统负载保护
Sentinel 同时对系统的维度提供保护。防止雪崩,是系统防护中重要的一环。当系统负载较高的时候,如果还持续让请求进入,可能会导致系统崩溃,无法响应。在集群环境下,网络负载均衡会把本应这台机器承载的流量转发到其它的机器上去。如果这个时候其它的机器也处在一个边缘状态的时候,这个增加的流量就会导致这台机器也崩溃,最后导致整个集群不可用。
针对这个情况,Sentinel 提供了对应的保护机制,让系统的入口流量和系统的负载达到一个平衡,保证系统在能力范围之内处理最多的请求。
五、启动 Sentinel 控制台
5.1 获取 Sentinel 控制台
支持直接下载和源码构建两种方式:
1. 直接下载:下载 Sentinel 控制台,注:这个官方下载不能正常启动,并且版本较低。使用百度网盘下载。
2. 源码构建:进入 Sentinel Github 项目页面,将代码 git clone 到本地自行编译打包,参考此文档。
5.2 启动控制台
执行 Java 命令 java -jar sentinel-dashboard.jar
完成 Sentinel 控制台的启动。 控制台默认的监听端口为 8080。Sentinel 控制台使用 Spring Boot 编程模型开发,如果需要指定其他端口,请使用 Spring Boot 容器配置的标准方式,详情请参考 Spring Boot 文档。
写了一个批处理文件来启动自定义端口为8080
的Sentinel
控制台,该文件内容:
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -jar D:\backup\software\sentinel-dashboard-1.6.3.jar
-Dserver.port=8080 控制台端口,sentinel控制台是一个spring boot程序。客户端配置文件需要填对应的配置。
-Dcsp.sentinel.dashboard.server=localhost:8080 控制台的地址,指定控制台后客户端会自动向该地址发送心跳包。
-Dproject.name=sentinel-dashboard 指定Sentinel控制台程序的名称
-Dcsp.sentinel.api.port=8719 (默认8719) 客户端提供给Dashboard访问或者查看Sentinel的运行访问的参数
注:
- csp.sentinel.dashboard.server这个配置是用在客户端,这里Sentinel控制台也使用是用于自己监控自己程序的api,否则无法显示控制台的api情况,当然这个也可以根据情况不显示。
- csp.sentinel.api.port=8719是客户端的端口,需要把客户端设置的端口穿透防火墙,可在控制台的“机器列表”中查看到端口号,这里Sentinel控制台也使用是用于自己程序的api传输,由于是默认端口所以控制台也可以不设置。
- 客户端需向控制台提供端口,配置文件配置,如:spring.cloud.sentinel.transport.port=8720
从 Sentinel 1.6.0 起,Sentinel 控制台引入基本的登录功能,默认用户名和密码都是 sentinel。
5.3 访问控制台
http://localhost:8080
,默认用户名和密码都是 sentinel。
六、代码实践
6.1 新建Module
6.1.1 POM文件,引入Sentinel starter
。
<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.0.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.pay.sentinel</groupId>
<artifactId>sentinel</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>sentinel</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud-alibaba.version>2.2.1.RELEASE</spring-cloud-alibaba.version>
</properties>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
6.1.2 application.properties
增加配置,在应用的 /src/main/resources/application.properties 中添加基本配置信息。核心配置:spring.cloud.sentinel.transport.dashboard=localhost:8080
。
#################################### common config : ####################################
spring.application.name=guoxiuzhi sentinel
# 应用服务web访问端口
server.port=8090
# ActuatorWeb访问端口
management.server.port=8091
management.endpoints.jmx.exposure.include=*
management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always
# spring cloud access&secret config
# 可以访问如下地址查看: https://usercenter.console.aliyun.com/#/manage/ak
alibaba.cloud.access-key=****
alibaba.cloud.secret-key=****
#################################### sentinel config : ####################################
spring.cloud.sentinel.transport.dashboard=localhost:8080
management.health.sentinel.enabled=false
spring.cloud.sentinel.eager=true
6.1.3 SentinelApplication
package com.pay.sentinel.sentinel;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @ClassName: SentinelApplication
* @Description:
* @author: 郭秀志 jbcode@126.com
* @date: 2020/6/15 9:35
* @Copyright:
*/
@SpringBootApplication
public class SentinelApplication {
public static void main(String[] args) {
SpringApplication.run(SentinelApplication.class, args);
}
}
6.1.4 SentinelTestController
package com.pay.sentinel.sentinel.sentinel;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* @ClassName: SentinelTestController
* @Description: 测试Sentinel
* @author: 郭秀志 jbcode@126.com
* @date: 2020/6/15 9:20
* @Copyright:
*/
@RestController
public class SentinelTestController {
@GetMapping(value = "/hello")
public String hello(@RequestParam String name) {
return "Hello " + getName(name);
}
public String getName(String name) {
return name;
}
}
说明:Sentinel starter 默认为所有的 HTTP 服务提供了限流埋点,如果只想对 HTTP 服务进行限流,那么只需要引入依赖,无需修改代码。
6.1.5 启动项目
访问:http://localhost:8090/hello?name=郭秀志
如果Sentinel控制台并没有任何http://localhost:8090/test
的相关信息,不要急,这是因为sentinel使用懒加载机制,没调用的不会出现在dashboard中。访问一下,然后再看dashboard中就有了。
七、 配置限流规则
Sentinel 提供了两种配置限流规则的方式:代码配置 和 控制台配置。本示例使用的方式为通过控制台配置。
7.1 通过代码来实现限流规则的配置。
一个简单的限流规则配置示例代码如下,更多限流规则配置详情请参考 Sentinel 文档。
List<FlowRule> rules = new ArrayList<FlowRule>();
FlowRule rule = new FlowRule();
rule.setResource(str);
// set limit qps to 10
rule.setCount(10);
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setLimitApp("default");
rules.add(rule);
FlowRuleManager.loadRules(rules);
7.2 通过控制台进行限流规则配置(我们采用这种方式)
7.2.1 认识规则
在sentinel的dashboard中有一个流控规则,如上图,下面对这些名词进行解释:
- 资源名:唯一名称,默认请求路径
- 针对来源:sentinel可以针对调用者进行限流,填写微服务名称,默认default,表示不区分来源
- 阈值类型:
QPS(每秒钟的请求数):当调用该API的QPS达到阈值时,进行限流;
线程数:当调用该API的线程数达到阈值时进行限流。 - 是否集群:不需要集群
- 流控模式:
直接:达到限流条件时直接限流;
关联:关联的资源达到阈值时限流自己(当与A关联的资源B达到阈值时,就限流A自己。应用场景:支付服务达到阈值的时候,就限流下订单的服务);
链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流,API级别的针对来源。 - 流控效果:
快速失败:直接失败,抛异常;
warm up:根据codeFactor(冷加载因子,默认是3)的值,从阈值/codeFactor,经过预热时长,才达到设置的QPS阈值;
排队等待:匀速排队,让请求以匀速通过,阈值类型必须设置为QPS,否则无效。
7.2.2 配置实践
- 阈值类型:QPS/流控模式:直接/流控效果:快速失败
设置的意思就是,访问/hello,一秒钟超过一次,就是直接快速报错。我现在对http://localhost:8090/hello?name=郭秀志
访问快速两次,就会返回如下信息:Blocked by Sentinel (flow limiting)
,代表被限流了。慢速访问,正常返回:Hello 郭秀志
。 - 直接失败
我们再访问http://localhost:8090/hello?name=郭秀志
,发现不管点多快,都没有被流控给拦住。因为我们在一个浏览器中访问,始终是一个线程。这样配置的意思就是并发线程数超过阈值1时,就会返回失败信息。可以用jmeter模拟并发访问的情况。 -
流控模式之关联
当关联的资源达到阀值,就限流自己。即关联的资源是优先保证的主资源。
意思就是/test
的QPS数超过1,就会导致/hello
不能用。测试方法:用jmeter对/test进行并发访问,然后我们在浏览器访问/hello。就会发现也会返回Blocked by Sentinel (flow limiting)
。
我们来验证一下,这里用了PostMan工具来调用/test,每隔200ms循环调用。这样就保证关联资源/test肯定超过了阀值。
我们再来请求/hello,发现被限流了。
这种关联模式有什么应用场景呢?
我们举个例子,订单服务中会有2个重要的接口,一个是读取订单信息接口,一个是写入订单信息接口。
在高并发业务场景中,两个接口都会占用资源,如果读取接口访问过大,就会影响写入接口的性能。业务中如果我们希望写入订单比较重要,要优先考虑写入订单接口。那就可以利用关联模式;
在资源名设置读取接口,关联资源上面设置写入接口;这样就起到了优先写入,一旦写入请求多,就限制读的请求。
关联模式的目标就是保护关联资源的。
- 链路模式
只记录链路入口的流量
上面是解释,有点不是太清楚,我们来看个案例,我们改造一下代码。
增加了一个服务,方法用@SentinelResource
进行注解,就是定义一个资源名。
package com.pay.sentinel.sentinel.sentinel;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import org.springframework.stereotype.Service;
/**
* @ClassName: MyService
* @Description:
* @author: 郭秀志 jbcode@126.com
* @date: 2020/6/15 16:58
* @Copyright:
*/
@Service
public class MyService {
@SentinelResource("chineseHello")
public String getChineseHello() {
return "你好";
}
}
SentinelTestController使用Service
package com.pay.sentinel.sentinel.sentinel;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* @ClassName: SentinelTestController
* @Description: 测试Sentinel
* @author: 郭秀志 jbcode@126.com
* @date: 2020/6/15 9:20
* @Copyright:
*/
@RestController
public class SentinelTestController {
@Autowired
MyService myService;
@GetMapping(value = "/hello")
@SentinelResource("helloResource")
public String hello(@RequestParam String name) {
String chineseHello = myService.getChineseHello();
return chineseHello + "/Hello " + getName(name);
}
public String getName(String name) {
return name;
}
@GetMapping(value = "/test")
public String test() {
String chineseHello = myService.getChineseHello();
return chineseHello + "test!";
}
}
让/hello和/test都调用这个服务;那我们就可以利用链路模式设置限制哪个入口的流量了。配置链路规则
7.2.3 流控效果说明
上面用的流控效果都是快速失败,现在来认识一下这些流控效果。
- 快速失败:上面的案例都是直接失败,就是超过阈值就返回
Blocked by Sentinel (flow limiting)
- 预热( Warm Up):请求的QPS从
阈值 / 冷加载因子(默认是3)
开始,经过预热时长
,最后达到阈值。比如下图的配置意思是:初始阈值为10 / 3 = 3
,经过10秒钟的时间,阈值慢慢升到10。现在快速访问testA,因为一开始QPS阈值为3,所以你点快一点可能就失败了,但是慢慢地,你点很快都不会失败了,因为最后阈值升为10,正常情况下没有人手速能达到1秒钟点11次吧。
这个预热的应用场景,如:秒杀系统在开启的瞬间,会有很多流量上来,很有可能把系统打死,预热方式就是把为了保护系统,可慢慢的把流量放进来,慢慢的把阀值增长到设置的阀值。
-
排队等待:就是不管同时有多少个请求过来,我每秒钟只处理阈值数的请求,其他老老实实排队等待去。如下图配置意思就是每秒钟只处理一个,其他的排队等着,每隔1000毫秒才放下一个请求进来。从字面上面就能够猜到,匀速排队,让请求以均匀的速度通过,阀值类型必须设成QPS,否则无效。
再次利用Postman工具,循环请求/test,我们可以发现test的每隔1秒执行一次,这么多的请求没有被拒绝,而且进入的排队。
排队的应用场景是什么呢?
比如有时候系统在某一个时刻会出现大流量,之后流量就恢复稳定,可以采用这种排队模式,大流量来时可以让流量请求先排队,等恢复了在慢慢进行处理
八、熔断降级
Sentinel除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。由于调用关系的复杂性,如果调用链路中的某个资源不稳定,最终会导致请求发生堆积。Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)。
Sentinel以三种方式衡量被访问的资源是否处理稳定的状态:
- 平均响应时间 (DEGRADE_GRADE_RT):当资源的平均响应时间超过阈值(DegradeRule 中的 count,以 ms 为单位)之后,资源进入准降级状态。接下来如果持续进入 5 个请求,它们的 RT 都持续超过这个阈值,那么在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地返回(抛出 DegradeException)。在下一个时间窗口到来时, 会接着再放入5个请求, 再重复上面的判断.
- 异常比例 (DEGRADE_GRADE_EXCEPTION_RATIO):当资源的每秒异常总数占通过量的比值超过阈值(DegradeRule 中的 count)之后,资源进入降级状态,即在接下的时间窗口(DegradeRule中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
- 异常数 (DEGRADE_GRADE_EXCEPTION_COUNT):当资源近 1 分钟的异常数目超过阈值之后会进行熔断。
修改Controller,增加sleep时间,使其满足降级条件。
@GetMapping(value = "/test")
public String test() {
String chineseHello = myService.getChineseHello();
try {
Thread.sleep(1500);//sleep 1.5秒返回结果
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("chineseHello = " + chineseHello);
return chineseHello + "test!";
}
上面规则的含义:当资源的平均响应时间超过阈值
500
(以 ms 为单位)之后,资源进入准降级状态。接下来如果持续进入 5 个请求,它们的 RT 都持续超过这个阈值,那么在接下的时间窗口10
(以 s 为单位)之内,对这个方法的调用都会自动地返回(抛出 DegradeException),实测是返回Blocked by Sentinel (flow limiting)
。九、热点规则
9.1 热点规则是什么?
就是针对热点数据做限流。比如id为1的商品是热点数据,那么可以针对id为1的这个商品做限流。
9.2 Controller中加一个方法,如下:
@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey", blockHandler = "deal_testHotKey") // 这个value值随意,只要唯一即可,但是一般和@GetMapping中的一致
public String testHotKey(@RequestParam(value = "p1", required = false) String p1,
@RequestParam(value = "p2", required = false) String p2) {
return "test hot key";
}
/**
* 兜底方法,参数除了原方法的参数,还要加上BlockException
* @param p1
* @param p2
* @param e
* @return
*/
public String deal_testHotKey(String p1, String p2, BlockException e) {
return "兜底方法";
}
9.3 热点规则配置:
这里配置的意思就是,
testHotKey
(就是@SentinelResource中的value值)这个资源,我对索引为0的参数(p1)进行监控,如果访问testHotKey带上了p1,并且QPS超过了1,那么接下来的1秒中内这个方法都会被降级。注意:索引为0的参数是p1,是controller中接收参数的顺序的索引。你访问http://192.168.0.104:8090/testHotKey?p2=1
,这里只有一个参数p2,在url中它是第0个参数,但是在controller中不是,所以这样访问并不会被降级。
9.3.1 热点配置的高级选项
参数例外项:上面的配置对p1进行限流,不管p1的值是多少,只要QPS超过1,就降级。现在的需求是如果p1的值是5,我就搞特殊的,因为它是VIP,所以让它QPS超过100才限流。配置如下图:
当参数不是5时,QPS超过1就会被限流降级,p1的值为5时,你狂点都可以正常访问。
注意:@SentinelResource只管我们控制台配置的违规情况,才会进行兜底,假如程序异常了,它是管不了的。比如我们在return 前加一行int a = 10 / 0,它还是会返回error page的,而不是兜底方法。
9.4 测试
访问:http://localhost:8090/testHotKey?p1=guo&p2=xiuzhi
正常返回:test hot key
如果访问过快,返回:兜底方法
访问:http://localhost:8090/testHotKey?p1=5&p2=xiuzhi
正常返回:test hot key
如果访问过快,同样返回:test hot key
十、规则丢失
无论是通过硬编码的方式来更新规则,还是通过接入 Sentinel Dashboard 后,在页面上操作来更新规则,都无法避免一个问题,那就是服务重新后,规则就丢失了,因为默认情况下规则是保存在内存中的。规则持久化参考如下文章。