Sentinel是阿里巴巴开源的,面向分布式服务架构的高可用防护组件。
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。 Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
源码地址:https://github.com/alibaba/Sentinel
官方文档:https://github.com/alibaba/Sentinel/wiki
Sentinel 具有以下特征:
阿里云提供了企业级的Sentinel服务,应用高可用服务AHAS
Sentinel和Hystrix对比
Sentinel | Hystrix | |
---|---|---|
隔离策略 | 信号量隔离 | 线程池隔离/信号量隔离 |
熔断降级策略 | 基于响应时间或失败比率 | 基于失败比率 |
实时指标实现 | 滑动窗口 | 滑动窗口(基于RxJava) |
规则配置 | 支持多种数据源 | 支持多种数据源 |
扩展性 | 多个扩展点 | 插件的形式 |
基于注解的支持 | 支持 | 支持 |
限流 | 基于QPS,支持基于调用关系的限流 | 有限的支持 |
流量整形 | 支持慢启动,匀速器模式 | 不支持 |
系统负载保护 | 支持 | 不支持 |
控制台 | 开箱即用、可配置规则、查看秒级监控、机器发现等 | 不完善 |
常见框架的适配 | servlet、spring cloud、dubbo、grpc等 | servlet、spring cloud netflix |
Sentinel可以简单的分为
Sentinel核心库
和Dashboard
。核心库不依赖Dashboard,但是结合Dashboard可以取得最好的效果。
流程规则的定义
重要属性:
Field | 说明 | 默认值 |
---|---|---|
resource | 资源名,资源名是限流规则的作用对象 | |
count | 限流阈值 | |
grade | 限流阈值类型,QPS 模式(1)或并发线程数模式(0) | QPS 模式 |
limitApp | 流控针对的调用来源 | default ,代表不区分调用来源 |
strategy | 调用关系限流策略:直接、链路、关联 | 根据资源本身(直接) |
controlBehavior | 流控效果(直接拒绝/WarmUp/匀速+排队等待),不支持按调用关系限流 | 直接拒绝 |
clusterMode | 是否集群限流 | 否 |
通过代码自定义流量控制:
1、创建一个springboot项目,并引入sentinel核心库
<!--sentinel核心库-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>1.8.4</version>
</dependency>
<!--如果要使用@SentinelResource-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-annotation-aspectj</artifactId>
<version>1.8.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
编写一个controller
@RestController
@Slf4j
public class HelloController {
private static final String RESOURCE_NAME = "hello";
@RequestMapping("/hello")
public String hello(){
Entry entry = null;
try {
// sentinel针对资源进行限制
entry = SphU.entry(RESOURCE_NAME);
// 被保护的业务逻辑
String str = "hello world";
log.info("======="+str+"=======");
return str;
}catch (BlockException e){
// 资源访问组织,被限流或被降级,进行响应的处理操作
log.info("block");
return "被监控了";
}catch (Exception ex){
// 若需要配置降级规则,则需要通过这种方式记录业务异常
Tracer.traceEntry(ex,entry);
}finally {
if(entry != null){
entry.exit();
}
}
return null;
}
/**
* spring的初始化方法
*
**/
@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);
}
}
结果描述
浏览器进行访问/hello
接口,每隔一秒点击一次,正常返回hello world
,如果快速点击,返回被监控了
。
像这样的规则还有熔断降级规则
、系统保护规则
、访问控制规则
、热点规则
、查询更改规则
等等,代码使用方法与上述雷同。
@SentimeResource:注解引入
作用:改善接口中资源定义和被流控降级后的处理方法
1、添加依赖(上面已经引入,我再展示一遍这个依赖)
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-annotation-aspectj</artifactId>
<version>1.8.4</version>
</dependency>
2、配置bean
/**
* 注解支持的配置Bean
*/
@Bean
public SentinelResourceAspect sentinelResourceAspect(){
return new SentinelResourceAspect();
}
3、在controller层进行代码实现
@RestController
@Slf4j
public class HelloController {
private static final String RESOURCE_NAME = "hello";
/**
* value: 定义的资源
* blockHandler: 设置流控降级后的处理方法(默认该方法必须声明在同一个类)
* 如果不想在同一个类中blockHandLerClass但是方法必须是static静态的
* fallback : 当接口出现了异常,可以调用的方法
* 如果不想在同一个类中fallbackClass但是方法必须是static静态的
* 如果blockHandler和fallback同时指定,则blockHandler优先级更高
* exceptionsToIgnore 排除哪些异常不处理
**/
@RequestMapping("/hello")
@SentinelResource(value = RESOURCE_NAME,blockHandler = "blockHandlerForHello",fallback = "fallbackHandlerHello",exceptionsToIgnore = {})
public String hello(String id){
// int i = 1/0;
return "hello world";
}
/**
* 注意:
* 1、一定要public
* 2、返回值一定要和原方法一直,参数也要包含原函数的参数
*/
public String blockHandlerForHello(String id , BlockException ex){
ex.printStackTrace();
return "流控";
}
/**
* 异常会走这个方法
*/
public String fallbackHandlerHello(String id , Throwable e){
e.printStackTrace();
return "异常处理";
}
/**
* spring的初始化方法
*
**/
@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);
}
}
根据需要的版本进行下载下载jar包
windows版本启动:
下载完成后,在指定目录下面进行
java -jar 你的jar包名称
浏览器直接访问即可,localhost:8080,默认账户名和密码都是sentinel
修改端口号、以及账户名和密码
#需以这种方式启动即可
java -Dserver.port=8810 -Dsentinel.dashboard.auth.username=lili -Dsentinel.dashboard.auth.password=211314 -jar D:\a-mysource\sentinel\sentinel-dashboard-1.8.1.jar
linux版本启动
不做过多描述,命令与windows一样,
注意:如果你想要外界可以访问的话,jar包需要后台启动,可以参考这篇文章当xshell关闭时如何保持一个jar包程序在后台运行
并且打开相应端口防火墙,以及服务器的安全组放行。
效果展示:
本地集成控制台
<!--整合控制台-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-transport-simple-http</artifactId>
<version>1.8.4</version>
</dependency>
编辑我们的启动配置,在对应位置加入(主要是为了根据ip和端口号找到我们的控制台)
-Dcsp.sentinel.dashboard.server=127.0.0.1:8010
启动即可!
注意:刚打开我们的控制台是什么都没有的,随便对一个接口进行访问,再次刷新控制台即可。
1、添加依赖
<!--sentinel依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
2、yml配置
server:
port: 8005
# 服务名称
spring:
application:
name: order-sentinel
cloud:
sentinel:
transport:
#配置sentinel的地址以及端口号
dashboard: 127.0.0.1:8010
3、编写一个controller
@RestController
@RequestMapping("/order")
public class OrderController {
@GetMapping("/orderList")
public String getOrder() {
return "orderList";
}
@GetMapping("/hello")
public String getHello() {
return "hello";
}
}
启动,先对接口进行一个访问,再进入控制台即可
我们可以对两个请求快速进行访问几次,然后点击实时监控,可以看到请求的一些信息
我们经过请求,发现两次请求都被添加进来了。并且可为每个请求进行流控,降级,热点,授权
流量控制,其原理是监控应用流量的QPS或并发线程等指标,当达到指定的阈值对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性
应用场景:
我们在簇点链路对流控规则进行添加操作,先进行示例QPS方式如下
解释:该请求每秒最多2个请求,否则就会被流控
测试:浏览器快速刷新,结果如下
结论:成功被流控,这是默认显示,如果想修改默认显示,操作如下
@RestController
@RequestMapping("/order")
public class OrderController {
@GetMapping("/hello")
@SentinelResource(value = "hello",blockHandler = "helloHandler")
public String getHello() {
return "hello";
}
public String helloHandler(BlockException e) {
return "被流控了";
}
}
需要对我们刚才自己添加的资源进行流控
再次快速刷新,这个时候就是我们自定义的流控描述了。
再次测试线程数流控:
线程数流控是为了保护业务线程池不被慢调用耗尽
实现:添加一个方法
@RequestMapping("/threadHello")
@SentinelResource(value = "threadHello",blockHandler = "helloHandler")
public String threadHello() throws InterruptedException {
TimeUnit.SECONDS.sleep(5);
return "hello";
}
打开两个浏览器进行访问(模拟两个线程)
当两个资源之间具有争抢或者依赖关系的时候,这两个资源便有了关联,比如对数据库同一个字段的读操作和写操作存在争抢,这个时候,我们就可以把读写操作进行关联,当写的操作过于频繁时,读数据的请求就会被限流。
步骤:
1、新建两个接口
@GetMapping("/add")
public String add() {
return "生成订单";
}
@GetMapping("/get")
public String get() {
return "查询订单";
}
2、生成订单对查询订单限流,当生成订单的次数大于三,则查询订单被限流。
Jmeter工具对/order/add进行每秒四次请求测试,然后浏览器访问/order/get
下图记录了资源之间的调用链路,这些资源通过调用关系,相互之间构成一颗调用树
解释:有两个入口/order/getUser1
,/order/getUser2
请求都调用了资源User,Sentinel可以选择对其中一个进行资源限制
步骤:
1、定义一个接口和对应的实现类
public interface UserService {
String getUser();
}
@Service
public class UserServiceImpl implements UserService{
@Override
@SentinelResource(value = "getUser",blockHandler = "userHandler")
public String getUser() {
return "User";
}
public String userHandler(BlockException e){
return "被链路流控了";
}
}
2、编写controller层
@Autowired
UserService userService;
@GetMapping("/getUser1")
public String getUser1() {
return userService.getUser();
}
@GetMapping("/getUser2")
public String getUser2() {
return userService.getUser();
}
3、对getUser2添加链路流控
注意:测试发现功能不生效(高版本不生效)
yml里面加上
spring:
cloud:
sentinel:
web-context-unify: false # 默认将调用链路收敛
再次测试发现
只对order/getUser2
进行了流控,而对order/getUser1
没有进行流控
针对激增流量处理
warm up
方式,即预热\冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮,通过冷启动让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热时间,避免冷系统被压垮。
冷加载因子:codeFactor默认是3,即请求QPS从threshold/3开始,经预热时长逐渐升至设定的QPS阈值
解释:5s内时间进行预热,直到达到阈值(比如第一秒只能通过三个,第二秒可以通过5个,直到达到阈值)
演示:浏览器快速刷新请求,刚开始会被流控,随着预热,你的刷新速度已经不会被流控了
针对脉冲流量
匀速排队方式会严格控制请求通过的间隔时间,也即是让请求以匀速的速度通过,对应的是漏桶算法
这种方式主要用于处理间隔性突然的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。
注意:匀速排队模式暂时不支持QPS>1000的场景
解释:每秒接收5个请求,其他请求进行等待,等待时间为5秒
Jmeter工具对/order/orderList进行每秒10次请求测试,间隔时间为5s,循环5次。
然后观察实施监控
发现:所有的请求都进行了处理,并且还有空闲时间
除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。我们需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩,熔断降级作为保护自身的手段,通常在客户端(调用端进行配置)。
保护自身的的手段
触发熔断后的处理逻辑实例
慢调用比例:选择以慢调用比例作为阈值,需要设置允许的慢调用RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值。
则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态,若接下来的一个请求响应时间小于设置的慢调用RT则结束熔断,若大于设置的慢调用RT则会再次被熔断。
1、添加方法
@RequestMapping("/test")
@SentinelResource(value = "test",fallback = "testFallback")
public String test() throws InterruptedException {
Thread.sleep(101);
return "正常访问";
}
public String testFallback(Throwable e){
return "被熔断了";
}
2、添加规则
解释:10秒内有大于5个请求的访问,慢调用比例大于0.1就进行熔断。
测试:浏览器进行请求5次以上发现被熔断。
1、添加接口
@RequestMapping("/errorTest")
public String errorTest() {
int i = 1/0;
return "正常访问";
}
2、添加规则
解释:10秒内有大于5个请求的访问,异常比例大于0.1就进行熔断。
测试:浏览器进行请求5次以上发现被熔断。
1、添加规则
解释:10秒内有大于5个请求的访问,只有有一个异常就进行熔断。
测试:浏览器进行请求5次以上发现被熔断。
在我们服务消费方去调用服务提供方的时候,由于提供方出现的错误,而导致我们的消费方也报错。这个时候就需要我们的sentinel进行整合降级处理。
问题演示:
1、服务提供方【加一个1/0的异常】
@RestController
@RequestMapping("/stock")
public class StockController {
@Value("${server.port}")
String value;
@RequestMapping("/stockList")
public String getStock(){
int i = 1/0;
System.out.println("库存");
return "扣减库存"+value;
}
}
2、服务消费方
@FeignClient(value = "stock-server",path = "/stock")
public interface StockFeignService {
@RequestMapping("/stockList")
public String getStock();
}
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
StockFeignService stockFeignService;
@GetMapping("/orderList")
public String getOrder() {
return stockFeignService.getStock();
}
}
3、进行调用
1、这样就需要我们引入sentinel
依赖进行解决
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
2、application.yml
#对Feign的支持
feign:
sentinel:
enabled: true #添加feign对sentinel的支持
3、编写一个降级的类,去实现你的feign的service接口
@Component
public class StockFeignServiceFallback implements StockFeignService{
@Override
public String getStock() {
return "降级了";
}
}
4、接口添加fallback,对应你刚才编写的类
5、重启访问
何为热点?热点即经常访问的数据,很多时候我们希望统计某个热点数据中访问频次最高的数据,并对其进行访问限制。
接口编辑:
@RequestMapping("/get/{id}")
@SentinelResource(value = "getById",blockHandler = "hotBlockHandler")
public String getById(@PathVariable("id") Integer id){
return "正常访问";
}
public String hotBlockHandler(Integer id ,BlockException e) {
return "热点异常处理";
}
添加规则:
单机阈值
假设我们的参数是普通参数:
解释:设置一秒钟可以访问10次
保存完成后可以进行编辑,设置我们的热点参数:
解释:参数为1的,一秒只能访问两次。
结果:这个时候我们对id为1的进行访问,快速刷新访问就会被热点流控
一些指标:
我们实际开发环境中,肯定不希望我们重启后,这些规则就消失了,这个时候我们就需要持久化。
推模式:
生产环境下一班更常用的是push模式的数据源,对于push模式的数据源,如远程配置中心(ZooKeeper,Nacos,Apoll等等),推送的操作不应由Sentinel客户端进行,而应该经控制台统一进行管理,直接进行推送,数据源仅负责获取配置中心推送的配置并更新到本地。因此推送规则的正确做法应该是配置中心控制台/Sentinel控制台—>配置中心–>Sentinel数据源–>Sentinel,而不是经过Sentinel数据源推送至配置中心,这样的流程就非常清晰了。
引入依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
nacos中新建一个配置
Field | 说明 | 默认值 |
---|---|---|
resource | 资源名,资源名是限流规则的作用对象 | |
count | 限流阈值 | |
grade | 限流阈值类型,QPS 模式(1)或并发线程数模式(0) | QPS 模式 |
limitApp | 流控针对的调用来源 | default ,代表不区分调用来源 |
strategy | 调用关系限流策略:直接、链路、关联 | 根据资源本身(直接) |
controlBehavior | 流控效果(直接拒绝/WarmUp/匀速+排队等待),不支持按调用关系限流 | 直接拒绝 |
clusterMode | 是否集群限流 | 否 |
yml配置
server:
port: 8005
# 服务名称
spring:
application:
name: order-sentinel
cloud:
sentinel:
transport:
#配置sentinel的地址以及端口号
dashboard: 127.0.0.1:8010
web-context-unify: false # 默认将调用链路收敛
datasource:
hello-rule: #可以自定义
nacos:
#配置的名字
data-id: order-sentinel-hello-rule
server-addr: 101.34.254.160:8847
data-type: json
rule-type: flow
重启项目:
流控规则成功进来了,我们进行快速刷新接口,测试流控效果
流控效果也正常显示。