概述
在微服务架构中,根据业务来拆分成一个个的服务,服务与服务之间可以通过 RPC 相互调用,在 Spring Cloud 中可以用 RestTemplate + LoadBalanceClient 和 Feign 来调用。为了保证其高可用,单个服务通常会集群部署。由于网络原因或者自身的原因,服务并不能保证 100% 可用,如果单个服务出现问题,调用这个服务就会出现线程阻塞,此时若有大量的请求涌入,Servlet 容器的线程资源会被消耗完毕,导致服务瘫痪。服务与服务之间的依赖性,故障会传播,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的 “雪崩” 效应。
为了解决这个问题,业界提出了熔断器模型。
阿里巴巴开源了 Sentinel 组件,实现了熔断器模式,Spring Cloud 对这一组件进行了整合。在微服务架构中,一个请求需要调用多个服务是非常常见的,如下图:
较底层的服务如果出现故障,会导致连锁故障。当对特定的服务的调用的不可用达到一个阀值熔断器将会被打开。
熔断器打开后,为了避免连锁故障,通过 fallback 方法可以直接返回一个固定值。
什么是 Sentinel
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式服务架构的流量控制组件,主要以流量为切入点,从流量控制、熔断降级、系统自适应保护等多个维度来帮助您保障微服务的稳定性。
官网:https://sentinelguard.io/zh-cn/
Sentinel的特性
- 丰富的应用场景: Sentinel 承接了阿里巴巴近 10 年的 双十一大促流量 的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、实时熔断下游不可用应用等。
- 完备的实时监控: Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
- 广泛的开源生态: Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。
- 完善的 SPI 扩展点: Sentinel 提供简单易用、完善的 SPI 扩展点。您可以通过实现扩展点,快速的定制逻辑。例如定制规则管理、适配数据源等。
hystrix与sentinel的区别
功能 | Sentinel | Hystrix |
---|---|---|
隔离策略 | 信号量隔离(并发线程数限流) | 线程池隔离/信号量隔离 |
熔断降级策略 | 基于响应时间、异常比率、异常数 | 基于异常比率 |
实时统计实现 | 滑动窗口(LeapArray) | 滑动窗口(基于 RxJava) |
动态规则配置 | 支持多种数据源 | 支持多种数据源 |
扩展性 | 多个扩展点 | 插件的形式 |
基于注解的支持 | 支持 | 支持 |
限流 | 基于 QPS,支持基于调用关系的限流 | 有限的支持 |
流量整形 | 支持预热模式、匀速器模式、预热排队模式(流量规则处可配置) | 不支持 |
系统自适应保护 | 支持 | 不支持 |
控制台 | 提供开箱即用的控制台,可配置规则、查看秒级监控、机器发现等 | 简单的监控查看 |
Sentinel客户端
- 添加依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
- 添加yml配置
spring:
cloud:
sentinel:
transport:
# 默认端口8719端口,假如被占用会自动从8719开始依次+1扫描,直到找到未被占用的端口
port: 8719
dashboard: 192.168.0.102:8080
这里的 spring.cloud.sentinel.transport.port 端口配置会在应用对应的机器上启动一个 Http Server,该 Server 会与 Sentinel 控制台做交互。比如 Sentinel 控制台添加了 1 个限流规则,会把规则数据 push 给这个 Http Server 接收,Http Server 再将规则注册到 Sentinel 中。
- 创建Controller测试
@RestController
@RequestMapping("/v1")
public class TestController {
@Autowired
private ConfigurableApplicationContext applicationContext;
@GetMapping(value = "/test")
public String test() {
return "port:" +applicationContext.getEnvironment().getProperty("server.port");
}
}
- 测试
访问:http://127.0.0.1:8000/v1/test
打开:打开Sentinel Dashboard 如下配置成功
Sentinel服务端
Sentinel 控制台提供一个轻量级的控制台,它提供机器发现、单机资源实时监控、集群资源汇总,以及规则管理的功能。您只需要对应用进行简单的配置,就可以使用这些功能。
注意: 集群资源汇总仅支持 500 台以下的应用集群,有大概 1 - 2 秒的延时。
下载安装
官方文档:https://sentinelguard.io/zh-cn/docs/dashboard.html
- 获取 Sentinel 控制台
- 方式一:从release 页面 下载最新版本的控制台 jar 包
- 方式二:从最新版本的源码自行构建 Sentinel 控制台
- 启动
注意:启动 Sentinel 控制台需要 JDK 版本为 1.8 及以上版本。
Sentinel 控制台是一个标准的 Spring Boot 应用,以 Spring Boot 的方式运行 jar 包即可。
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar
如若8080端口冲突,可使用 -Dserver.port=新端口 进行设置。
3. 访问控制台
http://ip:8080/
服务熔断(openfeign)
1. 添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2. 创建 Feign 接口
通过 @FeignClient("服务名") 注解来指定调用哪个服务。代码如下:
@FeignClient(value = "eagle-provider",fallback = TestFallback.class)
public interface TestService {
@GetMapping(value = "/test/{message}")
String test(@PathVariable("message") String message);
}
熔断处理方法
@Component
public class TestFallback implements TestService{
@Override
public String test(String message) {
return "服务降级---TestFallback";
}
}
服务eagle-provider提供的Controller接口如下:
@RestController
public class TestController {
@Value("${spring.application.name}")
private String name;
@Resource
private ConfigurableApplicationContext applicationContext;
/**
* 配置的动态更新测试
*/
@GetMapping(value = "/test/{message}")
public String test(@PathVariable String message) {
return name+" port:" +applicationContext.getEnvironment().getProperty("server.port")+" "+message;
}
}
3. Controller调用接口
@RestController
public class TestController {
@Resource
private TestService testService;
/**
* openfeign测试
*/
@GetMapping(value = "/feignTest")
public String feignTest() {
return testService.test("我是eagle-consumer");
}
}
流控规则
概述
流量控制(flow control),其原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。
一条限流规则主要由下面几个因素组成,我们可以组合这些元素来实现不同的限流效果:
- 资源名:唯名称,默认请求路径
- 针对来源: Sentine可以针对调用者进行跟流,填写微服务名,默idefaut (不区分来源)
- 阈值类型/单机阈值:
- QPS (每秒钟的请求数量):当调用该api的QPS达到阈值的时候,进行限流
- 线程数:当调用该api的线程数达到阈值的时候,进行限流
- 是否集群:不需要集群
- 流控模式:
- 直接: api达到限流条件时,直接限流
- 关联:当关联的资源达到阈值时,就限流自己
- 链路:对一条链路的访问进行控制(指定资源从入口资源进来的流量,如果达到阈值,就进行限流) [api级别的针对来源]
- 流控效果:
- 快速失败:直接失败,拋异常
- Warm Up:根据codeFactor (冷加载因子,默认3)的值,从阈值codeFactor,经过预热时长,才达到设的QPS阈值
- 排队等待:
测试接口准备
@GetMapping(value = "/testA")
public String testA() {
return "testA port:" +applicationContext.getEnvironment().getProperty("server.port");
}
@GetMapping(value = "/testB")
public String testB() {
return "testB port:" +applicationContext.getEnvironment().getProperty("server.port");
}
流控规则配置
官方文档:https://github.com/alibaba/Sentinel/wiki
1. QPS
当调用该api的QPS达到阈值的时候,进行限流
表示调用/v1/testA时,QPS的单机阀值大于1后面的请求直接失败。失败结果如下:
2. 线程数
当调用该api的线程数达到阈值的时候,进行限流
表示调用/v1/testA时,线程数的单机阀值大于1后面的请求直接失败。
比如a请求过来,处理很慢,在一直处理,此时b请求又过来了,
此时因为a占用一个线程,此时要处理b请求就只有额外开启一个线,那么就会报错。可以延时模拟此情况Thread.sleep(1000)。
直接失败结果如下:
3. 关联
当关联的资源达到阈值时,就限流自己
表示调用/v1/testB的QPS的单机阀值大于1,之后调用/v1/testA的请求直接失败。
使用postman模拟测试。
应用场景: 比如支付接口达到阈值,就要限流下订单的接口,防止一直有订单
4. 链路
对一条链路的访问进行控制(指定资源从入口资源进来的流量,如果达到阈值,就进行限流) [api级别的针对来源]
比方说,我有一个二叉树
服务A->服务B->服务D, 服务A->服务B->服务E, 服务A->服务C->服务E, 服务A->服务C->服务F均可视作链路。
假设服务A为入口资源,服务D为终点资源,对这条链路进行限制的话,则资源A,B,D均会被限制访问。
5. Warm up(预热)
- Wemm Up:根据codeFactor (冷加载因子,默认3)的值,从阈值/codeFactor,经过预热时长,才达到设置的QPS阈值。
系统初始化的阀值为10/ 3约等于3.,即阀值刚开始为3; 然后过了5秒后阀值才慢性升高恢到10
应用场景
如秒杀系统在开启的瞬间,会有很多流量上来,很有可能把系统打死,预热方式就是把为了保护系统,可慢慢的把流放进来,慢的把阀值增长到设置的阀值。
6. 排队等待
阀值类型必须设成QPS,否则无效。
表示调用/v1/testA时,QPS的单机阀值大于1,请求以QPS=1匀速通过,QPS的单机阀值大于1的请求排队等候,排队等候的超市时间为2000ms。
降级规则
概述
除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。由于调用关系的复杂性,如果调用链路中的某个资源不稳定,最终会导致请求发生堆积。Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)。
降级策略
-
RT (平均响应时间,秒级):当 1s 内持续进入 N 个请求,对应时刻的平均响应时间(秒级)均超过阈值(count,以 ms 为单位),那么在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地熔断(抛出 DegradeException)。
注意 Sentinel 默认统计的 RT 上限是 4900 ms,超出此阈值的都会算作 4900 ms,若需要变更此上限可以通过启动配置项 -Dcsp.sentinel.statistic.max.rt=xxx 来配置。 -
异常比列(秒级):当资源的每秒请求量 >= N(可配置),并且每秒异常总数占通过量的比值超过阈值(DegradeRule 中的 count)之后,资源进入降级状态,即在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
-
异常数(分钟级):当资源近 1 分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若 timeWindow 小于 60s,则结束熔断状态后仍可能再进入熔断状态。
即时间窗口时间配置要大于60秒
测试接口准备
@GetMapping(value = "/testC")
public String testC() {
// 模拟异常
int a = 1/0;
return "testC port:" +applicationContext.getEnvironment().getProperty("server.port");
}
@GetMapping(value = "/testD")
public String testD() {
// 模拟业务处理时间一份钟
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "testD port:" +applicationContext.getEnvironment().getProperty("server.port");
}
降级规则配置
RT (平均响应时间,秒级)
表示调用/v1/testD时,此时的平均响应时间大于200ms则触发熔断,3秒之后恢复正常。
测试:
jmeter以每秒10个线程访问/v1/testD,由于代码里面模拟业务睡眠了一秒,平局响应时间肯定大于降级策略的200ms,触发熔断,3秒之内访问/v1/testD,仍触发熔断,jmeter停止访问,3秒后正常访问。
异常比列(秒级)
表示调用/v1/testC时,此时的异常比例大于0.2(即20%)则触发熔断,3秒之后恢复正常。
异常数(分钟级)
表示调用/v1/testC时,一分钟内的异常数大于5则触发熔断,65秒之后恢复正常。
热点规则
概述
何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:
商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制
用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
Sentinel 利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。热点参数限流支持集群模式。
兜底方法
- 系统默认:限流出问题后,sentine|系统默认的提示:Blocked by Sentinel (flow limiting)
- 用户自定义:使用@SentinelResource直接实现降级方法,它等同Hystrix的@HystrixCommand同理
测试接口准备
@GetMapping("/testHotKey")
@SentinelResource(value="testHotKey",blockHandler="deal_testHotKey")
public String testHotKey(@RequestParam(value="p1",required=false) String p1,
@RequestParam(value="p2",required=false) String p2){
return "-----deal_testHotKey";
}
public String deal_testHotKey(String p1,String p2, BlockException exception){
return "-----------deal_testHotKey----༼ ༎ຶ ෴ ༎ຶ༽"; //系统默认提示:Blocked by Sentinel (flow limiting)
}
热点规则配置
表示访问/v1/testHotKey时,带上只要带了第一个参数并且QPS>1时触发熔断,1秒(统计窗口时长)之后恢复正常。测试结果如下:
热点规则配置---参数例外项
普通:指定参数p1的QPS>N(配置)会被限流
特例:指定参数p1的QPS>N(配置)会被限流,但是p1参数是某个特殊值的时候不会被限流
测试p1=“3”,jmeter每秒的qps=11,postman测试触发熔断
注意:
如果我们程序出现异常,是不会走blockHander的降级方法的,因为这个方法只配置了热点规则,没有配置限流规则。
我们这里配置的降级方法是sentinel针对热点规则配置的,只有触发热点规则才会降级。
系统规则
概述
系统保护规则是从应用级别的入口流量进行控制,从单台机器的 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为例:
表示单台机器上的QPS大于1时触发熔断。
测试下面任意接口QPS大于1都会触发熔断。
http://127.0.0.1:8100/v1/testA
http://127.0.0.1:8100/v1/testB
@SentinelResource注解
注意:注解方式埋点不支持 private 方法。
@SentinelResource 用于定义资源,并提供可选的异常处理和 fallback 配置项。 @SentinelResource 注解包含以下属性:
- value:资源名称,必需项(不能为空)
- entryType:entry 类型,可选项(默认为 EntryType.OUT)
- blockHandler / blockHandlerClass: blockHandler 对应处理 BlockException 的函数名称,可选项。blockHandler 函数访问范围需要是 public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException。blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
- fallback / fallbackClass:fallback 函数名称,可选项,用于在抛出异常的时候提供fallback处理逻辑。fallback函数可以针对所有类型的异常(除了exceptionsToIgnore 里面排除掉的异常类型)进行处理。fallback 函数签名和位置要求:
- 返回值类型必须与原函数返回值类型一致;
- 方法参数列表需要和原函数一致,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
- fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
- defaultFallback(since 1.6.0):默认的 fallback 函数名称,可选项,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。默认 fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。若同时配置了 fallback 和 defaultFallback,则只有 fallback 会生效。defaultFallback 函数签名要求:
- 返回值类型必须与原函数返回值类型一致;
- 方法参数列表需要为空,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
- defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
- exceptionsToIgnore(since1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。
注:1.6.0 之前的版本 fallback 函数只针对降级异常(DegradeException)进行处理,不能针对业务异常进行处理。
特别地,若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑。若未配置 blockHandler、fallback 和 defaultFallback,则被限流降级时会将 BlockException 直接抛出(若方法本身未定义 throws BlockException 则会被 JVM 包装一层 UndeclaredThrowableException)。
示例:
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;
}
}
从 1.4.0 版本开始,注解方式定义资源支持自动统计业务异常,无需手动调用 Tracer.trace(ex) 来记录业务异常。Sentinel 1.4.0 以前的版本需要自行调用 Tracer.trace(ex) 来记录业务异常。
限流
按照Url地址限流
@GetMapping("/testF")
public String testF(){
return "-----testF正常访问------";
}
熔断之后为sentinel默认页面
按照资源名称限流
@GetMapping("/testG")
@SentinelResource(value ="testG",blockHandler = "testGBlockHandler")
public String testG(){
return "-----testG正常访问------";
}
public String testGBlockHandler(BlockException exception){
//系统默认提示:Blocked by Sentinel (flow limiting)
return "------testGBlockHandler-----触发熔断----༼ ༎ຶ ෴ ༎ຶ༽";
}
解耦兜底方法
@GetMapping("/testH")
@SentinelResource(value ="testH",blockHandlerClass = TestHandler.class,blockHandler = "testHBlockHandler" )
public String testH(){
return "-----testH正常访问------";
}
public class TestHandler {
public static String testHBlockHandler(BlockException exception){
return "-----------触发熔断----༼ ༎ຶ ෴ ༎ຶ༽"; //系统默认提示:Blocked by Sentinel (flow limiting)
}
}
fallback和blockHandler区别
@GetMapping("/testG")
@SentinelResource(value ="testG",blockHandler = "testGBlockHandler",fallback = "testGFallback")
public String testG(@RequestParam(value="p1",required=false) String p1){
if (p1.equals("1")){
throw new RuntimeException("1111");
}
return "-----testG正常访问------";
}
public String testGBlockHandler(String p1,BlockException ex){
// Do some log here.
ex.printStackTrace();
return "------testGBlockHandler-----触发熔断----༼ ༎ຶ ෴ ༎ຶ༽"; //系统默认提示:Blocked by Sentinel (flow limiting)
}
public String testGFallback(String p1){
return "-----testGFallback------触发熔断----༼ ༎ຶ ෴ ༎ຶ༽";
}
-
fallback是用于管理异常的,当业务方法发生异常,可以降级到指定方法
-
blockHandler只对sentienl定义的规则降级
-
同时配置fallback和blockHandler,两者都会生效,触发降级时blockhandler优先生效
exceptionsToIgnore指定一个异常类,
表示如果当前方法抛出的是指定的异常,不降级,直接对用户抛出异常
规则持久化
Sentinel Dashboard中添加的规则是默认存储在内存中的,重启应用或者sentinel规则就会消失。
官方地址:https://github.com/alibaba/spring-cloud-alibaba/wiki/Sentinel
持久化配置到Nacos
- 添加依赖
<!-- 持久化sentinel规则到nacos -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
- 修改配置
spring:
cloud:
sentinel:
datasource:
ds1:
nacos:
server-addr: ${spring.cloud.nacos.discovery.server-addr}
namespace: ${spring.cloud.nacos.config.namespace}
data-id: ${spring.application.name}
group-id: DEFAULT_GROUP
data-type: json
rule-type: flow
- nacos中添加配置
json中,这些属性的含义:
resource:资源名称;
limitApp:来源应用;
grade:阈值类型,0表示线程数,1表示QPS;
count:单机阈值;
strategy:流控模式,0表示直接,1表示关联,2表示链路;
controlBehavior:流控效果,0表示快速失败,1表示Warm Up, 2表示排队等待;
clusterMode:是否集群。