前言
该博客为Sentinel
学习笔记,主要目的是为了帮助后期快速复习使用
学习视频:7小快速通关SpringCloud
辅助文档:SpringCloud快速通关
源码地址:cloud-demo
一、简介
官网:https://sentinelguard.io/zh-cn/index.html
wiki:https://github.com/alibaba/Sentinel/wik
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel
以流量为切入点,从流量控制、流量路由、熔断降级、系统自适应过载保护、热点流量防护等多个维度保护服务的稳定性。
Sentinel
具有以下特征:
- 丰富的应用场景:
Sentinel
承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。 - 完备的实时监控:
Sentinel
同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。 - 广泛的开源生态:
Sentinel
提供开箱即用的与其它开源框架/库的整合模块,例如与Spring Cloud
、Apache Dubbo
、gRPC
、Quarkus
的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入Sentinel
。同时Sentinel
提供Java/Go/C++
等多语言的原生实现。 - 完善的 SPI 扩展机制:
Sentinel
提供简单易用、完善的SPI
扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。
1.1 架构
- 定义规则:由运维人员(
Ops
)定义流量控制和熔断降级规则。 - 存储规则:规则存储在配置中心(如
Nacos
或Zookeeper
)。 - 推送规则:
Sentinel Dashboard
从配置中心获取规则,并推送给各个应用的Sentinel Client
。 - Sentinel Client:每个应用集成
Sentinel Client
,根据推送的规则监控和管理资源(如方法调用)。
1.2 资源&规则
1.2.1 定义资源
资源是指在 Sentinel
中被控制的服务或操作,例如 API 接口、数据库查询、外部服务调用等都可以视为资源。
- 支持多种编程框架的自动适配,包括
Web Servlet
、Dubbo
、Spring Cloud
、gRPC
、Spring WebFlux
、Reactor
等,使得所有 Web 接口都可以被视作资源。 - 编程式:
SphU API
- 声明式:
@SentinelResource
1.2.2 定义规则
- 流量控制规则
- 熔断降级规则
- 系统保护规则
- 来源访问控制规则
- 热点参数规则
- 用户请求资源:
- 用户发起请求,访问某个资源(例如,调用一个 API 或访问一个 Web 页面)。
- Sentinel 检查:
Sentinel
对请求进行初步检查,确定是否需要应用流量控制规则。
- 应用规则:
Sentinel
根据预定义的规则(如流量控制、熔断降级等)对请求进行评估。
- 判断是否违反规则:
- 如果请求违反了预定义的规则(例如,请求量超过了设定的阈值),则进入下一步。
- 如果请求没有违反规则,则放行请求,允许其继续执行。
- 违反规则的处理:
- 如果请求违反了规则,
Sentinel
会抛出异常。
- 如果请求违反了规则,
- 兜底处理:
- 在抛出异常后,
Sentinel
会检查是否有兜底处理(fallback)机制。 - 兜底处理是一种备选方案,用于在请求被拒绝时提供替代服务或返回默认值。
- 在抛出异常后,
- 执行 fallback:
- 如果存在兜底处理机制,Sentinel 会执行相应的 fallback 方法,以提供备选服务或返回默认值。
- 默认错误处理:
- 如果没有兜底处理机制,
Sentinel
会返回默认错误,通知用户请求被拒绝。
- 如果没有兜底处理机制,
- 结束:
- 请求处理完成,无论是正常放行、执行 fallback 还是返回默认错误,流程结束。
一、快速开始
1.1 下载并启动sentinel-dashboard
- 下载控制台:https://github.com/alibaba/Sentinel/releases
- 启动命令:
java -jar sentinel.jar
【jar包名自行更换】 - 访问
http://localhost:8080/
,默认账号和密码都为sentinel
1.2 引入sentinel依赖
<!-- 服务保护Sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
1.3 配置连接
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080 # sentinel控制台地址
eager: true # 启动sentinel
1.4 启动微服务
在Sentinel 控制台 可以看到对应的微服务信息
1.5 初试Sentinel
在方法上加上@SentinelResource("xxx")
注解,将该方法标记为一个资源,并重启服务
@SentinelResource("createOrder")
@Override
public Order createOrder(Long productId, Long userId) {
// Product product = getProductFromRemoteWithLoadBalancerAnnotation(productId);
// 使用feign完成远程调用
Product product = productFeignClient.getProductById(productId);
Order order = new Order();
order.setId(1L);
// 总金额
order.setTotalAmount(product.getPrice().multiply(BigDecimal.valueOf(product.getNum())));
order.setUserId(userId);
order.setNickName("jyh");
order.setAddress("北京");
// 远程查询商品列表
order.setProductList(Arrays.asList(product));
return order;
}
发送一次请求
在Sentinel 控制台 >
簇点链路 >
点击刷新后,就可以看到调用链路的过程
注意:这里我们其实还有个OpenFeign
的资源没有显示
问题原因:OpenFeign
需要显式配置才能与 Sentinel
集成。若未启用 feign.sentinel.enabled
参数,Sentinel 无法监控 OpenFeign 接口。
feign:
sentinel:
enabled: true # 启用 Sentinel 对 OpenFeign 的支持
该问题详见上一篇博客:解决 Sentinel 控制台无法显示 OpenFeign 资源的问题
在这里我们可以对任意一个资源进行控制,这里简单测试一个流控规则
如果超过1秒一次的访问速度,就会报错,但这种错误对我们不太友好
二、异常处理
当访问的资源违背了访问规则,Sentinel
就会抛出BlockException
异常,该异常还有一些细节的实现类
这些异常由于资源方式的不同会有不同的处理机制
2.1 自定义 BlockExceptionHandler
当资源是一个web请求时,违背规则后,会被SentinelWebInterceptor
拦截,默认会被BlockExceptionHandler
处理,因此会看到如下错误提示:
因此,只需要自定义一个BlockExceptionHandler
,即可自定义错误格式
@Component
public class MyBlockExceptionHandler implements BlockExceptionHandler {
private ObjectMapper objectMapper = new ObjectMapper();
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
String resourceName, BlockException e) throws Exception {
response.setStatus(429); //too many requests
response.setContentType("application/json;charset=utf-8");
PrintWriter writer = response.getWriter();
R error = R.error(500, resourceName + " 被Sentinel限制了,原因:" + e.getClass());
String json = objectMapper.writeValueAsString(error);
writer.write(json);
writer.flush();
writer.close();
}
}
统一响应结果R
对象
@Data
public class R {
private Integer code;
private String msg;
private Object data;
public static R ok() {
R r = new R();
r.setCode(200);
r.setMsg("成功");
return r;
}
public static R ok(String msg, Object data) {
R r = new R();
r.setCode(200);
r.setMsg("成功");
r.setData(data);
return r;
}
public static R error() {
R r = new R();
r.setCode(500);
r.setMsg("失败");
return r;
}
public static R error(Integer code, String msg) {
R r = new R();
r.setCode(code);
r.setMsg(msg);
return r;
}
}
重启项目,并重新在Sentinel
控制台添加流控规则【Sentinel
默认将配置存储在内存中,项目重启所有配置会失效】
当我们快速访问,就可以得到自定义的更加友好的错误提示
2.2 blockHandler
当我们对添加了 @SentinelResource("createOrder")
注解的createOrder
添加了浏览控制,当违背规则会等到如下错误页:
当我们使用@SentinelResource("createOrder")
注解进行标注资源时,并没进行任何的异常处理,因此抛出的异常会被MVC
默认的异常处理机制处理。那该如何自定义错误格式呢?
只需要在@SentinelResource
注解上指定blockHandler
属性,指明兜底函数,即可
@SentinelResource(value = "createOrder",blockHandler = "createOrderFallback")
@Override
public Order createOrder(Long productId, Long userId) {
// Product product = getProductFromRemoteWithLoadBalanceAnnotation(productId);
//使用Feign完成远程调用
Product product = productFeignClient.getProductById(productId);
Order order = new Order();
order.setId(1L);
// 总金额
order.setTotalAmount(product.getPrice().multiply(new BigDecimal(product.getNum())));
order.setUserId(userId);
order.setNickName("zhangsan");
order.setAddress("尚硅谷");
//远程查询商品列表
order.setProductList(Arrays.asList(product));
return order;
}
//兜底回调
public Order createOrderFallback(Long productId, Long userId, BlockException e){
Order order = new Order();
order.setId(0L);
order.setTotalAmount(new BigDecimal("0"));
order.setUserId(userId);
order.setNickName("未知用户");
order.setAddress("异常信息:"+e.getClass());
return order;
}
重启项目,并重新在Sentinel 控制台
给createOrder
添加流控规则
当我们快速访问,就可以得到自定义的兜底数据
最佳实践:
@SentinelResource
注解一般标注在非Controler
层,一旦违反规则后,如果业务规定有兜底回调的数据就使用blockHandler
属性指定,如果没有规定,也可不用任何一种回调机制,直接让异常抛往全局,由SpringBoot
的全局异常处理器处理
2.3 OpenFeign - 兜底回调
- 开启熔断
feign:
sentinel:
enabled: true
- 编写 fallback 函数
@FeignClient(value = "service-product",fallback = ProductFeignClientFallback.class) // feign客户端
public interface ProductFeignClient {
@GetMapping("/product/{id}")
Product getProductById(@PathVariable("id") Long id);
}
@Component
public class ProductFeignClientFallback implements ProductFeignClient {
@Override
public Product getProductById(Long id) {
System.out.println("兜底回调....");
Product product = new Product();
product.setId(id);
product.setPrice(new BigDecimal("0"));
product.setProductName("未知商品");
product.setNum(0);
return product;
}
}
重启项目,并重新在Sentinel 控制台
给createOrder
添加流控规则
当我们快速访问,仍然可以得到自定义的兜底数据
2.4 SphU 硬编码【了解】
在我们的代码中的任何位置、任何代码块都可以使用SphU.entry("资源名");
进行控制资源。用这种方式,当资源违背了规则会抛出BlockException
。这个时候可以捕捉异常,进行相应的处理操作
@Override
public Order createOrder(Long productId, Long userId) {
// Product product = getProductFromRemoteWithLoadBalancerAnnotation(productId);
// 使用feign完成远程调用
Product product = productFeignClient.getProductById(productId);
Order order = new Order();
order.setId(1L);
// 总金额
try {
SphU.entry("hahaha");
order.setTotalAmount(product.getPrice().multiply(BigDecimal.valueOf(product.getNum())));
order.setUserId(userId);
order.setNickName("jyh");
order.setAddress("北京");
// 远程查询商品列表
order.setProductList(Arrays.asList(product));
} catch (BlockException e) {
// 资源访问阻止,被限流或者被降级
// 在此处进行相应的处理操作
}
return order;
}
三、规则 - 流量控制
Sentinel
流量控制的核心是通过设定规则,限制资源的访问频率或并发量,防止系统被过多请求压垮。
3.1 阈值类型
阈值类型分为:
- QPS:统计每秒请求数,底层使用计数器 【推荐、速度快】
- 并发线程数:统计并发线程数,底层引入了线程池
集群阈值模式分为: - 单机均摊:将集群的总阈值平均分配到每个节点上。每个节点独立承担一部分流量限制。
- 总体阈值:集群中的所有节点共享一个总阈值,
Sentinel
会实时统计整个集群的流量,并根据总阈值进行流控。
3.2 流控模式
调用关系包括调用方、被调用方;一个方法又可能会调用其它方法,形成一个调用链路的层次关系;有了调用链路的统计信息,我们可以衍生出多种流量控制手段。
3.2.1 直接策略
直接策略是 Sentinel
默认的流控模式,也是最简单的模式。它直接对当前资源的流量进行统计和控制,当流量达到设定的阈值时,会直接对当前资源进行限流
3.2.2 链路策略
链路策略是基于调用链路的流量控制模式。它只统计从指定链路访问当前资源的请求流量,并根据该流量是否超过阈值来决定是否对当前资源进行限流。
案例理解
这里createOrder
和seckill
方法都调用了业务层createOrder
方法创建订单
@RestController
@RequiredArgsConstructor
public class OrderController {
private final OrderService orderService;
// 创建订单
@GetMapping("/create")
public Order createOrder(@RequestParam("productId") Long productId,
@RequestParam("userId") Long userId) {
return orderService.createOrder(productId, userId);
}
@GetMapping("/seckill")
public Order seckill(@RequestParam("productId") Long productId,
@RequestParam("userId") Long userId) {
Order order = orderService.createOrder(productId, userId);
order.setId(Long.MAX_VALUE);
return order;
}
}
并在配置文件中将web-context-unify
设为false,不使用统一上下文
spring:
cloud:
sentinel:
web-context-unify: false # 不使用统一上下文
这是默认使用统一上下文的树状视图,如下图
不使用统一上下文的树状视图,如下图
新增流量控制,仅对/seckill
链路进行流量控制
实现效果:/create
链路无流量限制,/seckill
链路每秒只能发起一次请求
3.2.3 关联策略
关联策略允许根据与当前资源相关的另一个资源的流量来触发限流。当关联资源的流量达到设定阈值时,会对当前资源进行限流。
案例理解
模拟读/写数据库,操作同一个资源。实现当写数据库流量大时,才限制读数据库流量;写数据库流量小时,读数据库流量无限制(优先写)
@RestController
@RequiredArgsConstructor
public class OrderController {
/**
* 模拟读/写数据库,操作同一个资源。实现当写数据库流量大时,才限制读数据库流量;写数据库流量小时,读数据库流量无限制(优先写)
*/
@GetMapping("/writeDb")
public String writeDb() {
return "writeDb success....";
}
@GetMapping("/readDb")
public String readDb() {
return "readDb success....";
}
}
给/readDb
添加流量控制,关联/writeDb
资源
实现效果:当大量写入时,读取才受到流量限制
3.3 流控效果
3.3.1 快速失败(直接拒绝)
当流量达到设定的阈值时,直接拒绝请求,抛出 FlowException 异常。
3.3.2 Warm Up(预热/冷启动)
系统从低阈值开始,逐步增加允许的流量,直到达到设定的阈值。这种模式可以避免系统在冷启动时被瞬间的高流量打垮。
3.3.3 匀速排队
将请求以固定的速率通过,超出阈值的请求会排队等待,直到超时或被处理。
注意:只有快速失败支持流控模式(直接、关联、链路)的设置
四、规则 - 熔断降级
Sentinel
的熔断降级是一种保护机制,用于防止系统因某个服务的故障而崩溃。它通过监控服务的异常情况(如超时、错误率等),在达到预设条件时自动“熔断”(阻止)对该服务的调用,避免故障扩散。
4.1 断路器
- 关闭状态(Closed):正常放行请求,同时统计异常情况。
- 打开状态(Open):拒绝请求,直接返回降级逻辑(如错误信息),避免故障扩散。
- 半开状态(Half-Open):允许少量请求通过,根据结果决定是否恢复服务。
4.2 工作原理
- 初始状态(Closed 关闭)
- 断路器初始状态为关闭状态(Closed),此时所有请求都可以访问目标服务。
- 统计和监控
- 在关闭状态下,断路器会统计请求的异常比例和异常数。
statIntervalMs
(统计时长):指定了统计的时间窗口。minRequestAmount
(最小请求数):指定了在统计时间内最小的请求数量,只有当请求数量达到这个值时,统计数据才有效。
- 触发熔断(Open 打开)
- 如果在统计时间内,异常比例或异常数超过了预设的阈值(慢调用比例或异常比例),断路器会转换到打开状态(Open)。
- 在打开状态下,新的请求会被直接拒绝,以防止更多的请求发送到可能已经故障的服务。
- 熔断时长(timeWindow)
- 断路器在打开状态下会持续一段时间,这段时间称为熔断时长(
timeWindow
),在这段时间内,请求都会被拒绝。
- 断路器在打开状态下会持续一段时间,这段时间称为熔断时长(
- 半开状态(Half-Open 半开)
- 熔断时长结束后,断路器会进入半开状态(Half-Open),在这种状态下,断路器会允许一个或少量请求通过,以探测目标服务是否已经恢复。
- 探测结果
- 如果探测请求成功,断路器会认为服务已经恢复正常,然后转换回关闭状态(Closed),允许所有请求通过。
- 如果探测请求失败,断路器会再次转换到打开状态(Open),继续拒绝请求,并重新计时熔断时长。
4.3 熔断规则 - 熔断策略
4.3.1 慢调用比例
慢调用比例熔断策略基于请求的响应时间。如果请求的响应时间超过预设的阈值(慢调用阈值),则认为该请求是慢调用。当慢调用的比例(即慢调用请求数占总请求数的比例)在统计时间内超过设定的阈值时,熔断器会触发熔断。
4.3.2 异常比例
异常比例熔断策略基于请求的异常情况。如果请求返回了错误或异常,该请求被认为是异常的。当异常请求的比例(即异常请求数占总请求数的比例)在统计时间内超过设定的阈值时,熔断器会触发熔断。
4.3.3 异常数
异常数熔断策略基于请求的异常数量。当在统计时间内,异常请求的数量超过预设的阈值时,熔断器会触发熔断。这种策略不关心异常的比例,只关注异常请求的绝对数量。
4.3.4 有无熔断的异同点
- 无熔断规则:服务 A 每次都会尝试调用服务 B,只有在服务 B 返回错误后才执行
fallback
。 - 有熔断规则:一旦服务 B 出现异常,服务 A 会直接执行
fallback
,不再调用服务 B。
从图中可以看出,不管有无熔断都会执行fallback
[兜底函数],有熔断的系统速度更快、更健壮
五、规则 - 热点规则
热点规则是流控规则的一种
热点:经常访问的数据
注意:目前
Sentinel
自带的adapter
仅Dubbo
方法埋点带了热点参数,其它适配模块(如Web
)默认不支持热点规则,可通过自定义埋点方式指定新的资源名并传入希望的参数。注意自定义埋点的资源名不要和适配模块生成的资源名重复,否则会导致重复统计。
5.1 环境测试搭建
@RestController
@RequiredArgsConstructor
public class OrderController {
private final OrderService orderService;
@GetMapping("/seckill")
@SentinelResource(value = "seckill-order", blockHandler = "seckillFallback")
public Order seckill(@RequestParam(value = "productId",defaultValue = "1000") Long productId,
@RequestParam(value = "userId",required = false) Long userId) {
Order order = orderService.createOrder(productId, userId);
order.setId(Long.MAX_VALUE);
return order;
}
private Order seckillFallback(Long productId, Long userId, BlockException e) {
System.out.println("seckillFallback...");
Order order = new Order();
order.setId(productId);
order.setUserId(userId);
order.setAddress("异常信息:" + e.getClass());
return order;
}
}
5.2 热点参数限流
-
需求1:每个用户秒杀 QPS 不得超过 1(秒杀下单 userId 级别,userId是第二个参数)【参数索引从0开始】
实现效果:如果请求参数携带userId
参数,并且访问速度超过1秒1次,会被限流。
-
需求2:6号用户是vvip,不限制QPS(例外情况)
实现效果:如果请求参数携带userId
参数为6,不会被限流,但其他会被限流。 -
需求3:666号是下架商品,不允许访问
实现效果:尽管请求参数携带 userId
参数为6,不会被限流,但 productId
参数为666,被限制访问,最终效果被限制访问。
@SentinelResource
注解指定兜底函数小细节:
- 使用 fallback 属性:
指定兜底函数时,函数参数应为Throwable
,而不是BlockException
。- 使用 blockHandler 属性:
指定兜底函数时,函数参数应为BlockException
。- blockHandler 和 fallback 的区别
- fallback 更通用,用于处理各种服务调用失败的情况。
- blockHandler 专门用于处理 Sentinel 熔断器触发的情况,提供了更细粒度的控制。
五、规则 - 授权规则【了解】
Sentinel
的授权规则主要用于控制服务调用方的访问权限,通过设置白名单和黑名单来决定哪些来源的调用者可以访问特定的资源。具体来说:
- 白名单:只有白名单内的来源(origin)可以访问资源。
- 黑名单:黑名单内的来源(origin)不允许访问资源。
授权规则的配置包括资源名(即受保护的资源),流控应用(即调用方名单),以及授权类型(白名单或黑名单)。通过这种方式,Sentinel
可以对请求方来源进行判断和控制,以增强系统的安全性和稳定性。
六、规则 - 系统规则【了解】
Sentinel
系统规则是一种从应用级别的入口流量进行控制的规则,它基于单台机器的总体 Load、RT(响应时间)、入口 QPS(每秒查询数)、CPU 使用率和线程数等维度来监控应用数据,以确保系统在最大吞吐量下运行的同时保持整体稳定性。