1. Sentinel 是什么?java
随着微服务的流行,服务和服务之间的稳定性变得愈来愈重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。node
Sentinel 具备如下特征:git
Sentinel 的主要特性:github
Sentinel 的开源生态:web
Sentinel 分为两个部分:spring
2. Sentinel 快速开始数据库
首先,引入 Sentinel 依赖apache
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>1.7.1</version>
</dependency>
接着,定义资源网络
资源 是 Sentinel 中的核心概念之一。最经常使用的资源是咱们代码中的 Java 方法。 固然,您也能够更灵活的定义你的资源,例如,把须要控制流量的代码用 Sentinel API SphU.entry("HelloWorld") 和 entry.exit() 包围起来便可。在下面的例子中,咱们将 System.out.println("hello world"); 做为资源(被保护的逻辑),用 API 包装起来。例如:多线程
try (Entry entry = SphU.entry("HelloWorld")) {
// Your business logic here.
System.out.println("hello world");
} catch (BlockException e) {
// Handle rejected request.
e.printStackTrace();
}
// try-with-resources auto exit
还可使用注解定义资源 https://github.com/alibaba/Sentinel/wiki/%E6%B3%A8%E8%A7%A3%E6%94%AF%E6%8C%81
例如:
@SentinelResource("HelloWorld")
public void helloWorld() {
// 资源中的逻辑
System.out.println("hello world");
}
最后,定义规则
接下来,经过流控规则来指定容许该资源经过的请求次数,例以下面的代码定义了资源 HelloWorld 每秒最多只能经过 20 个请求。
private static void initFlowRules(){
List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule();
rule.setResource("HelloWorld");
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
// Set limit QPS to 20.
rule.setCount(20);
rules.add(rule);
FlowRuleManager.loadRules(rules);
}
完成!
完整的代码以下:
pom.xml
1 <?xml version="1.0" encoding="UTF-8"?>
2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4 <modelVersion>4.0.0</modelVersion>
5 <parent>
6 <groupId>org.springframework.boot</groupId>
7 <artifactId>spring-boot-starter-parent</artifactId>
8 <version>2.2.2.RELEASE</version>
9 <relativePath/> <!-- lookup parent from repository -->
10 </parent>
11 <groupId>com.cjs.example</groupId>
12 <artifactId>sentinel-example</artifactId>
13 <version>0.0.1-SNAPSHOT</version>
14 <name>sentinel-example</name>
15
16 <properties>
17 <java.version>1.8</java.version>
18 <spring-cloud.version>Greenwich.SR4</spring-cloud.version>
19 <spring-cloud-alibaba.version>2.1.0.RELEASE</spring-cloud-alibaba.version>
20 </properties>
21
22 <dependencies>
23 <dependency>
24 <groupId>org.springframework.boot</groupId>
25 <artifactId>spring-boot-starter-actuator</artifactId>
26 </dependency>
27 <dependency>
28 <groupId>org.springframework.boot</groupId>
29 <artifactId>spring-boot-starter-web</artifactId>
30 </dependency>
31 <dependency>
32 <groupId>com.alibaba.cloud</groupId>
33 <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
34 </dependency>
35
36 </dependencies>
37
38 <dependencyManagement>
39 <dependencies>
40 <dependency>
41 <groupId>org.springframework.cloud</groupId>
42 <artifactId>spring-cloud-dependencies</artifactId>
43 <version>${spring-cloud.version}</version>
44 <type>pom</type>
45 <scope>import</scope>
46 </dependency>
47
48 <dependency>
49 <groupId>com.alibaba.cloud</groupId>
50 <artifactId>spring-cloud-alibaba-dependencies</artifactId>
51 <version>${spring-cloud-alibaba.version}</version>
52 <type>pom</type>
53 <scope>import</scope>
54 </dependency>
55 </dependencies>
56 </dependencyManagement>
57
58 <build>
59 <plugins>
60 <plugin>
61 <groupId>org.springframework.boot</groupId>
62 <artifactId>spring-boot-maven-plugin</artifactId>
63 </plugin>
64 </plugins>
65 </build>
66
67 </project>
application.properties
server.port=8084
spring.application.name=sentinel-example
spring.cloud.sentinel.transport.dashboard=127.0.0.1:8080
SentinelExampleApplication.java
1 package com.cjs.example.sentinel;
2
3 import com.alibaba.csp.sentinel.Entry;
4 import com.alibaba.csp.sentinel.SphU;
5 import com.alibaba.csp.sentinel.slots.block.BlockException;
6 import com.alibaba.csp.sentinel.slots.block.RuleConstant;
7 import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
8 import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
9 import org.springframework.boot.SpringApplication;
10 import org.springframework.boot.autoconfigure.SpringBootApplication;
11
12 import java.util.ArrayList;
13 import java.util.List;
14
15 @SpringBootApplication
16 public class SentinelExampleApplication {
17
18 public static void main(String[] args) {
19 SpringApplication.run(SentinelExampleApplication.class, args);
20
21
22 // 配置规则.
23 initFlowRules();
24
25 while (true) {
26 // 1.5.0 版本开始能够直接利用 try-with-resources 特性,自动 exit entry
27 try (Entry entry = SphU.entry("HelloWorld")) {
28 // 被保护的逻辑
29 System.out.println("hello world");
30 } catch (BlockException ex) {
31 // 处理被流控的逻辑
32 System.out.println("blocked!");
33 }
34 }
35 }
36
37
38 private static void initFlowRules() {
39 List<FlowRule> rules = new ArrayList<>();
40
41 FlowRule rule = new FlowRule();
42 rule.setResource("HelloWorld");
43 rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
44 // Set limit QPS to 20.
45 rule.setCount(20);
46 rules.add(rule);
47
48 FlowRuleManager.loadRules(rules);
49
50 }
51 }
TestController.java
1 package com.cjs.example.sentinel;
2
3 import com.alibaba.csp.sentinel.annotation.SentinelResource;
4 import org.springframework.web.bind.annotation.GetMapping;
5 import org.springframework.web.bind.annotation.RestController;
6
7 @RestController
8 public class TestController {
9
10 @GetMapping("/hello")
11 @SentinelResource("hello")
12 public String hello() {
13 return "hello";
14 }
15
16 }
3. Sentinel 控制台
Sentinel 控制台最少应该包含以下功能:
https://github.com/alibaba/Sentinel/wiki/%E6%8E%A7%E5%88%B6%E5%8F%B0
获取控制台:
方式一:下载已经打好的包
https://github.com/alibaba/Sentinel/releases
wget https://github.com/alibaba/Sentinel/releases/download/1.7.1/sentinel-dashboard-1.7.1.jar
方式二:经过源码构件
mvn clean package
启动控制台
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.7.1.jar
默认用户名密码都是sentinel
4. Sentinel 注解支持
@SentinelResource 用于定义资源,并提供可选的异常处理和 fallback 配置项
@SentinelResource 注解的属性:
exceptionsToIgnore
里面排除掉的异常类型)进行处理。fallback 函数签名和位置要求:defaultFallback :默认的 fallback 函数名称,可选项,一般用于通用的 fallback 逻辑
Throwable
类型的参数用于接收对应的异常;fallbackClass
为对应的类的 Class
对象,注意对应的函数必需为 static 函数,不然没法解析; 须要注意的是,若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑。若未配置 blockHandler、fallback 和 defaultFallback,则被限流降级时会将 BlockException 直接抛出(若方法自己未定义 throws BlockException 则会被 JVM 包装一层 UndeclaredThrowableException)。
示例:
1 public class TestService {
2
3 // 对应的 `handleException` 函数须要位于 `ExceptionUtil` 类中,而且必须为 static 函数.
4 @SentinelResource(value = "test", blockHandler = "handleException", blockHandlerClass = {ExceptionUtil.class})
5 public void test() {
6 System.out.println("Test");
7 }
8
9 // 原函数
10 @SentinelResource(value = "hello", blockHandler = "exceptionHandler", fallback = "helloFallback")
11 public String hello(long s) {
12 return String.format("Hello at %d", s);
13 }
14
15 // Fallback 函数,函数签名与原函数一致或加一个 Throwable 类型的参数.
16 public String helloFallback(long s) {
17 return String.format("Halooooo %d", s);
18 }
19
20 // Block 异常处理函数,参数最后多一个 BlockException,其他与原函数一致.
21 public String exceptionHandler(long s, BlockException ex) {
22 // Do some log here.
23 ex.printStackTrace();
24 return "Oops, error occurred at " + s;
25 }
26 }
能够看到,blockHandler和fallback必须与原方法在同一个类中。若是不想写在同一个类中,能够利用blockHandlerClass来指定类,而后经过blockHandler指定方法名。
若是同时配置了blockHandler和fallback,则BlockException只会进到blockHandler处理逻辑中。
5. Sentinel 基本概念
资源
只要经过 Sentinel API 定义的代码,就是资源,可以被 Sentinel 保护起来。大部分状况下,可使用方法签名,URL,甚至服务名称做为资源名来标示资源。
规则
围绕资源的实时状态设定的规则,能够包括流量控制规则、熔断降级规则以及系统保护规则。全部规则能够动态实时调整。
流量控制
流量控制在网络传输中是一个经常使用的概念,它用于调整网络包的发送数据。然而,从系统稳定性角度考虑,在处理请求的速度上,也有很是多的讲究。任意时间到来的请求每每是随机不可控的,而系统的处理能力是有限的。咱们须要根据系统的处理能力对流量进行控制。Sentinel 做为一个调配器,能够根据须要把随机的请求调整成合适的形状,以下图所示:
流量控制有如下几个角度:
Sentinel 的设计理念是让您自由选择控制的角度,并进行灵活组合,从而达到想要的效果。
熔断降级
Sentinel 和 Hystrix 的原则是一致的: 当检测到调用链路中某个资源出现不稳定的表现,例如请求响应时间长或异常比例升高的时候,则对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而致使级联故障。
在限制的手段上,Sentinel 和 Hystrix 采起了彻底不同的方法。
Hystrix 经过 线程池隔离 的方式,来对依赖(在 Sentinel 的概念中对应 资源)进行了隔离。这样作的好处是资源和资源之间作到了最完全的隔离。缺点是除了增长了线程切换的成本(过多的线程池致使线程数目过多),还须要预先给各个资源作线程池大小的分配。 以下图:
Sentinel 对这个问题采起了两种手段:
和资源池隔离的方法不一样,Sentinel 经过限制资源并发线程的数量,来减小不稳定资源对其它资源的影响。这样不但没有线程切换的损耗,也不须要您预先分配线程池的大小。当某个资源出现不稳定的状况下,例如响应时间变长,对资源的直接影响就是会形成线程数的逐步堆积。当线程数在特定资源上堆积到必定的数量以后,对该资源的新请求就会被拒绝。堆积的线程完成任务后才开始继续接收请求。
除了对并发线程数进行控制之外,Sentinel 还能够经过响应时间来快速降级不稳定的资源。当依赖的资源出现响应时间过长后,全部对该资源的访问都会被直接拒绝,直到过了指定的时间窗口以后才从新恢复。
系统负载保护
Sentinel 同时提供系统维度的自适应保护能力。防止雪崩,是系统防御中重要的一环。当系统负载较高的时候,若是还持续让请求进入,可能会致使系统崩溃,没法响应。在集群环境下,网络负载均衡会把本应这台机器承载的流量转发到其它的机器上去。若是这个时候其它的机器也处在一个边缘状态的时候,这个增长的流量就会致使这台机器也崩溃,最后致使整个集群不可用。
针对这个状况,Sentinel 提供了对应的保护机制,让系统的入口流量和系统的负载达到一个平衡,保证系统在能力范围以内处理最多的请求。
6. 如何使用Sentinel
Sentinel 能够简单的分为 Sentinel 核心库和 Dashboard。核心库不依赖 Dashboard,可是结合 Dashboard 能够取得最好的效果。
资源,能够是任何东西,服务,服务里的方法,甚至是一段代码。使用 Sentinel 来进行资源保护,主要分为几个步骤:
在编码的时候,只须要考虑这个代码是否须要保护,若是须要保护,就将之定义为一个资源。
定义资源的经常使用方式
方式一: 抛出异常的方式定义资源
SphU
包含了 try-catch 风格的 API。用这种方式,当资源发生了限流以后会抛出 BlockException
。这个时候能够捕捉异常,进行限流以后的逻辑处理。示例代码以下:
1 // 1.5.0 版本开始能够利用 try-with-resources 特性
2 // 资源名可以使用任意有业务语义的字符串,好比方法名、接口名或其它可惟一标识的字符串。
3 try (Entry entry = SphU.entry("resourceName")) {
4 // 被保护的业务逻辑
5 // do something here...
6 } catch (BlockException ex) {
7 // 资源访问阻止,被限流或被降级
8 // 在此处进行相应的处理操做
9 }
特别地,若 entry 的时候传入了热点参数,那么 exit 的时候也必定要带上对应的参数(exit(count, args)),不然可能会有统计错误。这个时候不能使用 try-with-resources 的方式。另外经过 Tracer.trace(ex) 来统计异常信息时,因为 try-with-resources 语法中 catch 调用顺序的问题,会致使没法正确统计异常数,所以统计异常信息时也不能在 try-with-resources 的 catch 块中调用 Tracer.trace(ex)。
手动 exit 示例:
1 Entry entry = null;
2 // 务必保证 finally 会被执行
3 try {
4 // 资源名可以使用任意有业务语义的字符串,注意数目不能太多(超过 1K),超出几千请做为参数传入而不要直接做为资源名
5 // EntryType 表明流量类型(inbound/outbound),其中系统规则只对 IN 类型的埋点生效
6 entry = SphU.entry("自定义资源名");
7 // 被保护的业务逻辑
8 // do something...
9 } catch (BlockException ex) {
10 // 资源访问阻止,被限流或被降级
11 // 进行相应的处理操做
12 } catch (Exception ex) {
13 // 若须要配置降级规则,须要经过这种方式记录业务异常
14 Tracer.traceEntry(ex, entry);
15 } finally {
16 // 务必保证 exit,务必保证每一个 entry 与 exit 配对
17 if (entry != null) {
18 entry.exit();
19 }
20 }
热点参数埋点示例:
1 Entry entry = null;
2 try {
3 // 若须要配置例外项,则传入的参数只支持基本类型。
4 // EntryType 表明流量类型,其中系统规则只对 IN 类型的埋点生效
5 // count 大多数状况都填 1,表明统计为一次调用。
6 entry = SphU.entry(resourceName, EntryType.IN, 1, paramA, paramB);
7 // Your logic here.
8 } catch (BlockException ex) {
9 // Handle request rejection.
10 } finally {
11 // 注意:exit 的时候也必定要带上对应的参数,不然可能会有统计错误。
12 if (entry != null) {
13 entry.exit(1, paramA, paramB);
14 }
15 }
SphU.entry()
的参数描述:
方式2、注解方式定义资源
Sentinel 支持经过 @SentinelResource
注解定义资源并配置 blockHandler
和 fallback
函数来进行限流以后的处理。示例:
1 // 本来的业务方法.
2 @SentinelResource(blockHandler = "blockHandlerForGetUser")
3 public User getUserById(String id) {
4 throw new RuntimeException("getUserById command failed");
5 }
6
7 // blockHandler 函数,原方法调用被限流/降级/系统保护的时候调用
8 public User blockHandlerForGetUser(String id, BlockException ex) {
9 return new User("admin");
10 }
注意 blockHandler
函数会在原方法被限流/降级/系统保护的时候调用,而 fallback
函数会针对全部类型的异常。另外请注意 blockHandler
和 fallback
函数的形式要求。
方式3、支持异步调用
Sentinel 支持异步调用链路的统计。在异步调用中,须要经过 SphU.asyncEntry(xxx)
方法定义资源,并一般须要在异步的回调函数中调用 exit
方法。示例:
1 try {
2 AsyncEntry entry = SphU.asyncEntry(resourceName);
3
4 // 异步调用.
5 doAsync(userId, result -> {
6 try {
7 // 在此到处理异步调用的结果.
8 } finally {
9 // 在回调结束后 exit.
10 entry.exit();
11 }
12 });
13 } catch (BlockException ex) {
14 // Request blocked.
15 // Handle the exception (e.g. retry or fallback).
16 }
7. Sentinel 工做主流程
在 Sentinel 里面,全部的资源都对应一个资源名称(resourceName
),每次资源调用都会建立一个 Entry
对象。Entry 能够经过对主流框架的适配自动建立,也能够经过注解的方式或调用 SphU
API 显式建立。Entry 建立的时候,同时也会建立一系列功能插槽(slot chain),这些插槽有不一样的职责,例如:
整体架构图以下:
这个彩色的图貌似更好看一点儿
Sentinel 将 SlotChainBuilder
做为 SPI 接口进行扩展,使得 Slot Chain 具有了扩展的能力。您能够自行加入自定义的 slot 并编排 slot 间的顺序,从而能够给 Sentinel 添加自定义的功能。
8. Sentinel 流量控制
流量控制(flow control),其原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以免被瞬时的流量高峰冲垮,从而保障应用的高可用性。
FlowSlot
会根据预设的规则,结合前面 NodeSelectorSlot
、ClusterNodeBuilderSlot
、StatisticSlot
统计出来的实时信息进行流量控制。
限流的直接表现是在执行 Entry nodeA = SphU.entry(resourceName)
的时候抛出 FlowException
异常。FlowException
是 BlockException
的子类,您能够捕捉 BlockException
来自定义被限流以后的处理逻辑。
同一个资源能够建立多条限流规则。FlowSlot
会对该资源的全部限流规则依次遍历,直到有规则触发限流或者全部规则遍历完毕。
一条限流规则主要由下面几个因素组成,咱们能够组合这些元素来实现不一样的限流效果:
8.1. 基于QPS/并发数的流量控制
流量控制主要有两种统计类型,一种是统计并发线程数,另一种则是统计 QPS。类型由 FlowRule
的 grade
字段来定义。其中,0 表明根据并发数量来限流,1 表明根据 QPS 来进行流量控制。其中线程数、QPS 值,都是由 StatisticSlot
实时统计获取的。
能够经过下面的命令查看实时统计信息:
curl http://localhost:8719/cnode?id=resourceName
并发线程数流量控制
并发线程数限流用于保护业务线程数不被耗尽。例如,当应用所依赖的下游应用因为某种缘由致使服务不稳定、响应延迟增长,对于调用者来讲,意味着吞吐量降低和更多的线程数占用,极端状况下甚至致使线程池耗尽。为应对太多线程占用的状况,业内有使用隔离的方案,好比经过不一样业务逻辑使用不一样线程池来隔离业务自身之间的资源争抢(线程池隔离)。这种隔离方案虽然隔离性比较好,可是代价就是线程数目太多,线程上下文切换的 overhead 比较大,特别是对低延时的调用有比较大的影响。Sentinel 并发线程数限流不负责建立和管理线程池,而是简单统计当前请求上下文的线程数目,若是超出阈值,新的请求会被当即拒绝,效果相似于信号量隔离。
QPS流量控制
当 QPS 超过某个阈值的时候,则采起措施进行流量控制。流量控制的效果包括如下几种:直接拒绝、Warm Up、匀速排队。对应 FlowRule
中的 controlBehavior
字段。
8.2. 基于调用关系的流量控制
调用关系包括调用方、被调用方;一个方法又可能会调用其它方法,造成一个调用链路的层次关系。Sentinel 经过 NodeSelectorSlot
创建不一样资源间的调用的关系,而且经过 ClusterNodeBuilderSlot
记录每一个资源的实时统计信息。
有了调用链路的统计信息,咱们能够衍生出多种流量控制手段。
根据调用方限流
ContextUtil.enter(resourceName, origin)
方法中的 origin
参数标明了调用方身份。这些信息会在 ClusterBuilderSlot
中被统计。可经过如下命令来展现不一样的调用方对同一个资源的调用数据:
根据调用链路入口限流:链路限流
NodeSelectorSlot
中记录了资源之间的调用链路,这些资源经过调用关系,相互之间构成一棵调用树。这棵树的根节点是一个名字为 machine-root
的虚拟节点,调用链的入口都是这个虚节点的子节点。
一棵典型的调用树以下图所示:
具备关系的资源流量控制:关联流量控制
当两个资源之间具备资源争抢或者依赖关系的时候,这两个资源便具备了关联。好比对数据库同一个字段的读操做和写操做存在争抢,读的速度太高会影响写得速度,写的速度太高会影响读的速度。若是听任读写操做争抢资源,则争抢自己带来的开销会下降总体的吞吐量。可以使用关联限流来避免具备关联关系的资源之间过分的争抢,举例来讲,read_db
和 write_db
这两个资源分别表明数据库读写,咱们能够给 read_db
设置限流规则来达到写优先的目的:设置 FlowRule.strategy
为 RuleConstant.RELATE
同时设置 FlowRule.ref_identity
为 write_db
。这样当写库操做过于频繁时,读数据的请求会被限流。
9. Sentinel 文档
https://github.com/alibaba/Sentinel
https://github.com/alibaba/Sentinel/wiki/%E6%96%B0%E6%89%8B%E6%8C%87%E5%8D%97
https://github.com/alibaba/Sentinel/wiki/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8
https://github.com/alibaba/Sentinel/wiki/Sentinel%E5%B7%A5%E4%BD%9C%E4%B8%BB%E6%B5%81%E7%A8%8B
https://github.com/alibaba/Sentinel/wiki/%E6%B5%81%E9%87%8F%E6%8E%A7%E5%88%B6
https://github.com/alibaba/Sentinel/tree/master/sentinel-demo
https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel