Sentinel
# 流量保护
官⽹:https://sentinelguard.io/zh-cn/index.html
wiki:https://github.com/alibaba/Sentinel/wiki
Sentinel 具有以下特征:
丰富的应用场景:Sentinel 承接了阿⾥巴巴近 10 年的双⼗⼀⼤促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
⼴泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 SpringCloud、Apache Dubbo、gRPC、Quarkus 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。同时 Sentinel 提供 Java/Go/C++ 等多语言的原生实现。
完善的 SPI 扩展机制:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。
# 资源&规则
定义资源:
- 主流框架⾃动适配(Web Servlet、Dubbo、Spring Cloud、gRPC、Spring WebFlux、Reactor);所有Web接口均为资源
- 编程式:SphU API
- 声明式:@SentinelResource
定义规则:
- 流量控制规则
- 熔断降级规则
- 系统保护规则
- 来源访问控制规则
- 热点参数规则
# 环境搭建
# 依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
# 启动控制台
java -jar sentinel.jar
# 配置连接(懒加载)
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080
eager: true
默认是懒加载,只有访问时才会在控制台显示资源,可以通过eager指定为启动即加载
使用@SentinelResource注解表示监控该资源
# 异常处理
一个规则对应一个异常
- 流量控制规则
- 熔断降级规则
- 系统保护规则
- 来源访问控制规则
- 热点参数规则
# Web接口
当Web接口(Controller接口)访问被拒绝(因为资源控制)后会在BlockExceptionHandler返回异常
# 自定义 BlockExceptionHandler
@Component
public class MyBlockExceptionHandler implements BlockExceptionHandler {
private ObjectMapper objectMapper = new ObjectMapper();
@Override
public void handle(HttpServletRequest esponse 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();
}
}
由于当前的规则存储在内存中,每次重启都需要重新配置流量控制规则
# @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;
}
@SentinelResource需要通过blockHandler指定一个兜底回调,在兜底回调中使用BlockException参数捕获并处理异常,不指定会将异常向上抛,也可以在SpringBoot全局异常处理器(@RestControllerAdvice)中处理异常
# openFeign——兜底回调
@FeignClient(value = "service-product",fallback = ProductFeignClientFallback.class) // feign客户端
public interface ProductFeignClient {
//mvc注解的两套使用逻辑
//1、标注在Controller上,是接受这样的请求
//2、标注在FeignClient上,是发送这样的请求
@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;
}
}
请求失败会触发兜底回调ProductFeignClientFallback,如果没有会将异常向上抛出到springboot全局异常处理器处理
# SphU硬编码
try{
Entry entry = SphU.entry("haha");
}catch(BlockException e){
}
获取资源haha,获取失败会抛出异常BlockException
# 总结
在sentinel控制台中配置好规则后,违反规则就会调用我们编写的异常处理回调,如果没有则调用全局异常处理器
# 规则
# 流控规则——流量控制
Sentinel控制台中显示的资源名包括:
自动检测到的Web路径请求
使用@SentinelResource指定的资源名称
远程调用的完整请求描述
# 阈值类型
- QPS:使用计数器来统计通过的请求数量,轻量级,性能好
- 并发线程数:底层使用线程池来控制线程数,引入了线程池,性能不好,适用于控制线程数量情况
# 集群阈值模式
- 单机均摊:设置每个机器都是同一个阈值
- 总体阈值:设置所有的机器加起来的阈值
# 流控模式

直接策略:直接控制资源A的访问量
链路策略:控制B通过链路C的访问量,通过资源A不限制,通过资源B限制
- 通过sentinel.web-context-unify: false 设置不统一web上下文,分割请求链路,才能对不同链路进行不同的资源请求限制
关联策略:根据写的访问量控制读的访问量,写多时限制读
# 流控效果

- 直接拒绝
- Warm Up:period预热时间,一开始只启动1/3的QPS,period时间内提升到所有的QPS
- 匀速排队:超过Timeout没有排队成功会被丢弃
使用Warm Up、匀速排队后流控模式会被忽略 即只有直接策略能生效
# 熔断规则——熔断降级
当被调用不稳定时,为了避免因为积压等待导致服务雪崩,,会认为被调用方不可用,快速切断联系,不去调用
熔断降级一般在调用方设置
# 断路器

# 工作原理
断路器默认是关闭状态,设置一个慢调用比例,当统计时长statIntervalMs内达到最小请求数minRequestAmount时进行统计异常数(超时没有返回结果)当异常比例达到慢调用比例70%时会认为对方不稳定,将断路器打开,经过熔断时长timeWindow后会进入半开状态(会放行一个请求进行探测),如果请求调用成功,则断路器回复关闭状态,否则保持打开直到探测成功
# 熔断策略
慢调用比例
分别设置
- 返回时间RT、慢调用比例、熔断时长timeWindow、最小请求数minRequestAmount、统计时长statIntervalMs
异常比例
- 当远程调用出现异常时会有兜底回调,虽然每次调用都能兜底回调,但是会影响系统性能,使用熔断策略异常比例可以在异常多时熔断 提高系统响应速度
异常数:统计时长内出现一定的异常数会触发熔断
# 热点规则——热点参数

使用热点参数可以满足以下需求
- 每个用户秒杀下单QPS不能超过1
- vip用户不限制
- 666号 是下架商品,不能访问
注意:目前Sentinel自带的adapter仅Dubbo方法埋点带了热点参数,其它适配模块(如Web)默认不支持热点规则,可通过自定义埋点方式指定新的资源名并传入希望的参数。注意自定义埋点的资源名不要和适配模块生成的资源名重复,否则会导致重复统计,
如
@GetMapping("/seckill")
@SentinelResource(value = "seckill-order", fallback = "seckillFallback")
public Order seckill(@RequestParam("userId") Long userId,
@RequestParan("productId") Long productId) {
Order = orderService.createorder(productId, userId);
order.setId(Long.MAX_VALUE);
return order;
}
public Order seckillFallback(Long userId, Long productId, BlockException exception) {
System.out.println("seckillFallback...");
Order order = new Order();
order.setId(productId);
order.setuserId(userId);
order.setAddress("异常信息:" + exception.getclass()):
return order;
}
GetMapping不支持热点规则,需要自定义埋点@SentinelResource,资源名不要重复seckill,设置为seckill-order
如果设置Required=false可以不携带参数,则不携带参数时不会被流控
# 设置热点规则

- 索引从0开始,设置每个用户id(索引0)每秒只能通过1个请求
- 设置例外项:vip不限制访问
再添加一个热点规则

- 设置可以访问商品id(索引1)
- 禁止访问666号商品
# fallback与blockHandler兜底回调
@GetMapping("/seckill")
@SentinelResource(valve = "seckill-order", fallback = "seckillFallback")
public Order seckill(@RequestParam(value = "userId", required = false) Long userId,
@RequestParam(value = "productId", defaultValve = "1000") Long productId) {
Order order = orderService.createOrder(productId, userId);
order.setId(Long.MAX_VALUE);
return order;
}
在测试热点限流的时候,在这个方法里边,虽然我们写了fallback兜底回调,但是没有被调用,而是返回了默认的错误页,原因在于兜底回调有两种写法,blockHandler和fallback。 而@SentinelResource注解,处理异常的规则是有blockHandler优先使用blockHandler,没有指定blockHandler才走fallback, 也就是没有调用我们写的fallback处理逻辑,但是如果我们把fallback改为blockHandler是可以的正常返回,
blockHandler可以专门处理被流控的异常,而fallback比block的最大好处,就是它还能处理业务异常,业务异常不属于blockException,我们只需要把fallback回调方法的签名blockException改为Throwable,public Order seckillFallback(Long userId,Long productId,BlockException->Throwable exception){}
就可以处理业务异常
# 总结
配置的规则都是存储在内存中的,可以结合nacos配置在nacos中,nacos连接数据库实现持久化
流控规则、熔断规则、兜底回调blockHandler、fallback