Sentinel:微服务的流量守护神器

191 阅读30分钟

一、引言

在当今微服务架构盛行的时代,系统的稳定性成为了至关重要的考量因素。随着服务数量的不断增加以及业务复杂度的持续攀升,如何确保系统在高并发、高负载等各种复杂情况下依然能够稳定运行,成为了开发者们亟待解决的难题。而 Sentinel,作为一款由阿里巴巴开源的强大的流量控制、熔断降级和系统保护工具,为我们提供了一套全面且高效的解决方案。它就像是微服务架构中的 “守护者”,时刻监控着流量的变化,在关键时刻发挥作用,防止系统因流量过载或服务异常而崩溃,从而保障整个微服务架构的稳定性和可靠性。本文将带领大家深入认识 Sentinel,详细介绍其使用方式和配置方式,帮助大家更好地利用 Sentinel 来守护自己的微服务系统。

二、认识 Sentinel

2.1 什么是 Sentinel

Sentinel 是阿里巴巴开源的一款面向分布式服务架构的轻量级高可用流量控制组件 ,主要以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度来帮助开发者保障微服务的稳定性。在分布式系统中,服务之间的调用关系错综复杂,流量的波动也难以预测。当面对突发的流量高峰,如电商大促、热门活动等场景时,如果没有有效的流量控制措施,系统很容易因过载而崩溃。Sentinel 就像是系统的 “门卫”,能够实时监控流量情况,根据预设的规则对流量进行精确控制,确保系统在高负载情况下仍能稳定运行。它可以限制接口的每秒请求数(QPS),防止因瞬间大量请求导致系统资源耗尽;当某个服务出现故障或响应时间过长时,Sentinel 还能通过熔断降级机制,快速切断对该服务的调用,避免故障扩散,保障整个系统的可用性。

2.2 Sentinel 的核心特性

2.2.1 丰富的应用场景

Sentinel 在阿里巴巴内部经过多年的实践和打磨,积累了丰富的应用场景经验,尤其在应对高并发、复杂业务场景方面表现出色。以每年的双十一购物狂欢节为例,这是一场全球瞩目的电商盛宴,期间会产生海量的订单、支付、查询等请求,对系统的稳定性和性能是巨大的考验。Sentinel 在其中承担着关键的流量控制和服务保护职责。在秒杀场景中,通过精准的流量控制,Sentinel 可以将瞬间的高并发流量限制在系统能够承受的范围内,避免因大量用户同时抢购同一商品而导致系统崩溃,确保每个用户都能得到公平的抢购机会。同时,在消息削峰填谷场景中,Sentinel 能够根据系统的负载情况,对消息队列中的消息进行合理的消费和处理,防止消息堆积过多导致系统性能下降,保障消息处理的稳定性和及时性。这些应用场景充分展示了 Sentinel 强大的适应性和可靠性,使其成为保障大型分布式系统稳定运行的不可或缺的工具。

2.2.2 完备的实时监控

Sentinel 提供了功能强大且完备的实时监控系统,能够实时洞察系统的运行状态。其控制台是实时监控的核心展示平台,通过与 Sentinel 客户端的紧密协作,能够收集和展示详细的运行数据。在单台机器层面,控制台可以精确展示每秒的请求数(QPS)、并发线程数、响应时间等关键指标,这些数据以秒级粒度更新,让开发者能够及时捕捉到系统的细微变化。对于集群环境,Sentinel 的监控能力同样出色,它能够将分散在各个节点的运行数据进行汇总和分析,展示整个集群的运行情况,包括集群的整体 QPS、平均响应时间、各个节点的负载均衡情况等。这种实时监控能力不仅有助于开发者及时发现系统中的潜在问题,如某个接口的 QPS 突然飙升、响应时间变长等,还能为后续的流量控制和服务优化提供有力的数据支持,使开发者能够基于实际运行数据制定更加合理的策略,保障系统的稳定运行。

2.2.3 广泛的开源生态

Sentinel 拥有广泛而活跃的开源生态,与众多主流的开源框架和库实现了无缝集成,极大地降低了在不同项目中引入和使用 Sentinel 的门槛。在微服务领域,Spring Cloud 和 Dubbo 是非常流行的框架,Sentinel 为它们提供了专门的整合模块。以 Spring Cloud 为例,开发者只需在项目的 pom.xml 文件中添加如下依赖:

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

然后在配置文件中进行简单的配置,即可快速将 Sentinel 接入 Spring Cloud 项目中,实现对微服务的流量控制和保护。对于 Dubbo 项目,同样可以通过引入相应的依赖和简单配置,轻松享受 Sentinel 带来的强大功能。这种广泛的开源生态兼容性,使得 Sentinel 能够适应不同的技术架构和项目需求,无论是新开发的项目还是对现有项目进行改造,都能方便地引入 Sentinel 来提升系统的稳定性和可靠性。

2.2.4 完善的 SPI 扩展点

SPI(Service Provider Interface)是一种服务发现机制,Sentinel 利用 SPI 机制提供了丰富且完善的扩展点,为开发者提供了高度的定制化能力。通过实现这些扩展接口,开发者可以根据项目的具体需求,定制各种个性化的逻辑。例如,在规则管理方面,默认情况下 Sentinel 使用内存存储规则,但在一些生产环境中,可能需要将规则持久化到数据库或其他分布式存储系统中。开发者可以通过实现 SPI 扩展接口,定制自己的规则管理逻辑,实现与数据库的交互,将规则存储到数据库中,并在系统启动时从数据库加载规则。在适配动态数据源方面,当项目的数据源发生变化时,如从 MySQL 切换到 PostgreSQL,或者需要根据不同的业务场景使用不同的数据源,开发者可以通过 SPI 扩展接口,实现对动态数据源的适配,确保 Sentinel 能够准确地获取数据并进行相应的流量控制和服务保护。这种完善的 SPI 扩展机制,使得 Sentinel 能够满足各种复杂业务场景的需求,具有极高的灵活性和可扩展性。

三、Sentinel 的使用方式

3.1 引入 Sentinel 依赖

在使用 Sentinel 之前,首先需要在项目中引入相关依赖。如果你使用的是 Maven 项目,只需在 pom.xml 文件中添加以下依赖:

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

这些依赖引入了 Sentinel 的核心功能以及与 Spring Cloud 的集成支持,使我们能够在 Spring Cloud 项目中方便地使用 Sentinel 进行流量控制和服务保护 。引入依赖后,Maven 或 Gradle 会自动下载并管理这些依赖项及其传递依赖,确保项目具备使用 Sentinel 的基础。

3.2 定义资源

在 Sentinel 中,资源是需要保护的目标,例如一个方法、一个接口或者一段代码块。定义资源是使用 Sentinel 的关键步骤,它为后续的流量控制、熔断降级等操作提供了作用对象。

3.2.1 抛出异常的方式定义资源

通过 SphU 类的 entry 方法可以以抛出异常的方式定义资源,这是一种较为基础的资源定义方式。示例代码如下:

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.slots.block.BlockException;
public class ResourceDefinitionExample {
    public static void main(String[] args) {
        String resourceName = "exampleResource";
        while (true) {
            try (Entry entry = SphU.entry(resourceName)) {
                // 被保护的业务逻辑
                System.out.println("Executing business logic");
            } catch (BlockException ex) {
                // 资源访问阻止,被限流或被降级
                System.out.println("Resource access blocked, due to rate limiting or degradation");
            }
        }
    }
}

在上述代码中,SphU.entry(resourceName) 用于定义资源,resourceName 是资源的唯一标识,它可以是任何具有业务语义的字符串,比如方法名、接口名等,在这个例子中我们定义了一个名为 exampleResource 的资源。当请求进入该资源时,Sentinel 会检查是否有相关的规则限制,如果请求违反了限流、熔断等规则,就会抛出 BlockException 异常,我们可以在 catch 块中捕获该异常,并进行相应的处理,例如返回友好的错误提示给用户、记录日志等。这种方式通过异常处理机制来区分正常业务逻辑和因规则限制导致的异常情况,使代码逻辑更加清晰,便于开发者理解和维护。

3.2.2 返回布尔值方式定义资源

SphO 类提供了以返回布尔值的方式来定义资源,这种方式更加灵活,适合在一些需要根据资源访问结果进行不同业务逻辑处理的场景。代码示例如下:

import com.alibaba.csp.sentinel.SphO;
public class BooleanResourceDefinition {
    public static void main(String[] args) {
        String resourceName = "booleanResource";
        while (true) {
            if (SphO.entry(resourceName)) {
                try {
                    // 被保护的业务逻辑
                    System.out.println("Business logic executed successfully");
                } finally {
                    SphO.exit();
                }
            } else {
                // 资源访问阻止,被限流或被降级
                System.out.println("Resource access blocked, handle gracefully");
            }
        }
    }
}

在这段代码中,SphO.entry(resourceName) 尝试进入资源,如果返回 true,表示资源访问成功,允许执行被保护的业务逻辑;如果返回 false,则说明资源访问被阻止,可能是因为达到了限流阈值或者触发了熔断降级规则,此时可以在 else 分支中进行相应的处理,如返回默认值、进行缓存读取等操作,以保证系统的正常运行和用户体验。这种方式通过返回值直接判断资源的访问状态,避免了异常处理的开销,在一些对性能要求较高且业务逻辑相对简单的场景中具有优势。

3.2.3 注解方式定义资源

Sentinel 提供了 @SentinelResource 注解,使用该注解可以更加便捷地定义资源,并且可以同时配置限流、降级等处理逻辑,极大地简化了代码。以下是一个使用 @SentinelResource 注解的示例:

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
public class AnnotationResourceDefinition {
    @SentinelResource(value = "annotationResource", blockHandler = "handleBlockException")
    public static void businessMethod() {
        System.out.println("Executing business method with annotation");
    }
    public static void handleBlockException(BlockException ex) {
        System.out.println("Blocked, handle exception: " + ex.getMessage());
    }
}

在上述代码中,@SentinelResource 注解标记在 businessMethod 方法上,value 属性指定了资源名称为annotationResource。blockHandler 属性指定了当资源被限流或降级时调用的处理方法handleBlockException,该方法的参数必须包含 BlockException 类型,用于接收异常信息,以便在处理方法中进行相应的逻辑处理,如记录日志、返回错误信息等。通过这种注解方式,将资源定义和异常处理逻辑紧密结合在一起,使代码结构更加清晰,提高了代码的可读性和可维护性 ,同时也减少了重复的代码编写。

3.2.4 异步调用支持

在异步调用场景中,Sentinel 同样提供了完善的支持,确保在异步操作中也能对资源进行有效的控制。示例代码如下:

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import java.util.concurrent.CompletableFuture;
public class AsyncResourceDefinition {
    public static void main(String[] args) {
        String resourceName = "asyncResource";
        try {
            Entry entry = SphU.asyncEntry(resourceName);
            CompletableFuture.supplyAsync(() -> {
                // 异步业务逻辑
                System.out.println("Executing async business logic");
                return "Result";
            }).thenAccept(result -> {
                try {
                    System.out.println("Async result: " + result);
                } finally {
                    entry.exit();
                }
            });
        } catch (BlockException ex) {
            // 处理被流控的逻辑
            System.out.println("Blocked in async operation: " + ex.getMessage());
        }
    }
}

在这个例子中,通过 SphU.asyncEntry(resourceName) 定义异步资源,在异步操作完成后的回调函数thenAccept中,使用 entry.exit() 方法退出资源,确保资源的统计和规则检查能够正确进行。如果在进入资源时被限流或降级,会抛出 BlockException 异常,在 catch 块中可以进行相应的处理,如重试异步操作、返回默认值等。这种对异步调用的支持,使得 Sentinel 能够适应各种复杂的业务场景,无论是同步还是异步操作,都能有效地保障系统的稳定性和可靠性。

3.3 定义规则

定义好资源后,就需要为这些资源配置相应的规则,以实现流量控制、熔断降级等功能。Sentinel 提供了多种类型的规则,每种规则都针对不同的场景和需求,下面将详细介绍各种规则的使用。

3.3.1 流量控制规则 (FlowRule)

流量控制是 Sentinel 最基本也是最重要的功能之一,它通过监控应用流量的 QPS(每秒请求数)或并发线程数等指标,当达到指定阈值时对流量进行控制,避免系统被瞬时的流量高峰冲垮,保障应用的高可用性。例如,在电商促销活动中,大量用户同时访问商品详情页,如果没有流量控制,系统可能会因为瞬间的高并发请求而崩溃。通过设置流量控制规则,我们可以将请求流量限制在系统能够承受的范围内,确保系统稳定运行。

在 Sentinel 中,设置流量控制规则的代码示例如下:

import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import java.util.ArrayList;
import java.util.List;
public class FlowRuleExample {
    public static void main(String[] args) {
        List<FlowRule> rules = new ArrayList<>();
        FlowRule rule = new FlowRule();
        // 资源名,即需要控制流量的资源
        rule.setResource("productDetail");
        // 限流阈值类型,1表示QPS,0表示并发线程数
        rule.setGrade(1);
        // 限流阈值,这里设置为每秒最多允许100个请求
        rule.setCount(100);
        // 流控效果,0表示快速失败,1表示Warm Up,2表示排队等待
        rule.setControlBehavior(0);
        rules.add(rule);
        FlowRuleManager.loadRules(rules);
    }
}

在上述代码中,首先创建了一个 FlowRule 对象,并设置了相关属性。setResource 方法指定了需要控制流量的资源名称,这里是 productDetail;setGrade 方法设置限流阈值类型,1表示基于 QPS 进行限流,0则表示基于并发线程数限流;setCount 方法设置具体的限流阈值,即每秒最多允许 100 个请求通过;setControlBehavior 方法设置流控效果,0表示快速失败,当请求超过阈值时,直接抛出 BlockException 异常,拒绝请求。最后,通过FlowRuleManager.loadRules(rules) 方法将配置好的规则加载到 Sentinel 中生效。通过灵活调整这些参数,可以根据不同的业务场景和系统性能,制定出合理的流量控制策略。

3.3.2 熔断降级规则 (DegradeRule)

熔断降级是当调用链路中的某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误,就像电路中的保险丝一样,在出现过载或短路等异常情况时,及时切断电路,保护整个系统。例如,在一个微服务架构中,某个服务依赖的第三方接口出现故障,响应时间过长或者频繁返回错误,如果不进行熔断降级处理,大量的请求会在这个服务上堆积,导致资源耗尽,进而影响到整个系统的正常运行。

在 Sentinel 中配置熔断规则的代码如下:

import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import java.util.ArrayList;
import java.util.List;
public class DegradeRuleExample {
    public static void main(String[] args) {
        List<DegradeRule> rules = new ArrayList<>();
        DegradeRule rule = new DegradeRule();
        // 资源名
        rule.setResource("thirdPartyService");
        // 熔断策略,0表示慢调用比例,1表示异常比例,2表示异常数
        rule.setGrade(1);
        // 慢调用比例阈值,当慢调用比例超过该值时触发熔断
        rule.setSlowRatioThreshold(0.5);
        // 最小请求数,当请求数小于该值时,即使异常比例超过阈值也不熔断
        rule.setMinRequestAmount(10);
        // 熔断时长,单位为秒,熔断后在该时长内请求会被直接拒绝
        rule.setTimeWindow(10);
        rules.add(rule);
        DegradeRuleManager.loadRules(rules);
    }
}

在这段代码中,创建了一个 DegradeRule 对象并配置了相关属性。setResource 指定了需要熔断降级的资源为thirdPartyService;setGrade 设置熔断策略,1表示基于异常比例进行熔断;setSlowRatioThreshold 设置慢调用比例阈值为 0.5,即当慢调用比例超过 50% 时触发熔断;setMinRequestAmount 设置最小请求数为 10,只有在请求数达到 10 次以上时,才会根据异常比例判断是否熔断;setTimeWindow 设置熔断时长为 10 秒,在这 10 秒内,对该资源的请求会被直接拒绝,直到熔断时间结束后,才会逐渐恢复对该资源的调用 。通过合理配置这些参数,可以有效地避免因个别服务的不稳定而导致整个系统的崩溃,提高系统的容错能力。

3.3.3 系统保护规则 (SystemRule)

系统保护规则主要用于监控系统的整体负载情况,如 CPU 使用率、系统负载(Load)、平均 RT(响应时间)、入口 QPS 和并发线程数等指标,当系统的这些指标达到一定阈值时,自动调整流量,防止系统因过载而崩溃,从而保障整个系统的稳定性。例如,当系统的 CPU 使用率持续过高,可能会导致系统响应变慢甚至死机,此时系统保护规则可以通过限制流量,减轻系统负担,确保系统能够正常运行。

在 Sentinel 中配置系统保护规则的示例如下:

import com.alibaba.csp.sentinel.slots.system.SystemRule;
import com.alibaba.csp.sentinel.slots.system.SystemRuleManager;
import java.util.ArrayList;
import java.util.List;
public class SystemRuleExample {
    public static void main(String[] args) {
        List<SystemRule> rules = new ArrayList<>();
        SystemRule rule = new SystemRule();
        // 最大CPU使用率,这里设置为0.8,即80%
        rule.setHighestSystemLoad(0.8);
        // 入口QPS阈值,当QPS超过该值时触发系统保护
        rule.setAvgRt(100);
        // 并发线程数阈值,当并发线程数超过该值时触发系统保护
        rule.setMaxThread(100);
        rules.add(rule);
        SystemRuleManager.loadRules(rules);
    }
}

在上述代码中,创建了一个 SystemRule 对象并进行配置。setHighestSystemLoad 设置最大 CPU 使用率为 0.8,当系统的 CPU 使用率超过 80% 时,可能会触发系统保护机制;setAvgRt 设置平均 RT(响应时间)阈值为 100 毫秒,当系统的平均响应时间超过 100 毫秒时,也可能触发系统保护;setMaxThread 设置并发线程数阈值为 100,当系统的并发线程数超过 100 时,同样会触发系统保护。通过这些参数的设置,可以实时监控系统的运行状态,根据系统的实际负载情况动态调整流量,确保系统在高负载情况下仍能稳定运行 。

3.3.4 访问控制规则 (AuthorityRule)

访问控制规则通过黑白名单机制,根据调用来源(如 IP 地址、应用名称等)对资源的访问进行控制,只有符合规则的调用来源才能访问资源,从而实现对资源的精细化访问管理。例如,在一个多租户的应用系统中,可能需要限制某些租户对特定资源的访问,或者只允许特定的 IP 地址段访问某些敏感接口,这时就可以使用访问控制规则来实现。

在 Sentinel 中配置访问控制规则的示例代码如下:

import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRuleManager;
import java.util.ArrayList;
import java.util.List;
public class AuthorityRuleExample {
    public static void main(String[] args) {
        List<AuthorityRule> rules = new ArrayList<>();
        AuthorityRule rule = new AuthorityRule();
        // 资源名
        rule.setResource("sensitiveResource");
        // 控制模式,1表示白名单,2表示黑名单
        rule.setStrategy(1);
        // 白名单或黑名单中的调用来源,这里假设是IP地址
        rule.setLimitApp("192.168.1.1,192.168.1.2");
        rules.add(rule);
        AuthorityRuleManager.loadRules(rules);
    }
}

在这段代码中,创建了一个 AuthorityRule 对象并进行配置。setResource 指定了需要进行访问控制的资源为sensitiveResource;setStrategy 设置控制模式,1表示白名单模式,只有在 setLimitApp 中指定的 IP 地址(这里是 192.168.1.1 和 192.168.1.2 )才能访问该资源,如果设置为2,则表示黑名单模式,不在名单中的 IP 地址可以访问,而名单中的 IP 地址被禁止访问。通过这种方式,可以根据业务需求灵活地设置访问控制策略,增强系统的安全性和可控性 。

3.3.5 热点参数限流

热点参数限流是对包含热点参数的请求进行限流,所谓热点参数,就是那些经常被访问且可能会引起流量突增的参数。例如,在一个商品查询接口中,某个热门商品的 ID 可能会被频繁查询,如果不对这个商品 ID 进行限流,可能会导致系统负载过高。通过热点参数限流,可以针对这些热点参数设置单独的限流规则,保证系统的稳定运行。

在 Sentinel 中配置热点参数限流的示例代码如下:

import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowItem;
import com.alibaba.csp.sentinel.slots.block.flow.param.Param
## 三、Sentinel的配置方式
![need_search_image_by_title]()
### 3.1 JVM -D参数方式
通过JVM的`-D`参数可以方便地对Sentinel进行配置,这种方式的优先级最高,在一些需要快速调整配置且配置项较少的场景下非常适用。例如,我们可以通过以下参数设置应用名和日志路径:
```bash
java -Dproject.name=myProject -Dcsp.sentinel.log.dir=/data/sentinel/logs -jar myApp.jar

在上述命令中,-Dproject.name=myProject 设置了应用名为 myProject,这在 Sentinel 控制台中用于标识应用,方便对不同应用进行统一管理和监控。-Dcsp.sentinel.log.dir=/data/sentinel/logs 指定了 Sentinel 的日志输出目录为 /data/sentinel/logs,日志中记录了 Sentinel 的运行状态、规则执行情况等重要信息,有助于我们在出现问题时进行排查和分析。这种方式适用于需要在启动应用时就确定配置,并且配置不会频繁变动的场景,比如生产环境中应用名和日志路径一般是固定的 。

3.4 properties 文件方式

使用 properties 文件配置 Sentinel 也是一种常见的方式,它提供了一种相对集中和易于管理的配置方式。 properties 文件的格式如下:

# 应用名
project.name=myProject
# 控制台地址
csp.sentinel.dashboard.server=localhost:8080
# 本地启动HTTP API Server的端口号
csp.sentinel.api.port=8719
# 日志目录
csp.sentinel.log.dir=./logs/sentinel
# 日志文件名中是否加入进程号,区分同一机器上的多个sentinel应用
csp.sentinel.log.use.pid=true
# 日志输出类型,可选值:file(输出到文件,默认)、console(输出到控制台)
csp.sentinel.log.output.type=file

在这个配置文件中,project.name 指定了应用名称,csp.sentinel.dashboard.server 配置了 Sentinel 控制台的地址,客户端会自动向该地址发送心跳包,以便在控制台上进行监控和管理。csp.sentinel.api.port 设置了本地启动 HTTP API Server 的端口号,用于与控制台进行通信。csp.sentinel.log.dir 指定了日志目录,csp.sentinel.log.use.pid 决定是否在日志文件名中加入进程号,当在同一机器上部署多个 Sentinel 应用时,设置为 true 可以有效区分不同应用的日志。csp.sentinel.log.output.type 设置日志输出类型,默认为输出到文件。

要加载该配置文件,在启动应用时,可以通过 -Dcsp.sentinel.config.file 参数指定配置文件的路径,支持 classpath 路径配置,例如:

java -Dcsp.sentinel.config.file=classpath:sentinel.properties -jar myApp.jar

如果不指定路径,默认情况下,Sentinel 会尝试从 classpath:sentinel.properties 文件读取配置,读取编码默认为 UTF-8 。这种方式适用于配置项较多且可能需要在不同环境(开发、测试、生产)中进行切换的场景,通过修改 properties 文件就可以方便地调整配置,而无需修改代码和重新打包应用。

3.5 数据源配置(重点)

数据源配置是 Sentinel 配置中非常关键的部分,它决定了规则的存储和获取方式,直接影响到 Sentinel 的动态规则管理能力。Sentinel 支持多种数据源扩展,按照工作模式主要可以分为拉模式数据源和推模式数据源。

3.5.1 拉模式数据源

拉模式数据源是指客户端主动向某个规则管理中心定期轮询拉取规则,这个规则中心可以是关系型数据库(RDBMS)、文件,甚至是版本控制系统(VCS)等。以文件数据源为例,其工作原理是客户端通过 FileRefreshableDataSource 定时从指定文件中读取规则 JSON 文件,如果发现文件发生变化,就更新规则缓存。

以下是一个基于文件数据源的代码示例:

import com.alibaba.csp.sentinel.init.InitFunc;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.csp.sentinel.datasource.FileRefreshableDataSource;
import com.alibaba.csp.sentinel.datasource.WritableDataSource;
import com.alibaba.csp.sentinel.datasource.WritableDataSourceRegistry;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import java.io.File;
import java.io.IOException;
import java.util.List;
public class FileDataSourceInit implements InitFunc {
    @Override
    public void init() throws Exception {
        // 规则存储目录,可根据实际情况修改
        String ruleDir = System.getProperty("user.home") + "/sentinel/rules";
        String flowRulePath = ruleDir + "/flow-rule.json";
        // 创建目录和文件(如果不存在)
        mkdirIfNotExits(ruleDir);
        createFileIfNotExits(flowRulePath);
        // 流控规则读取数据源
        ReadableDataSource<String, List<FlowRule>> flowRuleRDS = new FileRefreshableDataSource<>(
                flowRulePath,
                source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {})
        );
        // 将可读数据源注册至FlowRuleManager
        FlowRuleManager.register2Property(flowRuleRDS.getProperty());
        // 流控规则写入数据源
        WritableDataSource<List<FlowRule>> flowRuleWDS = new WritableDataSource<>(
                flowRulePath,
                this::encodeJson
        );
        // 将可写数据源注册至transport模块的WritableDataSourceRegistry中
        WritableDataSourceRegistry.registerFlowDataSource(flowRuleWDS);
    }
    private void mkdirIfNotExits(String filePath) throws IOException {
        File file = new File(filePath);
        if (!file.exists()) {
            file.mkdirs();
        }
    }
    private void createFileIfNotExits(String filePath) throws IOException {
        File file = new File(filePath);
        if (!file.exists()) {
            file.createNewFile();
        }
    }
    private <T> String encodeJson(T t) {
        return JSON.toJSONString(t);
    }
}

在上述代码中,首先定义了规则存储的目录和文件路径,然后创建了 FileRefreshableDataSource 作为可读数据源,用于从文件中读取流控规则,并将其注册到 FlowRuleManager 中,这样当规则文件发生变化时,会自动更新内存中的规则。同时,创建了 WritableDataSource 作为可写数据源,用于将控制台推送的规则写入文件中 。

拉模式数据源的优点是实现简单,不需要额外的中间件支持,不引入新的依赖,适用于一些简单的场景或者对规则更新实时性要求不高的场景。然而,它也存在明显的缺点,由于规则是定时轮询拉取的,所以规则更新会有延迟,如果轮询时间设置过大,可能导致长时间延迟;如果轮询时间过小,又会频繁读取文件,影响性能。此外,规则存储在本地文件,如果需要迁移微服务,那么需要把规则文件一起迁移,否则规则会丢失 。

3.5.2 推模式数据源

推模式数据源是指规则中心统一推送规则,客户端通过注册监听器的方式时刻监听变化,比如使用 Nacos、Zookeeper 等配置中心。以 Nacos 为例,其配置步骤如下:

  1. 添加依赖:在项目的pom.xml文件中添加 Sentinel 与 Nacos 集成的依赖:
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
  1. 配置 Nacos 地址和规则信息:在 application.yml 文件中进行如下配置:
spring:
  cloud:
    sentinel:
      datasource:
        flow:
          nacos:
            server-addr: localhost:8848 # Nacos地址
            dataId: ${spring.application.name}-flow-rules
            groupId: SENTINEL_GROUP
            rule-type: flow
        degrade:
          nacos:
            server-addr: localhost:8848
            dataId: ${spring.application.name}-degrade-rules
            groupId: SENTINEL_GROUP
            rule-type: degrade

在上述配置中,server-addr 指定了 Nacos 服务器的地址和端口;dataId 用于标识规则数据,通常结合应用名来命名,便于区分不同应用的规则;groupId指定了分组,可根据业务需求进行设置;rule-type 指定了规则类型,如 flow 表示流控规则,degrade 表示熔断降级规则 。

  1. 实现规则的实时推送和监听:Sentinel 客户端会连接到 Nacos,获取规则配置,并监听 Nacos 配置变化。当 Nacos 中的规则发生变化时,客户端会接收到通知,然后更新本地缓存,从而使本地缓存总是和 Nacos 一致。同时,Sentinel 控制台也会监听 Nacos 配置变化,如发生变化就更新本地缓存,确保控制台显示的规则与 Nacos 中的规则一致 。

推模式数据源的优势在于规则更新的实时性和一致性好,能够及时将规则的变更推送到客户端,适用于对规则实时性要求较高的生产环境。但它也存在一些缺点,比如需要引入额外的配置中心(如 Nacos),增加了系统的复杂性和维护成本;同时,对配置中心的稳定性和可靠性要求较高,如果配置中心出现故障,可能会影响规则的推送和获取 。

四、案例实战

4.1 模拟业务场景

假设我们正在开发一个电商系统,该系统包含多个核心业务模块,如商品展示、订单处理、支付结算等。其中,商品详情页和订单提交是两个关键的业务场景,它们的流量特点和可能面临的问题如下:

  • 商品详情页访问:商品详情页是用户了解商品信息的重要入口,尤其是在促销活动期间,如 “双 11”“618” 等,会有大量用户同时涌入系统,快速访问商品详情页,导致该接口的流量瞬间激增。如果没有有效的流量控制措施,系统很容易因为无法承受高并发请求而出现响应缓慢甚至崩溃的情况,影响用户体验,导致用户流失。
  • 订单提交:订单提交是电商交易的核心环节,当用户确认购买商品后,会向订单提交接口发送请求。在促销活动时,订单提交的并发量会急剧上升,而且该操作涉及到库存扣减、订单数据存储等多个复杂的业务逻辑和数据库操作,对系统的性能和稳定性要求极高。如果在高并发情况下不能合理控制流量,可能会导致库存超卖、订单数据不一致等严重问题,影响交易的正常进行。

4.2 使用 Sentinel 进行流量控制和熔断降级

为了解决上述业务场景中可能出现的问题,我们将使用 Sentinel 对商品详情页访问和订单提交接口进行流量控制和熔断降级。

  1. 引入依赖:在项目的 pom.xml 文件中添加 Sentinel 相关依赖:
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
  1. 配置 Sentinel 控制台:在 application.yml 文件中配置 Sentinel 控制台地址和端口:
spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8080
        port: 8719
  1. 定义资源和规则
  • 商品详情页访问:在商品详情页接口的 Controller 方法上添加 @SentinelResource 注解定义资源,并在 Sentinel 控制台配置流量控制规则。假设我们设置商品详情页接口 /product/detail/{productId}的 QPS 阈值为 100,即每秒最多允许 100 个请求访问该接口,当 QPS 超过 100 时,直接快速失败,返回错误信息。
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ProductController {
    @GetMapping("/product/detail/{productId}")
    @SentinelResource("productDetail")
    public String getProductDetail(@PathVariable Long productId) {
        // 模拟查询商品详情的业务逻辑
        return "Product detail for id: " + productId;
    }
}

在 Sentinel 控制台中,进入流量控制页面,为 productDetail 资源添加规则,模式选择 “直接”,阈值类型选择 “QPS”,单机阈值设置为 100。

  • 订单提交:同样,在订单提交接口的 Controller 方法上添加 @SentinelResource 注解定义资源,并配置流量控制和熔断降级规则。假设订单提交接口为 /order/submit,我们设置其 QPS 阈值为 50,当 QPS 超过 50 时,采用排队等待的流控效果,排队超时时间为 2 秒,即请求会在队列中等待 2 秒,如果 2 秒内无法处理则返回错误。同时,配置熔断降级规则,当接口的异常比例超过 20%,并且请求数达到 10 次以上时,触发熔断,熔断时长为 5 秒。
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OrderController {
    @PostMapping("/order/submit")
    @SentinelResource("orderSubmit")
    public String submitOrder() {
        // 模拟订单提交的业务逻辑,可能会抛出异常
        if (Math.random() > 0.8) {
            throw new RuntimeException("Order submission failed");
        }
        return "Order submitted successfully";
    }
}

在 Sentinel 控制台中,为 orderSubmit 资源添加流量控制规则,模式选择 “直接”,阈值类型选择 “QPS”,单机阈值设置为 50,流控效果选择 “排队等待”,超时时间设置为 2000 毫秒。在熔断降级页面,添加规则,熔断策略选择 “异常比例”,阈值设置为 0.2,最小请求数设置为 10,熔断时长设置为 5 秒。

  1. 运行项目并测试:启动项目后,使用 JMeter 等工具进行压力测试。
  • 商品详情页测试:当并发请求数小于等于 100 时,接口能够正常返回商品详情信息;当并发请求数超过 100 时,超过阈值的请求会被 Sentinel 拦截,返回流控异常信息,例如:
{
    "code": -1,
    "message": "Flow limit exceeded"
}
  • 订单提交测试:当 QPS 小于等于 50 时,请求能够正常提交订单;当 QPS 超过 50 时,部分请求会进入排队等待状态,如果排队时间超过 2 秒,会返回错误信息:
{
    "code": -1,
    "message": "Queue timeout"
}

当订单提交接口的异常比例超过 20% 且请求数达到 10 次以上时,会触发熔断,在熔断的 5 秒内,所有请求都会直接返回熔断降级提示信息,例如:

{
    "code": -1,
    "message": "Service degraded"
}

通过以上配置和测试,我们可以看到 Sentinel 有效地对电商系统的关键业务接口进行了流量控制和熔断降级,保障了系统在高并发场景下的稳定性和可靠性,避免了因流量过载和服务异常导致的系统故障,提升了用户体验和业务的正常运行。

五、总结

在本文中,我们深入探索了 Sentinel 这一强大的流量控制、熔断降级和系统保护工具。首先,我们认识到 Sentinel 作为分布式服务架构中的关键组件,以流量为核心,从多个维度保障微服务的稳定性。其核心特性包括丰富的应用场景,如在电商大促等场景中的出色表现;完备的实时监控能力,通过控制台实时洞察系统运行状态;广泛的开源生态,与主流框架无缝集成,降低了接入门槛;完善的 SPI 扩展点,为开发者提供了高度定制化的能力。

在使用方式上,我们通过引入相关依赖,成功将 Sentinel 接入项目。通过多种方式定义资源,如抛出异常、返回布尔值、注解以及异步调用支持等方式,为流量控制和熔断降级提供了作用对象。同时,详细介绍了流量控制规则、熔断降级规则、系统保护规则、访问控制规则以及热点参数限流等规则的定义和配置,这些规则是 Sentinel 实现其强大功能的核心,能够根据不同的业务场景和系统需求,灵活地对流量进行控制和管理。

在配置方式上,我们探讨了 JVM -D 参数方式、properties 文件方式以及数据源配置等多种方式。其中,数据源配置是重点,拉模式数据源通过客户端主动轮询获取规则,实现简单但规则更新有延迟;推模式数据源借助配置中心统一推送规则,实时性和一致性好,但增加了系统复杂度 。