# sentinel-demo
**Repository Path**: learning-fish/sentinel-demo
## Basic Information
- **Project Name**: sentinel-demo
- **Description**: Sentinel限流规则详解
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2023-11-20
- **Last Updated**: 2023-11-28
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# Sentinel限流规则详解
## 一、限流规则
### 1.1 流控入门
> 限制/order/{id}这个资源的单机请求QPS为5,即每秒只允许5次请求,超出请求拦截报错

> JMeter压测

`测试效果:`

### 1.2 流控模式
在添加限流规则时,点击高级选项,可以选择三种流控模式
- **直接:** 统计当前资源的请求,触发阈值时对当前资源直接限流,也是 `默认的模式`
- **关联:** 统计`关联资源`的请求,关联资源触发阈值时,对当前资源限流
- **链路:** 统计`从指定链路访问到本资源`的请求,触发阈值时,对指定链路限流

#### 1.2.1 直接模式(略)
#### 1.2.2 关联模式
> 对订单`/order/update`资源统计,当`order/update`资源QPS达到阈值`5`时,对`/order/query`限流

> JMeter压测 对`/order/query` 和 `/order/update`同时压测

`测试效果:`

#### 1.2.3 链路模式
> 特殊配置
**关闭context整合:**
```yaml
spring:
cloud:
sentinel:
# Sentinel 默认会将 Controller方法做context整合,导致链路的流控失效
web-context-unify: false # 关闭context整合
```
**`@SentinelResource` 资源标记:**
```java
@Component
public class CommonQuery {
/**
* 公共功能 保存和查询会使用该功能
*/
@SentinelResource(value = "order")
public void common() {
// 公共功能
System.out.println("common");
}
}
```
`关闭前后变化:`

> 对链路 `/order/query` 访问资源`order`统计,达到阈值`2`时,对链路`/order/query`限流

> JMeter压测 对`/order/query` 和 `/order/save`同时压测

`测试效果:`
`order`资源拒绝的QPS则为链路`/order/query`的请求

### 1.3 流控效果
在添加限流规则时,点击高级选项,可以选择三种流控效果
**快速失败:** 达到阈值后,新的请求会被立即拒绝并抛出`FlowException`异常。是默认的处理方式。
**Warm Up:** `预热模式`,对超出阈值的请求同样是拒绝并抛出异常。但这种模式阈值会动态变化,从一个较小值逐渐增加到最大阈值。
**排队等待:** 让所有的请求按照先后次序`排队执行`,两个请求的间隔不能小于指定时长,派对等待超过指定时长则会拒绝并抛出异常。

#### 1.3.1 快速失败(略)
#### 1.3.2 Warm Up
> 对`/order/{id}`资源设置限流,采用`Warm Up`的流控效果,请求阈值初始值为 threshold (`10`)/ coldFactor(`默认值3`),持续指定时长`5`秒后,逐渐提升到threshold

> JMeter压测 对`/order/{id}`压测

`测试效果:`

#### 1.3.3 排队等待
> 对`/order/{id}`资源设置限流,采用`派对等待`的流控效果,超时时间为`5`秒

> JMeter压测 对`/order/{id}`压测

`测试效果:`

### 1.4 热点参数限流
> **注意:** 热点参数限流`对默认的SpringMVC资源无效`,需采用`@SentinelResource` 资源标记,参数类型`目前只对Java基本类型和字符串生效`
```java
@SentinelResource("hot")
@GetMapping("/{id}")
public Result getOrderById(@PathVariable Long id) {
return Result.success(id);
}
```
> 对`hot`资源参数索引为 0(即id)进行限流,请求id`默认QPS不超过2,102的QPS不超过4,103的QPS不超过10`

> JMeter压测 对`hot`压测 对id为101、102、103的订单同时发起请求

`测试效果:`

## 二、隔离和降级
### 2.1 Feign整合sentinel
> 导入maven依赖
```xml
org.springframework.cloud
spring-cloud-starter-openfeign
```
> 启用Feign客户端
```java
@SpringBootApplication
@EnableFeignClients
public class SentinelApplication {
public static void main(String[] args) {
SpringApplication.run(SentinelApplication.class, args);
}
}
```
> 编写远程调用接口
```java
@FeignClient(value = "user-demo",url = "localhost:8888",fallbackFactory = UserClientFallbackFactory.class)
@Component
public interface UserClient {
@GetMapping("/user/{id}")
Result getUserById(@PathVariable("id") Long id);
}
@Slf4j
@Component
class UserClientFallbackFactory implements FallbackFactory {
@Override
public UserClient create(Throwable throwable) {
return new UserClient() {
@Override
public Result getUserById(Long id) {
if (throwable instanceof BlockException) {
Result result = null;
if (throwable instanceof FlowException) {
result = Result.failure(100, "接口被限流了");
} else if (throwable instanceof DegradeException) {
result = Result.failure(101, "服务降级了");
} else if (throwable instanceof ParamFlowException) {
result = Result.failure(102, "热点参数限流了");
} else if (throwable instanceof AuthorityException) {
result = Result.failure(104, "授权规则不通过");
}
log.info("sentinel 异常 ===> [{}]", result);
return result;
}
log.info("远程调用异常 ====> [{}]", throwable.getMessage());
return Result.failure("用户 [" + id + "] 远程调用异常");
}
};
}
}
```
> 编写 controller
```java
@RestController
@RequestMapping("/user")
@RequiredArgsConstructor
public class UserController {
private final UserClient userClient;
@GetMapping("/{id}")
public Result getUserById(@PathVariable("id") Long id) {
return userClient.getUserById(id);
}
}
```
> 开启feign整合sentinel
```yaml
feign:
sentinel:
enabled: true
```
`测试效果:`

### 2.2 线程隔离
> 前置知识
**信号量隔离:** 基于信号量的并发控制方法(`Sentinel默认采用`)。信号量是一个计数器,用于控制同时访问某个资源的线程数量。当一个线程要访问该资源时,首先会尝试获取信号量。如果信号量的计数器大于0,表示资源可用,线程可以继续访问资源,并将信号量计数器减1;如果信号量的计数器等于0,表示资源已经被占用,线程需要等待直到有其他线程释放资源并增加信号量计数器。通过控制信号量的计数器,可以限制同时访问某个资源的线程数量,从而实现资源的隔离。
**线程池隔离:** 基于线程池的并发控制方法。线程池是一组预先创建的线程,用于执行任务。当一个任务需要执行时,会从线程池中获取一个空闲的线程来执行任务,如果没有空闲线程,则任务会被放入等待队列中,直到有线程空闲时才会执行。线程池隔离通过控制线程池的大小,可以限制同时执行的任务数量,从而实现资源的隔离。
| 隔离方式 | 超时 | 熔断 | 隔离原理 | 异步调用 | 资源消耗 |
| ---------- | -------------- | ---- | -------------------- | ------------------------ | ---------------------------------------- |
| 线程池隔离 | 支持 | 支持 | 每个服务单独用线程池 | 可以是异步,也可以是同步 | 大,大量线程的上下文切换(适用于低扇出) |
| 信号量隔离 | 不支持主动超时 | 支持 | 通过信号量的计数器 | 同步调用,不支持异步 | 小,只是计数器(适用于高频调用,高扇出) |
> 对`GET:http://localhost:8888/user/{id}`开启线程隔离,线程数不超过`2`

> JMeter压测 对`user/{id}`压测,访问`GET:http://localhost:8888/user/{id}`

`测试效果:`

### 2.3 熔断降级
> 熔断降级是一种在分布式系统中应对`服务故障或异常情况`的策略。它的目的是防止故障的服务对系统整体的影响,保证系统的`稳定性和可用性`。

### 2.4 熔断策略
[熔断规则详解](https://github.com/alibaba/Sentinel/wiki/%E7%86%94%E6%96%AD%E9%99%8D%E7%BA%A7)
> 编写测试代码 id `101 慢调用`、`102异常调用`
```java
@RestController
@RequestMapping("/user")
@RequiredArgsConstructor
public class UserController {
private final UserClient userClient;
@SentinelResource(value = "degradation",
fallbackClass = SentinelHandler.class, blockHandlerClass = SentinelHandler.class,
fallback = "fallback", blockHandler = "blockHandler")
@GetMapping("/{id}")
public Result getUserById(@PathVariable("id") Long id) throws InterruptedException {
if (id == 101) {
Thread.sleep(60);
} else if (id == 102) {
throw new RuntimeException("get user by id 102 error");
}
return userClient.getUserById(id);
}
}
```
> 编写SentinelHandler
```java
@Slf4j
public class SentinelHandler {
public static Result blockHandler(Long id, BlockException e) {
Result result = null;
if (e instanceof FlowException) {
result = Result.failure(100, "接口被限流了");
} else if (e instanceof DegradeException) {
result = Result.failure(101, "服务降级了");
} else if (e instanceof ParamFlowException) {
result = Result.failure(102, "热点参数限流了");
} else if (e instanceof AuthorityException) {
result = Result.failure(104, "授权规则不通过");
}
log.info("sentinel 异常 ===> [{}]", result);
return result;
}
public static Result fallback(Long id, Throwable e) {
return Result.failure(e.getMessage());
}
}
```
#### 2.4.1 熔断策略-慢调用
> 对`degradation`资源进行慢调用测试,当QPS达到`10(最小请求数/统计时长)`且调用RT大于`最大RT 50`超过`5(10*0.5)次`,则对`degradation`资源熔断降级

> JMeter压测 对`degradation`压测 对id为101 进行压测

`测试效果:`

#### 2.4.2 熔断策略-异常比例(略)
#### 2.4.3 熔断策略-异常数(略)
## 三、授权规则
> 编写代码实现`RequestOriginParser`接口,返回值为授权规则对应的`流控应用`
```java
@Component
public class HeaderOriginParser implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest request) {
String auth = request.getHeader("auth");
return StringUtil.isBlank(auth) ? "blank" : auth;
}
}
```
> 对`/user/{id}`资源添加授权规则,当`流控应用`为`admin`时`白名单`放行

JMeter压测 对`/user/{id}`压测,测试添加header和不添加header效果

`测试效果:`

## 四、自定义异常结果
> `BlockException`异常子类信息
| 异常 | 说明 |
| :------------------: | :--------------: |
| FlowException | 限流异常 |
| ParamFlowException | 热点参数限流异常 |
| DegradeException | 降级异常 |
| AuthorityException | 授权规则异常 |
| SystemBlockException | 系统规则异常 |

> 实现`BlockExceptionHandler`接口,编写全局sentinel异常处理Handler
```java
@Slf4j
@Component
public class GlobalSentinelHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
Result result = null;
if (e instanceof FlowException) {
result = Result.failure(HttpStatus.TOO_MANY_REQUESTS.value(), "接口被限流了");
} else if (e instanceof DegradeException) {
result = Result.failure(HttpStatus.TOO_MANY_REQUESTS.value(), "服务降级了");
} else if (e instanceof ParamFlowException) {
result = Result.failure(HttpStatus.TOO_MANY_REQUESTS.value(), "热点参数限流了");
} else if (e instanceof AuthorityException) {
result = Result.failure(HttpStatus.UNAUTHORIZED.value(), "授权规则不通过");
}
log.info("sentinel 异常 ===> [{}]", result);
Object json = JSON.toJSON(result);
httpServletResponse.setHeader("Content-Type", "application/json;charset=UTF-8");
PrintWriter writer = httpServletResponse.getWriter();
writer.print(json);
writer.close();
}
}
```
> 添加限流规则

`测试效果:`

## 五、持久化-push模式
### 5.1 持久化配置
> 添加持久化依赖
```xml
com.alibaba.csp
sentinel-datasource-nacos
com.alibaba.cloud
spring-cloud-starter-alibaba-sentinel
```
> 添加yaml配置
```yaml
default-cloud:
nacos:
server-addr: localhost:8848
username: nacos
password: nacos
sentinel:
datasource:
nacos:
namespace: sentinel
groupId: DEFAULT_GROUP
data-type: json
spring:
application:
name: sentinel-demo
cloud:
sentinel:
transport:
dashboard: 127.0.0.1:8080
# Sentinel 默认会将 Controller方法做context整合,导致链路的流控失效
web-context-unify: false # 关闭context整合
datasource:
# 接口限流规则
flow:
nacos:
server-addr: ${default-cloud.nacos.server-addr}
username: ${default-cloud.nacos.username}
password: ${default-cloud.nacos.password}
namespace: ${default-cloud.sentinel.datasource.nacos.namespace}
groupId: ${default-cloud.sentinel.datasource.nacos.groupId}
data-type: ${default-cloud.sentinel.datasource.nacos.data-type}
dataId: ${spring.application.name}-flow-rules
rule-type: flow
# 接口熔断降级规则
degrade:
nacos:
server-addr: ${default-cloud.nacos.server-addr}
username: ${default-cloud.nacos.username}
password: ${default-cloud.nacos.password}
namespace: ${default-cloud.sentinel.datasource.nacos.namespace}
groupId: ${default-cloud.sentinel.datasource.nacos.groupId}
data-type: ${default-cloud.sentinel.datasource.nacos.data-type}
dataId: ${spring.application.name}-degrade-rules
rule-type: degrade
server:
port: 8088
# feign 整合 sentinel
feign:
sentinel:
enabled: true
```
### 5.2限流规则配置
> 参数说明
- **resource:** 资源名
- **limitApp:** 来源应用,`默认值 default`
- **grade:** 阈值类型
- QPS(1)`默认值`
- 并发线程数(0)`无流控效果`
- **count:** 单机阈值、均摊阈值、集群阈值
- **clusterMode:** 是否集群限流
- false `默认值`
- true `无流控模式、无流控效果`
- clusterConfig.fallbackToLocalWhenFail 失败退化 默认值 false
- clusterConfig.flowId 全局唯一id
- clusterConfig.thresholdType 均摊阈值(`默认值`0)、集群阈值(1)
- **strategy:** 流控模式
- 直接(0)`默认值`
- 关联(1)refResource 关联资源
- 链路(2)refResource 入口资源
- **controlBehavior:** 流控效果
- 快速失败(0)`默认值`
- Warm Up(1)warmUpPeriodSec 预热时长(秒)
- 排队等待(2)maxQueueingTimeMs 超时时间(毫秒)
> 案例

```json
[
{
"resource":"test1",
"grade":1,
"count":2
},
{
"resource":"test2",
"grade":1,
"count":2,
"strategy":1,
"refResource":"test",
"controlBehavior":1,
"warmUpPeriodSec":5
},
{
"resource":"test3",
"grade":1,
"count":2,
"clusterMode":true,
"clusterConfig" : {
"flowId" : 1,
"fallbackToLocalWhenFail" : true
}
}
]
```
> 效果

其他规则参考 ====> [Spring Cloud Alibaba Sentinel 整合 nacos 进行规则持久化](https://developer.aliyun.com/article/897365)