SpringCloudAlibaba分布式流量控制组件Sentinel实战与源码分析(上)
概述
定义
Sentinel官网地址 https://sentinelguard.io/zh-cn/index.html 最新版本v1.8.4
Sentinel官网文档地址 https://sentinelguard.io/zh-cn/docs/introduction.html
Sentinel GitHub源码地址 https://www.github.com/alibaba/Sentinel
Sentinel是面向分布式服务架构的高可用防护组件,主要以流量为切入点,从流量控制、熔断降级、系统自适应保护等多个维度来帮助用户保障微服务的稳定性。
背景
分布式系统遇到常见棘手问题就是如何保障系统高可用性的场景,严重则会导致整个系统服务雪崩。
在微服务架构中,出现服务交叉调用是很常见的情况,如电商秒杀商品、商品详情、购物车等都会访问库存服务。如果其中下单服务不可用,就会出现线程池里的所有线程都因等待而被阻塞,进而导致整个系统的服务雪崩。服务雪崩效应是因服务提供者的不可用导致服务调用者的不可用,并将不可用逐步放大的过程。特别如果被一些非核心业务如积分服务而导致整个服务不可用那就非常不值得,假如积分服务有一些容错处理机制,到时在事后加一些补偿的机制加回来也是可以接收的。
常见容错机制
- 超时机制:一旦出现超时就释放资源,释放资源的速度相对还是较快的,一定程度上可以抑制资源耗尽的问题。
- 服务限流:如QPS超时一定值如1000直接拒绝。
- 隔离:隔离可以根据线程数量来限制,也可以根据信号来限制。线程隔离比如可以为每一服务限制可访问的线程数量。信号隔离可以限制并发访问,和限流有点类似。
- 服务熔断:远程服务不可用或网络抖动则暂时关闭服务。其作用如保险丝。
- 服务降级:当出现服务熔断后,服务将不再被调用,可以准备一个本地fallback回调,返回一些本地默认处理,作为一种补偿的机制。
特性
- 丰富的应用场景:阿里巴巴 10 年双十一积累的丰富流量场景,包括秒杀、双十一零点持续洪峰、热点商品探测、预热、消息队列削峰填谷等多样化的场景。
- 易于使用,快速接入:简单易用,开源生态广泛,针对 Dubbo、Spring Cloud、gRPC、Zuul、Reactor、Quarkus 等框架只需要引入适配模块即可快速接入。
- 多样化的流量控制:资源粒度、调用关系、指标类型、控制效果等多维度的流量控制和降级能力。
- 可视化的监控和规则管理:简单易用的 Sentinel 控制台,秒级的实时监控和动态规则管理。
基本概念
- 资源
- 资源是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容,例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码。在接下来的文档中,我们都会用资源来描述代码块。
- 只要通过 Sentinel API 定义的代码,就是资源,能够被 Sentinel 保护起来。大部分情况下,可以使用方法签名,URL,甚至服务名称作为资源名来标示资源。
- 规则
- 围绕资源的实时状态设定的规则,可以包括流量控制规则、熔断降级规则以及系统保护规则。所有规则可以动态实时调整。
功能和设计理念
-
流量控制
- 流量控制在网络传输中是一个常用的概念,它用于调整网络包的发送数据。然而,从系统稳定性角度考虑,在处理请求的速度上,也有非常多的讲究。任意时间到来的请求往往是随机不可控的,而系统的处理能力是有限的。我们需要根据系统的处理能力对流量进行控制。Sentinel 作为一个调配器,可以根据需要把随机的请求调整成合适的形状,如下图所示:
Sentinel 的设计理念可以自由选择控制的角度,并进行灵活组合,从而达到想要的效果流量控制。流量控制有以下几个角度:
- 资源的调用关系,例如资源的调用链路,资源和资源之间的关系;
- 运行指标,例如 QPS、线程池、系统负载等;
- 控制的效果,例如直接限流、冷启动、排队等。
-
熔断降级
-
降低调用链路中的不稳定资源也是 Sentinel 的使命之一。由于调用关系的复杂性,如果调用链路中的某个资源出现了不稳定,最终会导致请求发生堆积。Sentinel 和 Hystrix 的原则是一致的: 当调用链路中某个资源出现不稳定,例如,表现为 timeout,异常比例升高的时候,则对这个资源的调用进行限制,并让请求快速失败,避免影响到其它的资源,最终产生雪崩的效果。
-
设计理念:在限制的手段上,Sentinel 和 Hystrix 采取了完全不一样的方法。Hystrix 通过线程池的方式,来对依赖(在我们的概念中对应资源)进行了隔离。这样做的好处是资源和资源之间做到了最彻底的隔离。缺点是除了增加了线程切换的成本,还需要预先给各个资源做线程池大小的分配。Sentinel 对这个问题采取了两种手段:
- 通过并发线程数进行限制
和资源池隔离的方法不同,Sentinel 通过限制资源并发线程的数量,来减少不稳定资源对其它资源的影响。这样不但没有线程切换的损耗,也不需要您预先分配线程池的大小。当某个资源出现不稳定的情况下,例如响应时间变长,对资源的直接影响就是会造成线程数的逐步堆积。当线程数在特定资源上堆积到一定的数量之后,对该资源的新请求就会被拒绝。堆积的线程完成任务后才开始继续接收请求。
- 通过响应时间对资源进行降级
除了对并发线程数进行控制以外,Sentinel 还可以通过响应时间来快速降级不稳定的资源。当依赖的资源出现响应时间过长后,所有对该资源的访问都会被直接拒绝,直到过了指定的时间窗口之后才重新恢复。
-
-
系统负载保护
- Sentinel 同时提供系统维度的自适应保护能力。防止雪崩,是系统防护中重要的一环。当系统负载较高的时候,如果还持续让请求进入,可能会导致系统崩溃,无法响应。在集群环境下,网络负载均衡会把本应这台机器承载的流量转发到其它的机器上去。如果这个时候其它的机器也处在一个边缘状态的时候,这个增加的流量就会导致这台机器也崩溃,最后导致整个集群不可用。
- 针对这个情况,Sentinel 提供了对应的保护机制,让系统的入口流量和系统的负载达到一个平衡,保证系统在能力范围之内处理最多的请求。
同类组件功能对比Sentinel VS Hystrix VS resilience4j
主要工作机制
- 对主流框架提供适配或者显示的 API,来定义需要保护的资源,并提供设施对资源进行实时统计和调用链路分析。
- 根据预设的规则,结合对资源的实时统计信息,对流量进行控制。同时,Sentinel 提供开放的接口,方便定义及改变规则。
- Sentinel 提供实时的监控系统,方便快速了解目前系统的状态。
工作主流程
在 Sentinel 里面,所有的资源都对应一个资源名称以及一个 Entry。Entry 可以通过对主流框架的适配自动创建,也可以通过注解的方式或调用 API 显式创建;每一个 Entry 创建的时候,同时也会创建一系列功能插槽(slot chain)。这些插槽有不同的职责,例如:
NodeSelectorSlot
负责收集资源的路径,并将这些资源的调用路径,以树状结构存储起来,用于根据调用路径来限流降级;ClusterBuilderSlot
则用于存储资源的统计信息以及调用者信息,例如该资源的 RT, QPS, thread count 等等,这些信息将用作为多维度限流,降级的依据;StatisticSlot
则用于记录、统计不同纬度的 runtime 指标监控信息;FlowSlot
则用于根据预设的限流规则以及前面 slot 统计的状态,来进行流量控制;AuthoritySlot
则根据配置的黑白名单和调用来源信息,来做黑白名单控制;DegradeSlot
则通过统计信息以及预设的规则,来做熔断降级;SystemSlot
则通过系统的状态,例如 load1 等,来控制总的入口流量;
总体框架如下:
Sentinel 将 ProcessorSlot
作为 SPI 接口进行扩展(1.7.2 版本以前 SlotChainBuilder
作为 SPI),使得 Slot Chain 具备了扩展的能力。您可以自行加入自定义的 slot 并编排 slot 间的顺序,从而可以给 Sentinel 添加自定义的功能。
开源框架适配和多语言支持
为了减少开发的复杂程度,对大部分的主流框架例如 Web Servlet、Dubbo、Spring Cloud、gRPC、Spring WebFlux、Reactor 等都做了适配。只需要引入对应的依赖即可方便地整合 Sentinel。
Sentinel 目前提供 Java、Go、C++ 、RUST等语言的原生支持。
实战
组成部分
Sentinel 可以简单的分为 Sentinel 核心库和 Dashboard,核心库不依赖 Dashboard,但是结合 Dashboard 可以取得最好的效果。
- 核心库(Java 客户端):不依赖任何框架/库,能够运行于 Java 8 及以上的版本的运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持(见 主流框架适配)。
- 控制台(Dashboard):Dashboard 主要负责管理推送规则、监控、管理机器信息等。
三步构建
我们说的资源,可以是任何东西,服务,服务里的方法,甚至是一段代码。使用 Sentinel 来进行资源保护,主要分为几个步骤:
- 定义资源:资源 是 Sentinel 中的核心概念之一。最常用的资源是我们代码中的 Java 方法。 当然也可以更灵活的定义你的资源,例如,把需要控制流量的代码用 Sentinel API
SphU.entry("HelloWorld")
和entry.exit()
包围起来即可。 - 定义规则:通过流控规则来指定允许该资源通过的请求次数
- 检验规则是否生效
使用之前得先引入依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>1.8.4</version>
</dependency>
入门初体验
package cn.itxs.ecom.storage.controller;
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
@RestController
@RequestMapping("/sentinel")
public class SentinelHelloControoler {
private static final String RESOURCE_NAME = "hello";
@RequestMapping("/hello")
public String hello(){
// 1.5.0 版本开始可以利用 try-with-resources 特性(使用有限制)
// 资源名可使用任意有业务语义的字符串,比如方法名、接口名或其它可唯一标识的字符串。
try (Entry entry = SphU.entry(RESOURCE_NAME)) {
// 被保护的业务逻辑
return "hello itxs";
} catch (BlockException ex) {
// 资源访问阻止,被限流或被降级
// 在此处进行相应的处理操作
return "hello itxs block!";
}
}
@PostConstruct
private void initFlowRules(){
List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule();
rule.setResource(RESOURCE_NAME);
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
// Set limit QPS to 2.
rule.setCount(2);
rules.add(rule);
FlowRuleManager.loadRules(rules);
}
}
连续访问访问测试http://localhost:4081/sentinel/hello,可以看到被流控了。
@SentinelResource使用
使用提供的@SentinelResource注解,getAccount()
方法就成了一个资源。注意注解支持模块需要配合 Spring AOP 或者 AspectJ 一起使用。
@RequestMapping("/account")
@SentinelResource(value = ACCOUNT_RESOURCE_NAME,blockHandler = "blockHandlerForGetAccount")
public Account getAccount(Integer id){
return new Account(1,"1001",100);
}
public Account blockHandlerForGetAccount(Integer id,BlockException ex){
ex.printStackTrace();
return new Account(1,"被流控了",100);
}
连续访问测试http://localhost:4081/sentinel/account,可以看到也被流控了。
异常处理fallback机制
@RequestMapping("/account_exception")
@SentinelResource(value = ACCOUNT_RESOURCE_NAME,fallback = "fallbackHandlerForGetAccount")
public Account getAccountException(Integer id){
int i = 1/0;
return new Account(1,"1001",100);
}
public Account fallbackHandlerForGetAccount(Integer id,Throwable ex){
ex.printStackTrace();
return new Account(1,"异常处理",100);
}
访问测试http://localhost:4081/sentinel/account_exception,可以看到走fallback方法处理了。
熔断降级
@PostConstruct
private void initDegradeRules(){
List<DegradeRule> rules = new ArrayList<>();
DegradeRule rule = new DegradeRule();
rule.setResource(DEGRADE_RESOURCE_NAME);
rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);
rule.setCount(2);
rule.setMinRequestAmount(2);
rule.setStatIntervalMs(60*1000);
rule.setTimeWindow(10);
rules.add(rule);
DegradeRuleManager.loadRules(rules);
}
@RequestMapping("/degrade")
@SentinelResource(value = DEGRADE_RESOURCE_NAME,entryType = EntryType.IN,blockHandler = "blockHandlerForDegrade")
public Account degrade(Integer id){
throw new RuntimeException("异常");
}
public Account blockHandlerForDegrade(Integer id,BlockException ex){
ex.printStackTrace();
return new Account(1,"熔断降级",100);
}
连续访问两次访问异常http://localhost:4081/sentinel/account_exception,之后十秒内访问都直接熔断降级,10秒后再访问两次显示异常
Sentinel 控制台
功能
Sentinel 提供一个轻量级的开源控制台,它提供机器发现以及健康情况管理、监控(单机和集群),规则管理和推送的功能。通过整合 Sentinel 核心库和 Dashboard。Sentinel 控制台包含如下功能:
- 查看机器列表以及健康情况:收集 Sentinel 客户端发送的心跳包,用于判断机器是否在线。
- 监控 (单机和集群聚合):通过 Sentinel 客户端暴露的监控 API,定期拉取并且聚合应用监控信息,最终可以实现秒级的实时监控。
- 规则管理和推送:统一管理推送规则。
- 鉴权:生产环境中鉴权非常重要。这里每个开发者需要根据自己的实际情况进行定制。
注意:Sentinel 控制台目前仅支持单机部署。Sentinel 控制台项目提供 Sentinel 功能全集示例,不作为开箱即用的生产环境控制台,若希望在生产环境使用请根据文档自行进行定制和改造。
docker部署
# 下载 docker pull bladex/sentinel-dashboard:tagname,没有tag默认下载最新版本latest,本次使用1.8.4版本bladex/sentinel-dashboard:1.8.4
docker pull bladex/sentinel-dashboard
# 运行,访问http 8858端口进入dashboard,用户名和密码sentinel/sentinel
docker run --name sentinel -d -p 8858:8858 -d bladex/sentinel-dashboard
二进制包部署
可以从其GitHub项目的 release 页面 下载最新版本的控制台 jar 包。目前最新版本为v1.8.4,也可以从最新版本的源码自行构建 Sentinel 控制台的jar包。由于下载源码1.8.4版本,那这里我们执行编译控制台源码得到二进制包。
# 启动 Sentinel 控制台需要 JDK 版本为 1.8 及以上版本。其中 -Dserver.port=8858 用于指定 Sentinel 控制台端口为 8858,没有指定为8080。从 Sentinel 1.6.0 起,Sentinel 控制台引入基本的登录功能,默认用户名和密码都是 sentinel
java -Dserver.port=8858 -Dsentinel.dashboard.auth.username=itxs -Dsentinel.dashboard.auth.password=123456 -jar sentinel-dashboard.jar
如果程序是Spring Boot 或 Spring Cloud 应用,则可以通过 Spring 配置文件来指定配置。
访问http://localhost:8858/
输入上面启动命令中的用户密码,进入控制台主页面
客户端接入控制台
控制台启动后,客户端需要按照以下步骤接入到控制台。
- 引入JAR包:客户端需要引入 Transport 模块来与 Sentinel 控制台进行通信。在
pom.xml
引入 JAR 包:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
<version>1.8.4</version>
</dependency>
- 配置启动参数:启动时加入 JVM 参数
-Dcsp.sentinel.dashboard.server=consoleIp:port
指定控制台地址和端口。若启动多个应用,则需要通过-Dcsp.sentinel.api.port=xxxx
指定客户端监控 API 的端口(默认是 8719) - 触发客户端初始化:确保客户端有访问量,Sentinel 会在客户端首次调用的时候进行初始化,开始向控制台发送心跳包。注意:还需要根据应用类型和接入方式引入对应的 适配依赖,否则即使有访问量也不能被 Sentinel 统计。
如果是整合SpringCloud Alibaba加入
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>2021.0.1.0</version>
</dependency>
**本人博客网站 **IT小神 www.itxiaoshen.com