Sentinel原理与SpringBoot整合实战

前言

随着微服务架构的广泛应用,服务和服务之间的稳定性变得越来越重要。在高并发场景下,如何保障服务的稳定性和可用性成为了一个关键问题。阿里巴巴开源的Sentinel作为一个面向分布式服务架构的流量控制组件,提供了从流量控制、熔断降级、系统负载保护等多个维度来保障服务稳定性的解决方案。

本文将详细介绍Sentinel的核心原理和基本功能点,并提供一个完整的SpringBoot整合Sentinel的案例,帮助读者快速掌握Sentinel的使用方法。

一、Sentinel简介

1.1 什么是Sentinel

Sentinel是阿里巴巴开源的,面向分布式服务架构的流量控制组件,主要以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度来保障服务的稳定性。

Sentinel的主要特性包括:

  • 丰富的应用场景:Sentinel承接了阿里巴巴近10年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
  • 完备的实时监控:Sentinel同时提供实时的监控功能。用户可以在控制台中看到接入应用的单台机器秒级数据,甚至500台以下规模的集群的汇总运行情况。
  • 广泛的开源生态:Sentinel提供开箱即用的与其它开源框架/库的整合模块,例如与Spring Cloud、Apache Dubbo、gRPC、Quarkus的整合。用户只需要引入相应的依赖并进行简单的配置即可快速地接入Sentinel。同时Sentinel提供Java/Go/C++等多语言的原生实现。
  • 完善的SPI扩展机制:Sentinel提供简单易用、完善的SPI扩展接口。用户可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。

1.2 Sentinel与其他熔断降级工具的对比

相比于其他熔断降级工具(如Hystrix),Sentinel具有以下优势:

  1. 丰富的流量控制:Sentinel提供了更丰富的流量控制策略,包括基于调用关系的流量控制、热点参数限流等。
  2. 实时监控:Sentinel提供了实时的监控功能,可以实时查看接入应用的运行情况。
  3. 轻量级设计:Sentinel采用轻量级设计,不依赖任何框架/库,能够运行于所有Java运行时环境。
  4. 多语言支持:Sentinel提供了Java/Go/C++等多语言的原生实现。
  5. 完善的SPI扩展机制:Sentinel提供了完善的SPI扩展接口,用户可以通过实现扩展接口来快速地定制逻辑。

二、Sentinel核心原理

2.1 基本工作原理

Sentinel的核心工作原理可以概括为:

  1. 资源定义:资源是Sentinel的核心概念,它可以是Java应用程序中的任何内容,例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码。只要通过Sentinel API定义的代码,就是资源。

  2. 规则配置:Sentinel通过规则来指定资源的访问控制策略。规则包括流量控制规则、熔断降级规则、系统保护规则、来源访问控制规则和热点参数规则等。

  3. 统计与判断:Sentinel会实时统计资源的调用数据,并根据预设的规则进行判断,若超出阈值,则采取相应的流量控制措施。

2.2 Sentinel的执行流程

Sentinel的执行流程主要分为以下几个步骤:

  1. 资源埋点 :通过SphU.entry(resourceName)entry.exit()方法对资源进行埋点。
  2. 规则判断:当资源被访问时,Sentinel会根据预设的规则进行判断。
  3. 流量控制:如果触发了规则条件,Sentinel会执行相应的流量控制措施,例如直接拒绝、排队等待或预热模式。
  4. 熔断降级:对于响应时间过长或异常比例过高的服务,Sentinel会进行熔断降级处理。
  5. 系统自适应保护:当系统负载过高时,Sentinel会自动进行系统保护。

2.3 Sentinel的核心组件

Sentinel的核心组件主要包括:

  1. Sentinel核心库:不依赖任何框架/库,能够运行于所有Java运行时环境,同时对Dubbo/Spring Cloud等框架也有较好的支持。
  2. Sentinel控制台:基于Spring Boot开发,打包后可以直接运行,不需要额外的Tomcat等应用容器。

三、Sentinel基本功能点

3.1 流量控制

流量控制(flow control)是Sentinel最核心的功能之一,其原理是监控应用流量的QPS或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。

流量控制主要有以下几个维度:

  1. 基于QPS/并发数的流量控制

    • 并发数控制:用于保护业务线程池不被慢调用耗尽。
    • QPS控制:当QPS超过某个阈值的时候,采取措施进行流量控制。
  2. 流量控制效果

    • 直接拒绝:默认的流量控制方式,当QPS超过任意规则的阈值后,新的请求就会被立即拒绝。
    • Warm Up:预热/冷启动方式,让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限。
    • 匀速排队:严格控制请求通过的间隔时间,让请求以均匀的速度通过,对应的是漏桶算法。
  3. 基于调用关系的流量控制

    • 根据调用方来源进行流量控制。
    • 支持针对不同的调用方设置不同的流控策略。

3.2 熔断降级

熔断降级(circuit breaking)是Sentinel的另一个重要功能,它用于当调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。

熔断降级主要有以下几个维度:

  1. 慢调用比例:当资源的响应时间超过阈值(慢调用)的比例超过设定的比例阈值时,触发熔断。
  2. 异常比例/异常数:当资源的异常比例或异常数超过阈值时,触发熔断。
  3. 恢复策略:熔断触发后,经过一段时间(即熔断超时时间),熔断器会进入探测恢复状态,若接下来的一个请求处理成功,则结束熔断,否则继续熔断。

3.3 热点参数限流

热点参数限流是一种更细粒度的流量控制,它允许用户针对某个热点参数进行限流,例如针对某个用户ID限流。

热点参数限流的特点:

  1. 可以针对某个参数的特定值单独设置限流阈值。
  2. 支持多种参数类型,包括基本类型和字符串类型。
  3. 可以设置参数例外项,对特定的参数值进行不同的限流处理。

3.4 系统自适应限流

系统自适应限流从整体维度对应用入口流量进行控制,结合应用的Load、CPU使用率、总体平均RT、入口QPS和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。

系统规则包含以下几个重要的参数:

  1. Load:当系统load1超过阈值,且系统当前的并发线程数超过系统容量时才会触发系统保护。
  2. CPU使用率:当系统CPU使用率超过阈值即触发系统保护。
  3. 平均RT:当单台机器上所有入口流量的平均RT达到阈值即触发系统保护。
  4. 并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
  5. 入口QPS:当单台机器上所有入口流量的QPS达到阈值即触发系统保护。

3.5 黑白名单控制

黑白名单控制根据资源的请求来源(origin)限制资源是否通过:

  1. 白名单:来源(origin)在白名单内的请求才可通过。
  2. 黑名单:来源(origin)在黑名单内的请求不允许通过。

3.6 实时监控

Sentinel提供实时的监控功能,用户可以实时查看接入应用的单台机器秒级数据,甚至500台以下规模的集群的汇总运行情况。监控数据包括:

  1. 通过的请求数(pass):一秒内到来到的请求。
  2. 被阻止的请求数(blocked):一秒内被流量控制的请求数量。
  3. 成功处理的请求数(success):一秒内成功处理完的请求。
  4. 总请求数(total):一秒内到来的请求以及被阻止的请求总和。
  5. 平均响应时间(RT):一秒内该资源的平均响应时间。
  6. 异常数(exception):一秒内业务本身异常的总和。

3.7 动态规则配置

Sentinel支持多种动态规则源,包括:

  1. 文件配置:规则存储在文件中,定期轮询文件获取规则。
  2. Nacos配置中心:规则存储在Nacos配置中心,通过监听配置变更事件即时获取规则。
  3. ZooKeeper:规则存储在ZooKeeper中,通过监听节点变更事件即时获取规则。
  4. Apollo配置中心:规则存储在Apollo配置中心,通过监听配置变更事件即时获取规则。
  5. Redis:规则存储在Redis中,通过定期轮询获取规则。

四、SpringBoot整合Sentinel完整案例

下面我们将通过一个完整的案例,演示如何在SpringBoot项目中整合Sentinel。

4.1 项目结构

复制代码
springcloud-sentinel
├── src
│   └── main
│       ├── java
│       │   └── com.example
│       │       ├── config
│       │       │   └── SentinelConfig.java
│       │       ├── controller
│       │       │   └── TestSentinelController.java
│       │       ├── service
│       │       │   └── HelloService.java
│       │       └── SentinelApplication.java
│       └── resources
│           └── application.properties
└── pom.xml

4.2 Maven依赖配置

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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>springboot-sentinel-demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <java.version>1.8</java.version>
        <spring-boot.version>2.6.3</spring-boot.version>
        <spring-cloud-alibaba.version>2021.0.1.0</spring-cloud-alibaba.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <!-- Spring Boot 依赖管理 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            
            <!-- Spring Cloud Alibaba 依赖管理 -->
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring-cloud-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <!-- Spring Boot Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <!-- Sentinel 依赖 -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        
        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot.version}</version>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

4.3 应用配置文件

properties 复制代码
# application.properties
spring.application.name=sentinel-demo
server.port=8080

# Sentinel 控制台地址
spring.cloud.sentinel.transport.dashboard=localhost:8080
# 取消Sentinel控制台懒加载
spring.cloud.sentinel.eager=true
# 设置应用与Sentinel控制台的心跳间隔时间
spring.cloud.sentinel.transport.heartbeat-interval-ms=3000

4.4 Sentinel配置类

java 复制代码
package com.example.config;

import com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * Sentinel配置类
 * 注册SentinelResourceAspect,用于支持@SentinelResource注解
 */
@Configuration
public class SentinelConfig {
    
    @Bean
    public SentinelResourceAspect sentinelResourceAspect() {
        return new SentinelResourceAspect();
    }
}

4.5 服务类

java 复制代码
package com.example.service;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.springframework.stereotype.Service;

/**
 * 业务服务类
 * 使用@SentinelResource注解定义资源点
 */
@Service
public class HelloService {
    
    /**
     * 定义一个资源点,指定blockHandler处理限流异常
     * @param name 参数
     * @return 返回结果
     */
    @SentinelResource(value = "sayHello", blockHandler = "sayHelloExceptionHandler")
    public String sayHello(String name) {
        return "Hello, " + name;
    }
    
    /**
     * 定义一个资源点,同时指定blockHandler和fallback
     * blockHandler: 处理限流异常
     * fallback: 处理业务异常
     * @param name 参数
     * @return 返回结果
     */
    @SentinelResource(value = "circuitBreaker", 
                     fallback = "circuitBreakerFallback", 
                     blockHandler = "sayHelloExceptionHandler")
    public String circuitBreaker(String name) {
        // 模拟业务逻辑,当name为"test"时抛出异常
        if ("test".equals(name)) {
            return "Hello, " + name;
        }
        throw new RuntimeException("发生异常");
    }
    
    /**
     * 熔断降级处理方法,处理业务异常
     * @param name 参数
     * @return 降级返回结果
     */
    public String circuitBreakerFallback(String name) {
        return "服务异常,熔断降级,请稍后重试!";
    }
    
    /**
     * 限流降级处理方法,处理限流异常
     * @param name 参数
     * @param ex 限流异常
     * @return 限流返回结果
     */
    public String sayHelloExceptionHandler(String name, BlockException ex) {
        return "访问过快,限流降级,请稍后重试!";
    }
}

4.6 控制器类

java 复制代码
package com.example.controller;

import com.example.service.HelloService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * 测试Sentinel的控制器
 */
@RestController
@RequestMapping("/sentinel")
public class TestSentinelController {
    
    @Autowired
    private HelloService helloService;
    
    /**
     * 测试限流功能
     * @param name 参数
     * @return 返回结果
     */
    @GetMapping("/hello")
    public String hello(@RequestParam("name") String name) {
        return helloService.sayHello(name);
    }
    
    /**
     * 测试熔断功能
     * @param name 参数
     * @return 返回结果
     */
    @GetMapping("/circuit")
    public String circuitBreaker(@RequestParam("name") String name) {
        return helloService.circuitBreaker(name);
    }
}

4.7 应用启动类

java 复制代码
package com.example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * Sentinel示例应用启动类
 */
@SpringBootApplication
public class SentinelApplication {
    
    public static void main(String[] args) {
        SpringApplication.run(SentinelApplication.class, args);
    }
}

4.8 运行与测试

4.8.1 下载并启动Sentinel控制台
  1. 下载Sentinel控制台jar包:

    bash 复制代码
    wget https://github.com/alibaba/Sentinel/releases/download/1.8.6/sentinel-dashboard-1.8.6.jar
  2. 启动Sentinel控制台:

    bash 复制代码
    java -Dserver.port=8080 -jar sentinel-dashboard-1.8.6.jar
  3. 访问Sentinel控制台:

    复制代码
    http://localhost:8080

    默认用户名和密码都是:sentinel

4.8.2 启动SpringBoot应用
  1. 启动应用:

    bash 复制代码
    mvn spring-boot:run
  2. 测试限流功能:

    复制代码
    http://localhost:8080/sentinel/hello?name=world
  3. 测试熔断功能:

    复制代码
    http://localhost:8080/sentinel/circuit?name=test
    
    http://localhost:8080/sentinel/circuit?name=other
4.8.3 在Sentinel控制台配置规则
  1. 登录Sentinel控制台后,可以看到应用已经注册到控制台

  2. 配置流控规则:

    • 点击"簇点链路",找到"sayHello"资源
    • 点击"流控"按钮,设置QPS阈值为5
    • 保存规则
  3. 配置熔断规则:

    • 点击"簇点链路",找到"circuitBreaker"资源
    • 点击"降级"按钮,设置异常比例阈值为0.5,时间窗口为10s
    • 保存规则
  4. 测试规则效果:

    • 快速刷新限流接口,当QPS超过5时,会触发限流
    • 多次访问熔断接口并传入非"test"参数,当异常比例超过50%时,会触发熔断

4.9 高级配置

4.9.1 持久化规则配置

Sentinel支持多种数据源来持久化规则配置,以下是使用文件配置的示例:

properties 复制代码
# 配置Sentinel规则持久化到文件
spring.cloud.sentinel.datasource.ds1.file.file=classpath:sentinel/flow-rule.json
spring.cloud.sentinel.datasource.ds1.file.data-type=json
spring.cloud.sentinel.datasource.ds1.file.rule-type=flow

spring.cloud.sentinel.datasource.ds2.file.file=classpath:sentinel/degrade-rule.json
spring.cloud.sentinel.datasource.ds2.file.data-type=json
spring.cloud.sentinel.datasource.ds2.file.rule-type=degrade
4.9.2 Nacos配置中心持久化
properties 复制代码
# 配置Sentinel规则持久化到Nacos
spring.cloud.sentinel.datasource.ds1.nacos.server-addr=localhost:8848
spring.cloud.sentinel.datasource.ds1.nacos.data-id=sentinel-flow-rules
spring.cloud.sentinel.datasource.ds1.nacos.group-id=SENTINEL_GROUP
spring.cloud.sentinel.datasource.ds1.nacos.data-type=json
spring.cloud.sentinel.datasource.ds1.nacos.rule-type=flow

spring.cloud.sentinel.datasource.ds2.nacos.server-addr=localhost:8848
spring.cloud.sentinel.datasource.ds2.nacos.data-id=sentinel-degrade-rules
spring.cloud.sentinel.datasource.ds2.nacos.group-id=SENTINEL_GROUP
spring.cloud.sentinel.datasource.ds2.nacos.data-type=json
spring.cloud.sentinel.datasource.ds2.nacos.rule-type=degrade
4.9.3 自定义全局异常处理
java 复制代码
package com.example.config;

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 com.alibaba.csp.sentinel.slots.system.SystemBlockException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;

/**
 * 自定义Sentinel全局异常处理
 */
@Configuration
public class SentinelBlockExceptionHandler implements BlockExceptionHandler {
    
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, BlockException e) throws Exception {
        // 设置响应类型
        response.setStatus(429);
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setCharacterEncoding("UTF-8");
        
        Map<String, Object> result = new HashMap<>();
        result.put("code", 429);
        result.put("success", false);
        
        if (e instanceof FlowException) {
            result.put("message", "请求被限流了");
        } else if (e instanceof DegradeException) {
            result.put("message", "请求被降级了");
        } else if (e instanceof ParamFlowException) {
            result.put("message", "热点参数限流");
        } else if (e instanceof SystemBlockException) {
            result.put("message", "系统规则限制");
        } else if (e instanceof AuthorityException) {
            result.put("message", "授权规则不通过");
        } else {
            result.put("message", "未知限流降级");
        }
        
        // 返回JSON数据
        PrintWriter out = response.getWriter();
        out.write(new ObjectMapper().writeValueAsString(result));
        out.flush();
        out.close();
    }
}

五、总结

本文详细介绍了Sentinel的核心原理和基本功能点,包括流量控制、熔断降级、热点参数限流、系统自适应限流等,并提供了一个完整的SpringBoot整合Sentinel的案例。

Sentinel作为一个面向分布式服务架构的流量控制组件,在微服务架构中扮演着重要的角色。它不仅提供了丰富的流量控制策略,还提供了实时的监控功能,可以帮助开发者更好地了解服务的运行情况,及时发现和解决问题。

在实际应用中,我们可以根据业务需求,灵活配置Sentinel的各种规则,以达到最佳的保护效果。同时,Sentinel还提供了多种规则持久化的方式,可以方便地将规则存储到配置中心,实现规则的动态更新。

参考资料

  1. Sentinel 官方文档
  2. Sentinel 工作原理
  3. Sentinel 流量控制
  4. Spring Cloud Alibaba Sentinel
相关推荐
xcya3 分钟前
Java ReentrantLock 核心用法
后端
用户4665370150516 分钟前
如何在 IntelliJ IDEA 中可视化压缩提交到生产分支
后端·github
小楓120121 分钟前
MySQL數據庫開發教學(一) 基本架構
数据库·后端·mysql
天天摸鱼的java工程师24 分钟前
Java 解析 JSON 文件:八年老开发的实战总结(从业务到代码)
java·后端·面试
白仑色25 分钟前
Spring Boot 全局异常处理
java·spring boot·后端·全局异常处理·统一返回格式
之诺31 分钟前
MySQL通信过程字符集转换
后端·mysql
喵手31 分钟前
反射机制:你真的了解它的“能力”吗?
java·后端·java ee
用户4665370150532 分钟前
git代码压缩合并
后端·github
武大打工仔36 分钟前
从零开始手搓一个MVC框架
后端
Monly2139 分钟前
RabbitMQ:SpringAMQP 入门案例
spring boot·rabbitmq·java-rabbitmq