Appearance
Sentinel
Sentinel · alibaba/spring-cloud-alibaba Wiki · GitHub
如何使用 · alibaba/Sentinel Wiki · GitHub
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维护保护服务的稳定性
特征
- 丰富的应用场景:秒杀的突发流量控制在可承受范围内,消息的削峰填谷、实时熔断下游不可用应用等
- 完备的实时监控:提供实时的监控功能,可以在控制台看到接入应用的单台机器秒级数据,甚至500台以下规模的集群的汇总运行情况
- 广泛的开源生态:提供开箱即用的与其他开源框架/库的整合模块。例如与Spring Cloud、dubbo、gRPC的整合,我们只需要引入响应的依赖并进行简单的配置即可快速接入Sentinel
- 完善的SPI扩展点:提供简单易用、完善的SPI扩展点,可以通过实现扩展点,快速定制逻辑。例如:定制规则管理、适配数据源deng
Sentinel和Hystrix对比
快速开始
Sentinel进行资源保护的几个步骤
- 定义资源
- 定义规则
- 检验规则是否生效
单体保护【在spring-boot-demo项目】
硬编码方式
- 引入依赖
implementation group: 'com.alibaba.csp', name: 'sentinel-core', version: '1.8.1'
- 定义受保护资源【接口】及流控规则
package com.huangjiliang.sentinel.controller;
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.Tracer;
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.GetMapping;
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("/resource")
public class ResourceController {
private static final String RESOURCE_NAME = "test1";
/**
* 1.定义资源
* @return
*/
@GetMapping("test1")
public String test1() {
Entry entry = null;
try {
// 资源名可以使用任意有业务予以的字符串来定义,比如方法名称、接口名称或者其他唯一表示的字符串
entry = SphU.entry(RESOURCE_NAME);
// 受保护的业务逻辑
String msg = "hello sentinel";
System.out.println("保护你,没话说!");
return msg;
// 资源访问阻止,被限流或者被降级
} catch (BlockException be) {
System.out.println("BlockException: " + be.getLocalizedMessage());
} catch (Exception e) {
System.out.println("exception: " + e.getLocalizedMessage());
Tracer.traceEntry(e, entry);
} finally {
if (entry != null) {
entry.exit();
}
}
return null;
}
/**
* 2.定义流控规则
* @PostConstruct 服务启动之后,指定该方法
*/
@PostConstruct
private static void initFlowRules() {
List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule();
// 设置受保护的资源
rule.setResource(RESOURCE_NAME);
// 设置流控规则 QPS
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
// 设置受保护的资源阈值。每秒请求1次
rule.setCount(1);
rules.add(rule);
FlowRuleManager.loadRules(rules);
}
}
- 流控规则校验
访问:http://localhost:9007/sentinel/resource/test1
- 当1秒内刷新接口多次就会进入BlockException异常
- 总结
- 业务侵入性很强,需要在controller中写入非业务代码
- 配置不灵活,若需要添加新的受保护资源,需要手动添加init方法来添加流控规则
@SentinelResouce注解实现
- @SentinelResouce注解用来表示资源是否被限流或者降级
- blockHandler:定义当资源内部发生了BlockException应该进入的方法(捕获的是Sentinel定义的异常)
- fallback:定义的是资源内部发生的Throwable应该进入的方法
- exceptionToIgnore:配置fallback可以忽略的异常
- 添加依赖
implementation group: 'com.alibaba.csp', name: 'sentinel-annotation-aspectj', version: '1.8.1'
- 注入切面支持
java
@Bean
public SentinelResourceAspect sentinelResourceAspect() {
return new SentinelResourceAspect();
}
- 定义保护资源&限流熔断降级处理异常类及方法
java
@GetMapping("/test2")
@SentinelResource(value = "resource-test2", fallbackClass = ExceptionUtils.class, fallback = "handleTest2Fallback", blockHandlerClass = ExceptionUtils.class, blockHandler = "handleTest2Exception")
private String test2() {
return "hello sentinel! this is test2";
}
java
package com.huangjiliang.sentinel.util;
import com.alibaba.csp.sentinel.slots.block.BlockException;
public class ExceptionUtils {
public static String handleTest2Exception(BlockException blockException) {
return "test2 is throw blockException";
}
public static String handleTest2Fallback(Throwable throwable) {
return "test2 is throw exception";
}
}
使用
sentinel-dashboard.jar
控制台定制流控规则启动命令:
nohup java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar > sentinel-log.txt
- 如若8080端口冲突,可使用
-Dserver.port=新端口
进行设置。 -Dsentinel.dashboard.auth.username=sentinel
用于指定控制台的登录用户名为 sentinel;-Dsentinel.dashboard.auth.password=123456
用于指定控制台的登录密码为 123456;如果省略这两个参数,默认用户和密码均为sentinel;-Dserver.servlet.session.timeout=7200
用于指定 Spring Boot 服务端 session 的过期时间,如7200 表示 7200 秒;60m 表示 60 分钟,默认为 30 分钟;- 更多配置清单请看官网介绍:Sentinel · alibaba/spring-cloud-alibaba Wiki · GitHub
- 如若8080端口冲突,可使用
访问:
http://192.168.1.6:8080
, 默认账号密码:sentinel/sentinel
登陆后的界面如下
客户端增加整合sentinel控制台依赖
implementation group: 'com.alibaba.csp', name: 'sentinel-transport-simple-http', version: '1.8.1'
客户端启动项目时增加VM Option参数,指定连接到控制台:
‐Dcsp.sentinel.dashboard.server=192.168.1.6:8080
注意:Sentinel 会在客户端首次调用的时候进行初始化,开始向控制台发送心跳包,所以要确保客户端有访问量【即请求接口一次,在刷新控制台即可】;
SpingCloudAlibaba整合sentinel
- 添加依赖
xml
<!-- sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
- bootstrap.yml增加sentinel相关配置
yaml
spring:
profiles:
active: prod
cloud:
nacos:
discovery:
# 使用nginx代理nacos集群时,注意此端口必须时配置stream端口再减1000,我这里的nginx代理时10020端口
server-addr: 192.168.1.6:9020
username: nacos
password: nacos
config:
server-addr: 192.168.1.6:9020
username: nacos
password: nacos
file-extension: yaml
sentinel:
transport:
# sentinel控制台地址
dashboard: 192.168.1.6:8080
# 指定应用与sentinel控制台交互的端口,应用会在本地启一个端口占用的HttpServer
port: 8719
# 客户端IP地址
client-ip: 192.168.1.4
- order服务增加一个测试sentinel接口
java
@GetMapping("sentinelDemo")
public String sentinelDemo() {
System.err.println("=====> sentinelDemo");
return "success test sentinel";
}
- 访问接口:
http://localhost:9501/order/sentinelDemo
- 目的是将接口访问刷新到sentinel控制台
- 查看sentinel控制台信息,发现
/order/sentinelDemo
接口已存在
- 在sentinel控制台配置流控规则。可以在簇点链路对应接口的右侧操作点击流控按钮;也可以在流控规则新建流控
- 资源名:接口的API
- 针对来源:默认是default,当多个微服务都调用这个资源时,可以配置微服务名来指定微服务设置阈值
- 阈值类型:QPS(当阈值设置为10时,访问接口次数大于10时,就会限流)、线程数(当阈值设置为10时,为接收请求该资源分配的线程数大于10就进行限流)
- 测试。一秒内刷新接口大于2次【我设置流控阈值为1】接口就会返回
Blocked by Sentinel (flow limiting)
接口查看流控规则
- 添加actuator依赖
xml
<!-- actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
- bootstrap.yml添加actuator暴露配置
yaml
management:
endpoints:
web:
exposure:
include: '*'
- 访问:
http://localhost:9501/actuator/sentinel
查看流控配置规则
Sentinel控制台
实时监控
监控接通过的QPS和拒绝的QPS
TODO
- 虚拟机部署sentinel,本地【windows】配置时,实时监控看不到为空,看不到图标信息
- 查看sentinel日志:报错
2022-07-28 15:50:57.063 ERROR 84363 --- [pool-2-thread-1] c.a.c.s.dashboard.metric.MetricFetcher : Failed to fetch metric from <http://192.168.1.4:8719/metric?startTime=1658994649000&endTime=1658994655000&refetch=false> (ConnectionException: Connection refused)
问题未解决【初步怀疑网络不通,但是在虚拟机使用curl 连接http://192.168.1.4:8719/metric?startTime=1658994649000&endTime=1658994655000&refetch=false
是有返回数据;以及使用telnet192.168.1.4 8719
也是通】。 - 此问题未得到解决
- 当将sentinel部署在和客户端一致的IP,没有以上问题
簇点链路
用来显示微服务所监控的API接口
流控规则
流量控制【flow control】,其原理是监控应用流量的QPS或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮系统【秒杀、大促等场景】,从而保障应用的高可用性
- 应对洪峰流量:秒杀、大促
- 消息型场景:削峰填谷
- 付费系统:根据使用流量多少限制
同一个资源可以创建多条限流规则。FlowSlot会对该资源的所有限流规则依次遍历,知道有规则触发限流或者所有规则遍历完毕。一条限流规则主要由下面几个因素组成。我们可以组合这些元素来实现不同的限流效果
field | 说明 | 默认值 |
---|---|---|
resource | 资源名时限流规则的作用对象 | |
count | 限流阈值 | |
grade | 限流阈值类型,QPS模式(1)或并发线程数模式(0) | QPS模式 |
limitApp | 流控针对的调用来源 | default,代表不区分调用来源 |
strategy | 调用关系限流策略:直接、链路、关联 | 根据资源本身(直接) |
controlBehavior | 流控效果(直接拒绝/WarmUp/匀速+排队等待),不支持调用关系限流 | 直接拒绝 |
clusterMode | 是否集群限流 | 否 |
限流阈值类型
QPS(Query per second):每秒请求数,就是说服务器在疫苗的时间内处理了多少个请求
并发线程数
并发数控制用于保护业务线程池不被慢调用耗尽。
例如:当应用所依赖的的下游应用由于某种原因导致服务不稳定,响应延迟增加,对于调用者来说,意味着吞吐量下降和更多的线程数占用,在极端情况下甚至线程池耗尽。
为应对太多线程占用的情况,业内有使用隔离的方案,比如:通过不同业务逻辑使用不同线程池来隔离业务自身之间的资源争抢(线程池隔离)。这种隔离方案虽然隔离性比较好,但是代价就是线程数目太多,线程池上下文切换的overhead比较大,特别时对低延迟的调用有比较大的影响
sentinel并发控制下不负创建和管理线程池,而是简单统计当前请求上下文的线程数目(正在执行的调用数目),如果超出阈值,新的请求会被拒绝,效果类似于信号量隔离,并发数控制通常在调用端进行配置
流控模式
基于调用关系的流量控制,调用关系包括调用方,被调用方;一个方案可能会调用其他方案,形成调用链路的层次关系
- 直接:资源调用达到设置的阈值后直接被流控抛出异常
- 关联:当两个资源具有争抢或者依赖关系的时候,这两个资源便具有了关联。比如对数据库同一个字段的读写操作存在争抢,读的速度过高会影响写的速度,写的速度过高会影响读速度。如果放任读写操作争抢资源,则争抢本身带来的开销会降低整体的吞吐量。可使用关联限流来避免具有关联的资源之间过度的争抢
- 比如:
/order/get
和/order/add
两个资源分别代表对数据库的读写,我们可以给/order/get
设置限流规则来达到写优先的目的:设置流控模式为关联,并同时设置关联资源为/order/add
。这样当写操作过于频繁时,读数据的请求就会被限流。
- 比如:
- 链路:根据调用链路入口限流。如下例子:
/order/sentinelDemo1
和/order/sentinelDemo2
都请求了业务层的sentinelDemo方法,两个接口同时争抢业务层sentinelDemo方法资源,我们需要对/order/sentinelDemo2
进行流控,对/order/sentinelDemo1
不限制
OrderMsgService
业务层添加业务代码:使用注解@SentinelResource
对方法配置生命流控资源
java
@SentinelResource(value = "sentinelDemo")
public String sentinelDemo() {
return "success";
}
- 控制层添加两个测试接口,并注入业务层
java
@Autowired
private StockFeignService stockFeignService;
@GetMapping("sentinelDemo1")
public String sentinelDemo1() {
System.err.println("=====> sentinelDemo1");
orderMsgService.sentinelDemo();
return "success test sentinel sentinelDemo1";
}
@GetMapping("sentinelDemo2")
public String sentinelDemo2() {
orderMsgService.sentinelDemo();
System.err.println("=====> sentinelDemo2");
return "success test sentinel sentinelDemo2";
}
配置文件关闭链路收敛:
spring.cloud.sentinel.web‐context‐unify: false
控制台配置,如下如
- 测试:
- 访问
/order/sentinelDemo2
,1秒内访问超过1此就会被流控。包500错误,所以需要在业务层sentinelDemo方法的注解@SentinelResource
配置相关流控是返回的信息。这个链路流控全局异常无法捕获到BlockException,只能在此注解配置 - 访问
/order/sentinelDemo1
,不会被流控
- 访问
流控效果
- 快速失败(RuleConstant.CONTROL_BEHAIOR_DEFAULT):默认的流控方式
当QPS超过任意规则的阈值后,新的请求就会被立即拒绝,拒绝方式为抛出FlowException。这种方式适用于对协同处理能力明确已知的情况下,比如通过压测确定了系统的准确水平
- Warm Up(激增流量)(RuleConstant.CONTROL_BEHAVIOR_WARN_UP):预热/冷启动方式,当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬时间把系统压垮。通过”冷启动“,让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上线,给冷系统一个预热的时间,避免冷系统被压垮
冷加载因子:codeFactor默认是3,即请求QPS从threshold/3开始,经预热时长逐渐升至设定的QPS阈值
- 排队等待(匀速排队)(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER):此方式会严格控制通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。【暂时不支持QPS>1000的场景】
这种方式主要用于处理间隔性突发流量,例如消息队列。想象以下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空先状态,我们系统系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求
降级规则
sentinel除了流量以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。我们需要对不稳定的弱依赖服务进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置
熔断降级与隔离
保护自身的手段
- 并发控制(信号量隔离)
- 基于慢调用比例熔断
- 基于异常比例熔断
触发熔断后的处理逻辑
- 提供fallback实现(服务降级)
- 返回错误result
- 读缓存(DB访问降级)
熔断降级规则说明
Field | 说明 | 默认值 |
---|---|---|
resource | 资源名,即规则的作用对象 | |
grade | 熔断策略,支持慢调用比例/异常比例/异常数策略 | 慢调用比例 |
count | 慢调用比例模式下为慢调用临界RT(超出该值为慢调用);异常比例/异常数模式下为对应的阈值 | |
timeWindow | 熔断时长,单位为s | |
minRequestAmount | 熔断触发的最小请求数,请求数小于该值时即使异常比例超出阈值也不会熔断(1.7.0引入) | 5 |
statIntervalMs | 统计时长(单位ms),如60*1000代表分钟级(1.8.0引入) | 1000ms |
slowRatioThreshold | 慢调用比例阈值,仅慢调用,模式有效(1.8.0引入) |
熔断策略
慢调用比例(SLOW_REQUEST_RATIO)
选择以慢调用比例作为阈值,需要设置允许的慢调用TR(即最大的响应时间),请求的响应时间大于该阈值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测回复状态(HALF-OPEN状态),若接下来的一个请求响应时间小于设置的慢调用RT则结束熔断,若大于设置的慢调用RT则会再次熔断
异常比例(ERROR_RATIO)
当单位统计时长(statIntervalMs)内请求数据大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN状态),若接下来的一个请求完成(没有错误)则结束熔断,否则会再次被熔断,异常比例的阈值范围是[0.0, 1.0]
代表0%~100%
异常数(ERROR_COUNT)
当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN状态),若接下来的一个请求成功完成(没有异常错误)则结束熔断,负责会再次被熔断
注意:异常降级仅针对业务异常,对sentinel限流降级本身的异常(BlockException)不生效
整合OpenFeign进行降级
- 所需依赖
xml
<!-- sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- 配置文件开启feign对sentinel的支持:
feign.sentinel.enabled=true
- 编写feign降级实现类
java
package com.cloud.order.feign;
import org.springframework.stereotype.Component;
/**
* StockFeignService降级业务
*/
@Component
public class ConsumerFallBackService implements StockFeignService{
@Override
public String openFeign(int goodsId) {
return goodsId + "降级了!";
}
@Override
public String sentinelDemo() {
return "降级了";
}
}
- feign接口添加降级回调fallback配置
@FeignClient(value = "alibaba-stock", path = "/stock", fallback = ConsumerFallBackService.class
java
package com.cloud.order.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
/**
* value:注册到nacos的服务名称
* path:路径的前缀
*/
@FeignClient(value = "alibaba-stock", path = "/stock", fallback = ConsumerFallBackService.class)
public interface StockFeignService {
@GetMapping("/openFeign/{goodsId}")
String openFeign(@PathVariable("goodsId") int goodsId);
@GetMapping("/sentinelDemo")
String sentinelDemo();
}
热点规则
热点:即经常访问的数据,经常统计某个热点数据访问频次最高的数据,并对其访问进行限制
常用场景:地点商品访问/操作控制;用户/IP防刷
实现原理:热点淘汰策略(LRU)
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看作是一种特殊的流量控制,仅对包含热点参数的资源调用生效
注意:
- 热点规则需要使用@SentinelResource("resourceName")注解,否则不生效
- 参数必须是7中基本数据类型才会生效
测试代码如下
java
@GetMapping("/test2")
@SentinelResource(value = "resource-test2", fallbackClass = ExceptionUtils.class, fallback = "handleTest2Fallback", blockHandlerClass = ExceptionUtils.class, blockHandler = "handleTest2Exception")
private String test2(Integer orderId) {
return "hello sentinel! this is test2";
}
单机阈值:针对所有参数的值进行设置的一个公共的阈值
- 假设当前参数大部分的值都是热点流量,单机阈值就是针对热点流量进行设置,额外针对普通流量进行参数值流控
- 假设当前参数大部分的值都是普通流量,单机阈值就是针对普通流量进行设置,额外针对热点流量进行参数值流控
配置热点参数规则
**注意:**资源名必须是@SsentinelResource(value="资源名")配置的资源名,热点规则依赖于注解
总结
- 流控一个是用在服务提供方(provider端),降级一般是用在调用方(消费方)(consumer端)