Spring Cloud Alibaba Sentinel 简单使用

Sentinel

Sentinel 主要功能

  1. 流量控制: 可以通过配置规则对接口的访问进行限制, 避免因流量过高而导致的系统崩溃.
  2. 服务熔断: 当后端服务不可用或异常时, 可以通过配置熔断规则快速失败并返回错误信息, 避免连锁故障.
  3. 系统负载保护: 根据系统负载情况, 自动控制流量的通过, 防止系统出现过载.
  4. 统计和监控: 提供实时的流量控制和熔断信息统计

Sentinel 作用

  1. 防止雪崩效应: 当访问发生异常时 Sentinel 可以通过熔断机制返回错误信息, 从而防止其他服务也出现故障.
  2. 流量控制: Sentinel 可以通过限流机制, 限制并发请求数量, 来防止突然出现大量请求导致系统过载.
  3. 保护系统稳定性: Sentinel 可以对请求进行限流和熔断, 避免某个服务发生故障从而导致系统不可用.

常见的流量控制算法

计数器算法

计数器算法是在一定时间内记录请求次数, 当超过规定时间后计数器就会清零然后重新开始计算, 当请求超过间隔时间内最大次数时就会拒绝访问

计数器算法的特点是: 实现简单, 但是存在"突刺现象"

漏桶算法

漏桶算法工作原理类似于一个底部有小孔的桶, 无论流入多少水, 流出的速度始终保持恒定. 当桶满时, 新的水会溢出, 即超出的流量被丢弃, 这种算法能够平滑突发流量, 常用于限制数据的传输速度

漏桶算法提供了一种机制, 可以让突发流量被整形, 以便为系统提供稳定的请求. 如: Sentinel 中流量整形(匀速排队功能)

令牌桶算法

令牌桶算法中, 存在一个固定大小的令牌桶, 该桶会以恒定的速率源源不断地产生令牌, 当系统需要发送数据时, 会从令牌桶中取出一个令牌, 如果桶中没有可用的令牌, 那么该次数据发送就会被限制, 这种机制确保了即使面临突发流量, 系统也能保持稳定运行

令牌桶算法和漏桶算法的区别是: 漏桶算法是匀速的处理请求, 而令牌桶算法可以应对突发的流量.

Sentinel 流量控制

流量控制的三种效果:

  1. 快速失败: 当请求触发限流规则后, 会被理解拒绝并抛出 FlowException. 这种方法适用于对系统处理能力确切已知的情况.
  2. 排队等待(匀速通过): 排队等待会控制请求的间隔时间, 让请求稳定并且匀速通过. 这种方法可以用来处理间隔性突发的高流量.
  3. 冷启动(Warm Up): 该方法是通过让流量缓慢增加, 在一定时间内逐渐增加到阈值上限, 从而防止流量突然增加时, 直接把系统拉升到高水位可能瞬间把系统压垮.

Sentinel 熔断

熔断策略也是三种:

  1. 慢调用比例: 在统计时长内, 如果时间超过最大RT则为慢请求, 如果慢请求超过阈值, 并且请求数量大于最小请求数, 就会触发熔断
  2. 异常比例: 在统计时长内, 如果请求的比例大于阈值, 并且请求数大于最小请求数, 就会触发熔断
  3. 异常数: 在统计时长内, 如果异常数大于阈值, 并且请求数大于最小请求数, 就会触发熔断

Sentinel 基本使用

添加依赖

xml 复制代码
<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

定义资源

定义资源可通过代码或者注解实现
通过代码方式定义资源:

java 复制代码
@RequestMapping("/getid")
public String getId() {
    try(Entry entry = SphU.entry("getid")) { // 定义资源
        return "getId: " + new Random().nextInt(100);
    }catch (BlockException e) {
        // 当前资源已经被限流或熔断
        return "被限制";
    }
}

通过注解方式定义资源:

java 复制代码
@SentinelResource(value = "getname", blockHandler = "myBlockHandler")
    @RequestMapping("/getname")
    public String getName() throws InterruptedException {
        Thread.sleep(100);
        return "getName: " + new Random().nextInt(100);
    }

    /**
     * 限流之后的执行方法
     * @param blockException
     * @return
     */
    public String myBlockHandler(BlockException blockException) {
        if (blockException instanceof FlowException) {
            return "限流";
        } else if (blockException instanceof DegradeException) {
            return "熔断";
        }
        return "被限制";
    }

注意:

  1. myBlockHandler 的返回类型和参数必须和绑定的方法一致, 否则就会报错
  2. myBlockHandler 必须包含 BlockException 参数

@SentinelResource 属性说明

  • value: 资源名称(必填)
  • entryType: 资源调用的流量类型 EntryType.IN/EntryType.OUT(默认)
  • blockHandler: 限流或熔断执行时对应的方法
  • fallback/fallbackClass: 非 BlockException 时, 其他非限流/熔断是异常对应的方法
  • exceptionsToIgnore: 用于指定哪些异常被排除掉, 不好计入异常统计和 fallback 逻辑中

定义限流规则

java 复制代码
    private static void initFlowRules() {
        List<FlowRule> rules = new ArrayList<>();
        FlowRule rule = new FlowRule();
        rule.setResource("getname"); // 必须, 资源名
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS); // 必须, 限流指标 QPS/线程数
        rule.setCount(1); // 必须, 限流数量: 上一步线程数或QPS值
        rules.add(rule);
        FlowRuleManager.loadRules(rules);
    }

定义熔断规则

java 复制代码
    private static void initDegradeRules() {
        List<DegradeRule> rules = new ArrayList<>();
        DegradeRule rule = new DegradeRule();
        rule.setResource("getname"); // 资源名
        rule.setGrade(RuleConstant.DEGRADE_GRADE_RT); // 熔断策略
        rule.setCount(10); // RT值
        rule.setStatIntervalMs(1000); // 统计时长
        rule.setSlowRatioThreshold(0.5); // 阈值
        rule.setMinRequestAmount(1); // 最小请求数
        rule.setTimeWindow(5); // 熔断时长 单位为秒
        rules.add(rule);
        DegradeRuleManager.loadRules(rules);

    }

如何判断熔断还是限流

java 复制代码
if (blockException instanceof FlowException) {
    return "限流";
} else if (blockException instanceof DegradeException) {
    return "熔断";
}

自定义 Sentinel 异常

Sentinel 异常分为三种: 局部自定义异常, 全局自定义异常, 系统自定义异常

局部自定义异常

java 复制代码
    @SentinelResource(value = "getname", blockHandler = "myBlockHandler")
    @RequestMapping("/getname")
    public String getName() throws InterruptedException {
        Thread.sleep(100);
        return "getName: " + new Random().nextInt(100);
    }

    /**
     * 限流之后的执行方法
     * @param blockException
     * @return
     */
    public String myBlockHandler(BlockException blockException) {
        if (blockException instanceof FlowException) {
            return "限流";
        } else if (blockException instanceof DegradeException) {
            return "熔断";
        }
        return "被限制";
    }

局部自定义异常只需要创建一个方法然后在@SentinelResource 中设置即可

全局自定义异常

java 复制代码
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

/**
 * 定义 Sentinel 全局自定义异常
 */
@Component
public class SentinelExceptionHandler implements BlockExceptionHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
        int code = HttpStatus.TOO_MANY_REQUESTS.value();
        String msg = "未知异常";
        if (e instanceof FlowException) {
            msg = "被限流";
        } else if (e instanceof DegradeException) {
            msg = "被熔断";
        } else if (e instanceof ParamFlowException) {
            msg = "请求触发了热点限流";
        } else if (e instanceof AuthorityException) {
            code = HttpStatus.UNAUTHORIZED.value();
            msg = "暂无权限";
        }
        httpServletResponse.setContentType("application/json;charset=utf-8");
        httpServletResponse.setStatus(code);
        httpServletResponse.getWriter().println("{\"code\":" + code + ", \"msg\":\"" + msg +"\"}");
    }
}

全局自定义异常需要实现 BlockExceptionHandler 接口然后重写 handle 方法

系统自定义异常

如果只配置熔断和限流的情况下配置全局自定义异常即可, 但是配置一些特殊的规则如: 热点规则, 全局自定义异常就不会起作用, 就需要配置系统自定义异常

java 复制代码
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.HashMap;
import java.util.Objects;

/**
 * 自定义系统异常
 */
@RestControllerAdvice
public class SystemException {
    @ExceptionHandler(ParamFlowException.class)
    public HashMap<String, Object> ParamFlowException(ParamFlowException e) {
        return new HashMap<>(){{
            put("code", HttpStatus.TOO_MANY_REQUESTS.value());
            put("msg", "热点限流");
        }};
    }
}

使用 Nacos 存储配置

Sentinel 配置的规则默认存储到内存中, 会随着项目的重启而清空, 所以就需要对这些规则进行持久化配置

引入依赖

xml 复制代码
<dependency>
  <groupId>com.alibaba.cloud</groupId>
  <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
  <groupId>com.alibaba.csp</groupId>
  <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

设置配置文件

yaml 复制代码
spring:
  application:
    name: sentinel-demo
  cloud:
    sentinel:
      transport:
        dashboard: localhost:18080
      datasource:
        ds:
          nacos:
            server-addr: localhost:8848
            username: nacos
            password: nacos
            data-id: ${spring.application.name}-flow-rules
            group-id: DEFAULT_GROUP
            data-type: json
            rule-type: flow
        ds2:
          nacos:
            server-addr: localhost:8848
            username: nacos
            password: nacos
            data-id: ${spring.application.name}-degrade-demo
            group-id: DEFAULT_GROUP
            data-type: json
            rule-type: degrade

Nacos 中设置配置信息

限流:

json 复制代码
[
  {
    "resource":"/user/getname",
    "limitApp":"default",
    "grade":1,
    "count":2,
    "strategy":0,
    "controlBehavior":0,
    "clusterMode":false

  },
  {
    "resource":"/user/getid",
    "limitApp":"default",
    "grade":1,
    "count":2,
    "strategy":0,
    "controlBehavior":0,
    "clusterMode":false

  }
]


熔断:

json 复制代码
[{
    "resource":"/user/getname",
    "grade":0,
    "count":10,
    "timeWindow":5,
    "minRequestAmount":1,
    "statIntervalMs":1000,
    "slowRationThreshold":0.5
}]
相关推荐
十叶知秋4 分钟前
【jmeter】jmeter的线程组功能的详细介绍
数据库·jmeter·性能测试
七星静香16 分钟前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
Jacob程序员17 分钟前
java导出word文件(手绘)
java·开发语言·word
ZHOUPUYU17 分钟前
IntelliJ IDEA超详细下载安装教程(附安装包)
java·ide·intellij-idea
stewie621 分钟前
在IDEA中使用Git
java·git
Elaine20239136 分钟前
06 网络编程基础
java·网络
G丶AEOM37 分钟前
分布式——BASE理论
java·分布式·八股
落落鱼201338 分钟前
tp接口 入口文件 500 错误原因
java·开发语言
想要打 Acm 的小周同学呀39 分钟前
LRU缓存算法
java·算法·缓存
镰刀出海42 分钟前
Recyclerview缓存原理
java·开发语言·缓存·recyclerview·android面试