1、SpringCloud Alibaba Sentinel实现熔断与限流
1、Sentinel
1、官网
https://github.com/alibaba/Sentinel
中文:https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D
2、是什么
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
3、下载
https://github.com/alibaba/Sentinel/releases
4、作用
5、怎么玩
服务使用中的各种问题
核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。
- 后台
控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。
- 前台8080
2、安装步骤
1、下载
https://github.com/alibaba/Sentinel/releases
- 前台8080
-
2、运行命令
前提
- java8环境OK
- 8080端口不能被占用
命令
java -jar sentinel-dashboard-1.7.0.jar
3、访问sentinel管理界面
-
3、初始化演示工程
1、启动相关服务
nacos
访问http://localhost:8848/nacos/#/login
启动sentinel
java -jar sentinel-dashboard-1.7.0.jar
2、创建工程cloudalibaba-sentinel-service8401
1、修改pom
```xml <?xml version=”1.0” encoding=”UTF-8”?> <project xmlns=”http://maven.apache.org/POM/4.0.0“
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<artifactId>cloud2020</artifactId> <groupId>com.daijunyi</groupId> <version>1.0-SNAPSHOT</version>
4.0.0 cloudalibaba-sentinel-service8401 com.daijunyi cloud-api-commons ${project.version} com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery com.alibaba.csp sentinel-datasource-nacos com.alibaba.cloud spring-cloud-starter-alibaba-sentinel org.springframework.cloud spring-cloud-starter-openfeign org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-actuator org.springframework.boot spring-boot-devtools runtime true cn.hutool hutool-all 4.6.3 org.projectlombok lombok true org.springframework.boot spring-boot-starter-test test
<a name="bomNJ"></a>
#### 2、修改yaml
```yaml
server:
port: 8401
spring:
application:
name: cloudalibaba-sentinel-service
cloud:
nacos:
discovery:
server-addr: localhost:1111
sentinel:
transport:
dashboard: localhost:8080
port: 8719 #默认8719,假如被占用了会自动从8719开始依次+1扫描。直至找到未被占用的端口
management:
endpoints:
web:
exposure:
include: '*'
3、主启动类
package com.daijunyi;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class SentinelService8401 {
public static void main(String[] args) {
SpringApplication.run(SentinelService8401.class,args);
}
}
4、业务类
- controller ```java package com.daijunyi.controller;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;
@RestController public class FlowLimitController {
@GetMapping("/testA")
public String testA() {
return "------testA";
}
@GetMapping("/testB")
public String testB() {
return "------testB";
}
}
<a name="M4RVy"></a>
#### 5、查看nacos注册的服务
![image.png](https://cdn.nlark.com/yuque/0/2021/png/12971636/1631159112621-767ce86a-3fda-4cef-968b-ef115eedb857.png#clientId=u48bb990c-06f3-4&from=paste&height=395&id=u10837d97&margin=%5Bobject%20Object%5D&name=image.png&originHeight=790&originWidth=2868&originalType=binary&ratio=1&size=142689&status=done&style=none&taskId=udbe37cd0-d292-42da-aade-944e5e4f740&width=1434)
<a name="Zozda"></a>
### 3、启动8401微服务后查看sentienl控制台
- 访问[http://localhost:8080](http://localhost:8080/#/dashboard/home)
- ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12971636/1631159241802-f955afc7-05f8-47a1-8df2-faae5a1997ba.png#clientId=u48bb990c-06f3-4&from=paste&height=399&id=u733ef825&margin=%5Bobject%20Object%5D&name=image.png&originHeight=798&originWidth=2878&originalType=binary&ratio=1&size=106616&status=done&style=none&taskId=u353a515e-6f11-4c30-ae0a-b6e7ae9e658&width=1439)
- Sentinel采用的懒加载说明
- 执行一次访问即可
- [http://localhost:8401/testA](http://localhost:8401/testA)
- [http://localhost:8401/testB](http://localhost:8401/testB)
- 查看效果
- ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12971636/1631159330101-36ca294f-de93-4605-8e77-a5251b2e4e99.png#clientId=u48bb990c-06f3-4&from=paste&height=675&id=u5572c12f&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1350&originWidth=2820&originalType=binary&ratio=1&size=247087&status=done&style=none&taskId=ua188b6d2-d290-43db-93e1-42771caf551&width=1410)
- sentinel8080正在监控微服务8401
<a name="aiL1O"></a>
## 4、流控规则
<a name="rp4AK"></a>
### 1、基本介绍
![image.png](https://cdn.nlark.com/yuque/0/2021/png/12971636/1631159418037-12119ea1-ceaa-4e9e-9500-50b5f6690cf9.png#clientId=u48bb990c-06f3-4&from=paste&height=649&id=u0222c00e&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1298&originWidth=2840&originalType=binary&ratio=1&size=277810&status=done&style=none&taskId=u1762797b-2c85-4a31-8da9-43e09490402&width=1420)
- 资源名:唯一名称,默认请求路径
- 针对来源: Sentinel可以针对调用者进行限流,填写微服务名,默认default (不区分来源)
- 阈值类型/单机阈值:
- QPS (每秒钟的请求数量) :当调用该ap的QPS达到阈值的时候,进行限流
- 线程数:当调用该api的线程数达到阈值的时候,进行限流
- 是否集群:不需要集群
- 流控模式:
- 直接: api达到限流条件时,直接限流
- 关联:当关联的资源达到阈值时,就限流自己
- 链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就进行限流) [api级别的针对来源]
- 流控效果:
- 快速失败:直接失败,抛异常
- Warm Up:根据codeFactor (冷加载因子,默认3)的值,从阈值/codeFactor, 经过预热时长,才达到设置的QPS阈值
- 排队等待:匀速排队,让请求以匀速的速度通过,阈值类型必须设置为QPS,否则无效
<a name="Ejmih"></a>
### 2、流控模式
<a name="sv3Ih"></a>
#### 1、直接(默认)
- 直接->快速失败
- 系统默认
- 配置及说明
- ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12971636/1631160632416-8957a369-64e9-4e2c-9ad6-3ce375482c73.png#clientId=u48bb990c-06f3-4&from=paste&height=512&id=u424e932d&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1024&originWidth=1382&originalType=binary&ratio=1&size=104671&status=done&style=none&taskId=u499b68f4-8e24-4932-8d48-bccc584624a&width=691)
- 测试
- 快速访问[http://localhost:8401/testA](http://localhost:8401/testA)
- ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12971636/1631160747334-0b738def-bdda-48cb-b0a3-4d19e53f77a1.png#clientId=u48bb990c-06f3-4&from=paste&height=68&id=u51979825&margin=%5Bobject%20Object%5D&name=image.png&originHeight=136&originWidth=636&originalType=binary&ratio=1&size=14373&status=done&style=none&taskId=u17ed47e2-02ef-47ff-98b5-11b4f6ddafb&width=318)
- 问题
- 直接调用默认报错信息,技术方面OK but,是否应该有我们自己的后续处理?
- 应该类似有一个fallback的兜底方法?
<a name="tgQtQ"></a>
#### 2、关联
- 是什么?
- 当关联的资源达到阈值时,就限流自己
- 当与A关联的资源B达到阈值后,就限流自己
- B惹事,A挂了
- 配置说明
- ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12971636/1631160896259-11ca6582-604e-4401-81b7-7de20156f285.png#clientId=u48bb990c-06f3-4&from=paste&height=530&id=u66cdd1d4&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1060&originWidth=1326&originalType=binary&ratio=1&size=105177&status=done&style=none&taskId=u020727be-4a82-4243-b327-2b0f7c8e184&width=663)
- 测试
- 运行postman访问/testB
- 执行20次,每次请求之间间隔0.3秒
- ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12971636/1631162193127-e871e541-fdc5-4c09-8fc5-14ba6a62f687.png#clientId=u48bb990c-06f3-4&from=paste&height=729&id=ue9c49419&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1458&originWidth=998&originalType=binary&ratio=1&size=87179&status=done&style=none&taskId=uae13ae20-977d-4741-b3f8-87cacb27182&width=499)
- 执行上的请求,请求[http://localhost:8401/testA](http://localhost:8401/testA)
- ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12971636/1631162233656-9ec7a363-d9c0-447a-bf34-813a634aa7fc.png#clientId=u48bb990c-06f3-4&from=paste&height=59&id=ua27659eb&margin=%5Bobject%20Object%5D&name=image.png&originHeight=118&originWidth=648&originalType=binary&ratio=1&size=14666&status=done&style=none&taskId=u1bee301f-ff77-4a1c-8ab4-68f87a99dc7&width=324)
- 但是当/testB请求完毕之后再请求[http://localhost:8401/testA](http://localhost:8401/testA)
- ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12971636/1631162270404-9abbef85-da42-4ff4-af5e-05b5f0013876.png#clientId=u48bb990c-06f3-4&from=paste&height=70&id=uc65d0399&margin=%5Bobject%20Object%5D&name=image.png&originHeight=140&originWidth=670&originalType=binary&ratio=1&size=10912&status=done&style=none&taskId=u1f8f4734-7e2b-49a1-89c5-120492037e4&width=335)
<a name="yuGxi"></a>
#### 3、链路
- 链路就是,当我们一个请求A和B过来,然后都去调用地下的service层同一个方法C,使用注解@SentinelResource标志为资源名称”c“
- 这个时候调用就会形成A-c,和B-c的一个链路,我们可以对资源c进行限流,但是只有当是从入口A过来的时候,我们才限流,B过来的请求访问资源c不限流,这个就是链路模式
- 坑:
- 从1.6.3 版本开始,Sentinel Web filter默认收敛所有URL的入口context,因此链路限流不生效。
- 1.7.0 版本开始(对应SCA的2.1.1.RELEASE),官方在CommonFilter 引入了WEB_CONTEXT_UNIFY 参数,用于控制是否收敛context。将其配置为 false 即可根据不同的URL 进行链路限流。
- SCA 2.1.1.RELEASE之后的版本,可以通过配置spring.cloud.sentinel.web-context-unify=false即可关闭收敛,我们当前使用的版本是SpringCloud Alibaba 2.1.0.RELEASE,无法实现链路限流。所以我们至少使用2.1.1.RELEASE的版本,需要写代码的形式实现进行配置这个环境。
- 修改父类pom中的SpringCloud Alibaba版本
```xml
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.1.1.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
修改8401的yaml
spring: cloud: sentinel: filter: enabled: false
再自己写配置类 ```java package com.daijunyi.config;
import com.alibaba.csp.sentinel.adapter.servlet.CommonFilter; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;
@Configuration public class FilterContextConfig { @Bean public FilterRegistrationBean sentinelFilterRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(new CommonFilter()); registration.addUrlPatterns(“/*”); // 入口资源关闭聚合 registration.addInitParameter(CommonFilter.WEB_CONTEXT_UNIFY, “false”); registration.setName(“sentinelFilter”); registration.setOrder(1); return registration; } }
- 业务代码修改
- 增加service
```java
package com.daijunyi.service;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import org.springframework.stereotype.Service;
@Service
public class FlowLimitService {
@SentinelResource(value = "flowLimit")
public String flowLimit(){
return "flowLimit";
}
}
- 修改controller
- testA和testB都去调用flowLimit ```java package com.daijunyi.controller;
import com.alibaba.csp.sentinel.context.ContextUtil; import com.daijunyi.service.FlowLimitService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController;
@RestController public class FlowLimitController {
@Autowired
FlowLimitService flowLimitService;
@GetMapping("/testA")
public String testA() {
String s = flowLimitService.flowLimit();
return "------testA";
}
@GetMapping("/testB")
public String testB() {
return "------testB";
}
@GetMapping("/testC")
public String testC(){
String s = flowLimitService.flowLimit();
System.out.println(s);
return "------testC";
}
}
- sentinel控制台配置
- 对资源flowLimit进行限制
- 并且是入口/testC来的
- ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12971636/1631167210156-342b0cb8-d7a8-421d-b658-3153fbd65fad.png#clientId=u48bb990c-06f3-4&from=paste&height=587&id=ub6924def&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1174&originWidth=2168&originalType=binary&ratio=1&size=209791&status=done&style=none&taskId=uf0db0141-b7c1-4f58-b258-0aee1259d27&width=1084)
- 测试
- 快速访问[http://localhost:8401/testC](http://localhost:8401/testC)
- ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12971636/1631167264752-c56706d9-78eb-43c3-bce4-4b950869d971.png#clientId=u48bb990c-06f3-4&from=paste&height=344&id=uaca50850&margin=%5Bobject%20Object%5D&name=image.png&originHeight=688&originWidth=1482&originalType=binary&ratio=1&size=165989&status=done&style=none&taskId=ub977e6bf-3490-4b96-9822-c3e4d41a188&width=741)
- 快速访问[http://localhost:8401/testA](http://localhost:8401/testA)
- ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12971636/1631167290509-cebf85ed-5c4d-4793-a8b2-d86f7915cec3.png#clientId=u48bb990c-06f3-4&from=paste&height=55&id=ucc7df9ea&margin=%5Bobject%20Object%5D&name=image.png&originHeight=110&originWidth=682&originalType=binary&ratio=1&size=10882&status=done&style=none&taskId=u854d844c-91fb-4d05-b305-950680fd806&width=341)
- 不限制
<a name="ttBik"></a>
### 3、流控效果
<a name="QilPl"></a>
#### 1、直接->快速失败(默认的流控处理)
- 直接失败,抛出异常
- Blocked by Sentinel (flow limiting)
- 源码
- com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController
<a name="ZsnGd"></a>
#### 2、预热
<a name="kbFMd"></a>
##### 1、说明
公式:阈值除以coldFactor(默认值为3),经过预热时长后才会达到阈值
<a name="ThSou"></a>
##### 2、官网
[https://github.com/alibaba/Sentinel/wiki/%E9%99%90%E6%B5%81---%E5%86%B7%E5%90%AF%E5%8A%A8](https://github.com/alibaba/Sentinel/wiki/%E9%99%90%E6%B5%81---%E5%86%B7%E5%90%AF%E5%8A%A8)<br />当流量突然增大的时候,我们常常会希望系统从空闲状态到繁忙状态的切换的时间长一些。即如果系统在此之前长期处于空闲的状态,我们希望处理请求的数量是缓步的增多,经过预期的时间以后,到达系统处理请求个数的最大值。Warm Up(冷启动,预热)模式就是为了实现这个目的的。<br />这个场景主要用于启动需要额外开销的场景,例如建立数据库连接等。<br />它的实现是在 [Guava](https://github.com/google/guava/blob/master/guava/src/com/google/common/util/concurrent/SmoothRateLimiter.java) 的算法的基础上实现的。然而,和 Guava 的场景不同,Guava 的场景主要用于调节请求的间隔,即 [Leaky Bucket](https://en.wikipedia.org/wiki/Leaky_bucket),而 Sentinel 则主要用于控制每秒的 QPS,即我们满足每秒通过的 QPS 即可,我们不需要关注每个请求的间隔,换言之,我们更像一个 [Token Bucket](https://en.wikipedia.org/wiki/Token_bucket)。<br />我们用桶里剩余的令牌来量化系统的使用率。假设系统每秒的处理能力为 b,系统每处理一个请求,就从桶中取走一个令牌;每秒这个令牌桶会自动掉落b个令牌。令牌桶越满,则说明系统的利用率越低;当令牌桶里的令牌高于某个阈值之后,我们称之为令牌桶"饱和"。<br />当令牌桶饱和的时候,基于 Guava 的计算上,我们可以推出下面两个公式:<br />rate(c)=m*c+ coldrate <br />其中,rate 为当前请求和上一个请求的间隔时间,而 rate 是和令牌桶中的高于阈值的令牌数量成线形关系的。cold rate 则为当桶满的时候,请求和请求的最大间隔。通常是 coldFactor * rate(stable)。<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/12971636/1631167574265-e2e12a60-3307-41db-bcf9-0bcc6416bc91.png#clientId=u48bb990c-06f3-4&from=paste&height=516&id=uf359a4a9&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1032&originWidth=1168&originalType=binary&ratio=1&size=350249&status=done&style=none&taskId=u8550400b-24eb-4ba7-a60c-e7f44caa6fa&width=584)<br />默认coldFactor为3,即请求QPS从threshold/3开始,经预热时长逐渐升至设定的QPS阈值。
<a name="BLj31"></a>
##### 3、源码
com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/12971636/1631167703700-ae64a69f-6120-4023-af3c-c5f58a1f290d.png#clientId=u48bb990c-06f3-4&from=paste&height=382&id=ub60c5003&margin=%5Bobject%20Object%5D&name=image.png&originHeight=764&originWidth=1610&originalType=binary&ratio=1&size=136882&status=done&style=none&taskId=u44963ae5-2bce-415b-97bd-09b8f024273&width=805)
<a name="KlcAo"></a>
##### 4、配置
![image.png](https://cdn.nlark.com/yuque/0/2021/png/12971636/1631167786008-01747ff6-067e-4391-ad96-c7d853b24106.png#clientId=u48bb990c-06f3-4&from=paste&height=532&id=u82a1a212&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1064&originWidth=1372&originalType=binary&ratio=1&size=107863&status=done&style=none&taskId=uffddbc92-3183-4d9b-b981-31f153b91d1&width=686)
<a name="qUDUd"></a>
##### 5、测试
- 快速点击[http://localhost:8401/testB](http://localhost:8401/testB)
- 发现刚开始总是出现被限流,慢慢的就不再限流了。可以抗住了
<a name="xCDHE"></a>
##### 6、应用场景
如:秒杀系统在开启的瞬间,会有很多流量上来,很有可能把系统打死,预热方式就是把为了保护系统,可慢慢的把流量放进来,慢慢的把阀值增长到设置的阀值
<a name="Wspww"></a>
#### 3、排队等待
<a name="PUaIn"></a>
##### 1、原理
- 匀速排队,阈值必须设置为QPS
- 匀速排队(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。详细文档可以参考 [流量控制 - 匀速器模式](https://github.com/alibaba/Sentinel/wiki/%E6%B5%81%E9%87%8F%E6%8E%A7%E5%88%B6-%E5%8C%80%E9%80%9F%E6%8E%92%E9%98%9F%E6%A8%A1%E5%BC%8F),具体的例子可以参见 [PaceFlowDemo](https://github.com/alibaba/Sentinel/blob/master/sentinel-demo/sentinel-demo-basic/src/main/java/com/alibaba/csp/sentinel/demo/flow/PaceFlowDemo.java)。
- ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12971636/1631168010262-91f694ee-989c-43d1-ac3e-f116838d322d.png#clientId=u48bb990c-06f3-4&from=paste&height=279&id=u14be3e47&margin=%5Bobject%20Object%5D&name=image.png&originHeight=558&originWidth=1632&originalType=binary&ratio=1&size=117453&status=done&style=none&taskId=u1e06f499-3073-444c-a7d0-36993b3c169&width=816)
- 这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。
- 注意:匀速排队模式暂时不支持 QPS > 1000 的场景
<a name="e4KNU"></a>
##### 2、源码
com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController
<a name="fs1tk"></a>
##### 3、配置
- 每秒只能一个
- 超时设置20秒
![image.png](https://cdn.nlark.com/yuque/0/2021/png/12971636/1631168177420-adaad4bd-0b99-478c-ae38-ea6a207d93a5.png#clientId=u48bb990c-06f3-4&from=paste&height=541&id=u59a41c4d&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1082&originWidth=1354&originalType=binary&ratio=1&size=101060&status=done&style=none&taskId=u1becbca2-afaf-46ed-916a-a5aa5bf6adb&width=677)
- 测试结果就是发现不过来多少请求,都是1秒处理一个请求。
<a name="cE4B1"></a>
## 5、降级规则
<a name="YGHoP"></a>
### 1、基本介绍
- RT (平均响应时间,秒级)
- 平均响应时间超出阈值且在时间窗口内通过的请求> =5,两个条件同时满足后触发降级窗口期过后关闭断路器RT最大4900 (更大的需要通过一Dcsp.sentinel.statistic.max.rt=XXXX才能生效)
- 异常比列(秒级)
- QPS >= 5且异常比例(秒级统计)超过阈值时,触发降级;时间窗口结束后,关闭降级
- 异常数(分钟级)
- 异常数(分钟统计)超过阈值时,触发降级;时间窗口结束后,关闭降级
![image.png](https://cdn.nlark.com/yuque/0/2021/png/12971636/1631169172640-176d127d-a4c9-45a5-8419-9725ba37c88d.png#clientId=u48bb990c-06f3-4&from=paste&height=313&id=u4648f07f&margin=%5Bobject%20Object%5D&name=image.png&originHeight=626&originWidth=1356&originalType=binary&ratio=1&size=55158&status=done&style=none&taskId=udcbb30ed-ba90-4a54-853a-9e1c3ec4bba&width=678)
- Sentinel熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出DegradeException) 。
- Sentinel的断路器是没有半开状态的
- 半开的状态系统自动去检测是否请求有异常,没有异常就关闭断路器恢复使用,有异常则继续打开断路器不可用。具体可以参考Hystrix
- ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12971636/1631169406098-a5c1f6be-388b-417e-ad90-8e1bb295bdda.png#clientId=u48bb990c-06f3-4&from=paste&height=371&id=ubb3d9509&margin=%5Bobject%20Object%5D&name=image.png&originHeight=742&originWidth=1202&originalType=binary&ratio=1&size=110403&status=done&style=none&taskId=u890184a7-3132-405a-8c2d-cfe1d6ecf04&width=601)
<a name="EdCpb"></a>
### 2、降级策略实战
<a name="QusWm"></a>
#### 1、RT
<a name="pbqtX"></a>
##### 1、是什么
![image.png](https://cdn.nlark.com/yuque/0/2021/png/12971636/1631169470493-cd484bb1-f5d8-46a3-9db3-fe0ff6f7629b.png#clientId=u48bb990c-06f3-4&from=paste&height=617&id=u848dd453&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1234&originWidth=1412&originalType=binary&ratio=1&size=907014&status=done&style=none&taskId=uc5e130a1-f96c-477b-b6a2-9379271dbbc&width=706)
<a name="mXn6l"></a>
##### 2、测试
- 添加业务代码
```java
@GetMapping("/testD")
public String testD()
{
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
log.info("testD 测试RT");
return "------testD";
}
- 配置
- jmeter压测
- 发送20个线程,重复20000次
- 并发请求大于5,并且每个请求的处理时间大于200毫秒,会导致服务熔断
修改代码
@GetMapping("/testE") public String testE() { log.info("testD 测试RT"); int age = 10/0; return "------testE"; }
配置
- jmeter测试 发送20个并发线程请求
- 访问http://localhost:8401/testE
-
3、异常数
1、是什么
异常数是按照分钟统计的
2、测试
- 配置
发送请求http://localhost:8401/testE
- 刚开始错误页面
- 点了5次之后发现不再报错了
-
6、热点key限流
1、是什么
2、官网
https://github.com/alibaba/Sentinel/wiki/%E7%83%AD%E7%82%B9%E5%8F%82%E6%95%B0%E9%99%90%E6%B5%813、@SentinelResource
@SentinelResource
- 兜底方法
- 分为系统默认和客户自定义,两种
- 之前的case,限流出问题后,都是用sentine|系统默认的提示: Blocked by Sentinel (flow limiting)
- 我们能不能自定?类似hystrix,某个方法出问题了,就找对应的兜底降级方法?
结论
从HystrixCommand到@SentinelResource
4、代码
@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) { //int age = 10/0; return "------testHotKey"; } //兜底方法 public String deal_testHotKey (String p1, String p2, BlockException exception){ return "------deal_testHotKey,o(╥﹏╥)o"; }
4、配置
要对sentinelResource中的value进行配置
- 配置为@SentinelResource(value = “testHotKey”)时
配置为@SentinelResource(value = “testHotKey”,blockHandler = “deal_testHotKey”)
快速访问http://localhost:8401/testHotKey?p1=abc
- 快速访问http://localhost:8401/testHotKey?p1=abc&p2=33
上述案例演示了第一个参数p1,当QPS超过1秒1次点击后马上被限流
特殊情况
热点参数的注意点,参数必须是基本类型或者String
2、测试
- 快速访问http://localhost:8401/testHotKey?p1=5
- 当p1等于5的时候,阈值变为200
blockHandler是对sentinel控制台的规则进行兜底控制
-
7、系统规则
1、是什么
https://github.com/alibaba/Sentinel/wiki/%E7%B3%BB%E7%BB%9F%E8%87%AA%E9%80%82%E5%BA%94%E9%99%90%E6%B5%812、各项配置参数说明
系统保护规则是从应用级别的入口流量进行控制,从单台机器的 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 达到阈值即触发系统保护。
3、测试一个QPS
配置
- 快速访问http://localhost:8401/testA
启动Nacos成功
- 启动Sentinel成功
- 修改model cloudalibaba-sentinel-service8401
- 添加业务类controller ```java package com.daijunyi.controller;
import com.alibaba.csp.sentinel.annotation.SentinelResource; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.daijunyi.entities.CommonResult; import com.daijunyi.entities.Payment; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;
@RestController public class RateLimitController { @GetMapping(“/byResource”) @SentinelResource(value = “byResource”,blockHandler = “handleException”) public CommonResult byResource() { return new CommonResult(200,”按资源名称限流测试OK”,new Payment(2020L,”serial001”)); } public CommonResult handleException(BlockException exception) { return new CommonResult(444,exception.getClass().getCanonicalName()+”\t 服务不可用”); } }
- 配置流控规则
- 表示1秒钟内查询次数大于1,就跑到我们自定义的处流,限流
- ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12971636/1631176433604-7e39ab0b-e9a6-4283-98aa-47466afcddbd.png#clientId=ubabf230a-8a0d-4&from=paste&height=405&id=u6b9756f2&margin=%5Bobject%20Object%5D&name=image.png&originHeight=810&originWidth=1378&originalType=binary&ratio=1&size=78486&status=done&style=none&taskId=u1cf9322a-0ffb-4de2-9475-f1bf3daee2d&width=689)
- 测试快速访问[http://localhost:8401/byResource](http://localhost:8401/byResource)
- ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12971636/1631176480127-f345a5a9-eaf9-4891-ba01-e6702565aabc.png#clientId=ubabf230a-8a0d-4&from=paste&height=74&id=uba8774f1&margin=%5Bobject%20Object%5D&name=image.png&originHeight=148&originWidth=1662&originalType=binary&ratio=1&size=30098&status=done&style=none&taskId=ubc9b819c-c616-4faa-b938-cafb34a94c2&width=831)
- 问题
- 此时关闭微服务8401看看
- Sentinel控制台,流控规则消失了?????
- 临时/持久?
- 临时的
<a name="SOdmC"></a>
### 2、客户自定义限流处理逻辑
- 创建customerBlockHandler类用于自定义限流处理逻辑
- 注意一定要静态代码
```java
package com.daijunyi.handler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.daijunyi.entities.CommonResult;
public class CustomerBlockHandler {
public static CommonResult handleException(BlockException exception)
{
return new CommonResult(444,exception.getClass().getCanonicalName()+"\t 服务不可用");
}
}
添加controller业务
@GetMapping("/rateLimit/customerBlockHandler") @SentinelResource(value = "customerBlockHandler", blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handleException") public CommonResult customerBlockHandler() { return new CommonResult(200,"按客戶自定义",new Payment(2020L,"serial003")); }
配置
- 快速访问http://localhost:8401/rateLimit/customerBlockHandler
如果配置改为对GetMapping的资源名称的话会返回默认的代理
Sentinel主要有三个核心API
sentinel整合ribbon+openFeign+fallback
1、Ribbon系列
-
1、新建cloudalibaba-provider-payment9003
修改pom ```xml <?xml version=”1.0” encoding=”UTF-8”?> <project xmlns=”http://maven.apache.org/POM/4.0.0“
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<artifactId>cloud2020</artifactId> <groupId>com.daijunyi</groupId> <version>1.0-SNAPSHOT</version>
4.0.0 cloudalibaba-provider-payment9003 com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery com.daijunyi cloud-api-commons ${project.version} org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-actuator org.springframework.boot spring-boot-devtools runtime true org.projectlombok lombok true org.springframework.boot spring-boot-starter-test test
- 修改yaml
```yaml
server:
port: 9003
spring:
application:
name: nacos-payment-provider
cloud:
nacos:
discovery:
server-addr: localhost:1111 #配置Nacos地址
management:
endpoints:
web:
exposure:
include: '*'
- 主启动类 ```java package com.daijunyi;
import com.daijunyi.entities.Payment; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication @EnableDiscoveryClient public class Payment9003 { public static void main(String[] args) { SpringApplication.run(Payment9003.class,args); } }
- 业务类
```java
package com.daijunyi.controller;
import com.daijunyi.entities.CommonResult;
import com.daijunyi.entities.Payment;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
@RestController
public class PaymentController {
@Value("${server.port}")
private String serverPort;
public static HashMap<Long, Payment> hashMap = new HashMap<>();
static{
hashMap.put(1L,new Payment(1L,"28a8c1e3bc2742d8848569891fb42181"));
hashMap.put(2L,new Payment(2L,"bba8c1e3bc2742d8848569891ac32182"));
hashMap.put(3L,new Payment(3L,"6ua8c1e3bc2742d8848569891xt92183"));
}
@GetMapping(value = "/paymentSQL/{id}")
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id){
Payment payment = hashMap.get(id);
CommonResult<Payment> result = new CommonResult(200,"from mysql,serverPort: "+serverPort,payment);
return result;
}
}
- 复制一个9003位9004
- 运行9003
- 修改端口号为9004
- 启动9004
- nacos显示
测试地址
创建cloudalibaba-consumer-nacos-order84
修改pom ```xml <?xml version=”1.0” encoding=”UTF-8”?> <project xmlns=”http://maven.apache.org/POM/4.0.0“
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<artifactId>cloud2020</artifactId> <groupId>com.daijunyi</groupId> <version>1.0-SNAPSHOT</version>
4.0.0 cloudalibaba-consumer-nacos-order84 org.springframework.cloud spring-cloud-starter-openfeign com.alibaba.cloud spring-cloud-starter-alibaba-nacos-discovery com.alibaba.cloud spring-cloud-starter-alibaba-sentinel com.daijunyi cloud-api-commons ${project.version} org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-actuator org.springframework.boot spring-boot-devtools runtime true org.projectlombok lombok true org.springframework.boot spring-boot-starter-test test
- 修改yaml
```yaml
server:
port: 84
spring:
application:
name: nacos-order-consumer
cloud:
nacos:
discovery:
server-addr: localhost:1111
sentinel:
transport:
dashboard: localhost:8080
port: 8719
service-url:
nacos-user-service: http://nacos-payment-provider
- 主启动类 ```java package com.daijunyi;
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication @EnableDiscoveryClient @EnableFeignClients public class ConsumerNacosOrder84 { public static void main(String[] args) { SpringApplication.run(ConsumerNacosOrder84.class,args); } }
- 业务类
- ApplicationContextConfig
```java
package com.daijunyi.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
- CircleBreakerController ```java package com.daijunyi.controller;
import com.alibaba.csp.sentinel.annotation.SentinelResource; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.daijunyi.entities.CommonResult; import com.daijunyi.entities.Payment; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
@RestController public class CircleBreakerController {
@Value("${service-url.nacos-user-service}")
public String SERVICE_URL;
@Resource
private RestTemplate restTemplate;
@RequestMapping("/consumer/fallback/{id}")
//@SentinelResource(value = "fallback") //没有配置
//@SentinelResource(value = "fallback",fallback = "handlerFallback") //fallback只负责业务异常
//@SentinelResource(value = "fallback",blockHandler = "blockHandler") //blockHandler只负责sentinel控制台配置违规
@SentinelResource(value = "fallback",fallback = "handlerFallback",blockHandler = "blockHandler",
exceptionsToIgnore = {IllegalArgumentException.class})
public CommonResult<Payment> fallback(@PathVariable Long id) {
CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/"+id, CommonResult.class,id);
if (id == 4) {
throw new IllegalArgumentException ("IllegalArgumentException,非法参数异常....");
}else if (result.getData() == null) {
throw new NullPointerException ("NullPointerException,该ID没有对应记录,空指针异常");
}
return result;
}
//fallback
public CommonResult handlerFallback(@PathVariable Long id, Throwable e) {
Payment payment = new Payment(id,"null");
return new CommonResult<>(444,"兜底异常handlerFallback,exception内容 "+e.getMessage(),payment);
}
//blockHandler
public CommonResult blockHandler(@PathVariable Long id, BlockException blockException) {
Payment payment = new Payment(id,"null");
return new CommonResult<>(445,"blockHandler-sentinel限流,无此流水: blockException "+blockException.getMessage(),payment);
}
}
<a name="QEk9k"></a>
#### 3、测试
- 服务列表
- ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12971636/1631179693088-36b46250-e108-41ec-9a93-8321e46cd0e1.png#clientId=ubabf230a-8a0d-4&from=paste&height=288&id=u4a95c439&margin=%5Bobject%20Object%5D&name=image.png&originHeight=576&originWidth=2858&originalType=binary&ratio=1&size=138761&status=done&style=none&taskId=u8438198d-e4a8-4896-85de-d1d57f739d5&width=1429)
- 访问[http://localhost:84/consumer/fallback/1](http://localhost:84/consumer/fallback/1)
- ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12971636/1631179760819-db40b14c-4fc3-453f-9b59-b7b1aaee94c8.png#clientId=ubabf230a-8a0d-4&from=paste&height=71&id=u4b47182f&margin=%5Bobject%20Object%5D&name=image.png&originHeight=142&originWidth=1782&originalType=binary&ratio=1&size=30509&status=done&style=none&taskId=u11f7a6e8-7862-4828-a8fe-2066ec5bc52&width=891)
- sentinel dashbord
- ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12971636/1631179834634-ec1f3a86-d5df-4072-b5e8-9e4a34bffdd6.png#clientId=ubabf230a-8a0d-4&from=paste&height=649&id=u0dbd7310&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1298&originWidth=2848&originalType=binary&ratio=1&size=238539&status=done&style=none&taskId=uf368655d-d92c-4568-b648-b66de6d4d1e&width=1424)
- 代码解析
- 当配置了@SentinelResource(value = "fallback") //没有配置
- 就是相当于只是一个资源路径,
- 不会对默认的sentinel仪表盘的相关规则进行降级
- 也不会对java运行时异常进行服务降级
- 当配置@SentinelResource(value = "fallback",fallback = "handlerFallback") //fallback只负责业务异常
- 对java异常会进行服务降级
- 对sentinel仪表盘的规则错误显示默认配置
- 当配置@SentinelResource(value = "fallback",blockHandler = "blockHandler") //blockHandler只负责sentinel控制台配置违规
- 对java异常不会进行服务降级
- 对sentinel仪表盘的规则会进行处理为自己的方法
- @SentinelResource(value = "fallback",fallback = "handlerFallback",blockHandler = "blockHandler",<br /> exceptionsToIgnore = {IllegalArgumentException.class})
- 对java异常进行服务降级
- 对sentinel的违规规则进行自定方法的处理
<a name="swh0H"></a>
### 2、Feign系列
- 修改84模块
- 添加openfeign库
```xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
修改yaml
#开启sentinel对feign的支持 feign: sentinel: enabled: true
增加service openfeign接口 ```java package com.daijunyi.service;
import com.daijunyi.entities.CommonResult; import com.daijunyi.entities.Payment; import com.daijunyi.service.openfeign.PaymentFallbackService; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.stereotype.Service; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable;
@Service @FeignClient(name = “nacos-payment-provider”,fallback = PaymentFallbackService.class) public interface PaymentService {
@GetMapping(value = "/paymentSQL/{id}")
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);
}
- 增加实现类
```java
package com.daijunyi.controller;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.daijunyi.entities.CommonResult;
import com.daijunyi.entities.Payment;
import com.daijunyi.service.PaymentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
@RestController
public class PaymentController {
@Autowired
private PaymentService paymentService;
@RequestMapping("/consumer/payment/{id}")
@SentinelResource(value = "payment",blockHandler = "blockHandler")
public CommonResult<Payment> fallback(@PathVariable Long id) {
CommonResult<Payment> result = paymentService.paymentSQL(id);
return result;
}
//blockHandler
public CommonResult blockHandler(@PathVariable Long id, BlockException blockException) {
Payment payment = new Payment(id,"null");
return new CommonResult<>(445,"blockHandler-sentinel限流,无此流水: blockException "+blockException.getMessage(),payment);
}
}
- 此处服务降级用的openFeign
- 限流控制用sentinel
- 配置一个限流控制
再修改一下9003进行睡眠20秒接口调用
一旦我们重启应用,Sentinel规则将消失,生产环境需要将配置规则进行持久化
2、怎么配置
将限流配置规则持久化进Nacos保存,只要刷新8401某个rest地址,sentinel控制台的流控规则就能看到,只要Nacos里面的配置不删除,针对8401上Sentinel上的流控规则持续有效
3、步骤
1、修改cloudalibaba-sentinel-service8401
修改pom
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency>
修改yaml ```yaml server: port: 84
spring: application: name: nacos-order-consumer cloud: nacos: discovery: server-addr: localhost:1111 sentinel: transport: dashboard: localhost:8080 port: 8719 datasource: #增加数据源配置 把sentinel流控规则持久化进nacos ds1: nacos: server-addr: localhost:1111 dataId: cloudalibaba-sentinel-service groupId: DEFAULT_GROUP data-type: json rule-type: flow
service-url: nacos-user-service: http://nacos-payment-provider
开启sentinel对feign的支持
feign: sentinel: enabled: true
- nacos进行配置
![image.png](https://cdn.nlark.com/yuque/0/2021/png/12971636/1631182521549-71880ca3-c7b5-4ceb-84e1-c8dcd890b293.png#clientId=ubabf230a-8a0d-4&from=paste&height=532&id=u1b6da6c0&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1064&originWidth=1534&originalType=binary&ratio=1&size=106041&status=done&style=none&taskId=u5a22c608-865e-4d7d-8fe2-03aa8fe0ff9&width=767)
```json
[
{
"resource": "/retaLimit/byUrl",
"limitApp": "default",
"grade": 1,
"count": 1,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
}
]
- 重启84,并且请求http://localhost:84/consumer/payment/1(如果这个时候不请求这个借口,这个资源的流控规则不会显示出来)
- 在sentinel中可以看到流控规则
- 加入我们要修改这个count属性,只需要在nacos中修改一下,sentinel会自动同步
- 查看sentinel控制台
- 但是在sentinel中修改的值,是不会同步到nacos中的
- 但是sentinel修改之后,nacos再次修改还是会同步到sentinel中