Sentinel概述

前言

​ 在现行高并发场景下,通常会采用微服务架构,可以更好的对服务接口进行水平扩展、接口限流等有效保障服务的可用性和稳定性。其中为了保障服务的高可用,同时也为了防止故障蔓延,通常会选择断路器,防止故障的服务还一直占用CPU资源,可能导致其他服务不能及时处理数据请求。

​ 由于服务器所能承受的压力是有限的,为了保证可用性,一般会做服务限流,减少服务压力。

​ 那么常见的断路器有Sentinel、Hystrix,而常见的微服务限流组件有RateLimiter、Hystrix、Sentinel等。其中Hystrix也在2022年官方发布说明停止维护,且Sentinel是阿里研发,专门为流量控制而生的限流框架。

​ 故在微服务中,服务限流熔断建议使用Sentinel。

介绍

Sentinel 是一个高可用、高扩展、高稳定性的开源流量控制和熔断降级框架,可以在分布式系统中实现实时的流量控制,防止系统因流量过大导致系统崩溃和服务降级。

Sentinel 提供了以下功能:

  • 流量控制:通过配置不同的规则,对请求流量进行限制。
  • **熔断降级:**当系统异常情况发生时,可以自动熔断系统,保证系统的可用性。
  • **系统负载保护:**在系统负载高峰期间,可以限制请求流量,避免系统资源耗尽。
  • 实时监控:可以实时监控系统的请求流量、响应时间、错误率等指标。

​ Sentinel 面向所有的 Java 应用,可以支持基于 Spring Cloud、Dubbo、gRPC 等服务框架的应用,也可以集成到基于 Tomcat、Jetty 等 Web 容器的应用中。

什么是流量控制?

​ 流量控制在网络传输中是一个常用的概念,它用于调整网络包的发送数据。

​ 任意时间到来的请求往往是随机不可控的,而系统的处理能力是有限的,由此需要根据系统的处理能力对流量进行控制。Sentinel 作为一个调配器,可以根据需要把随机的请求调整成合适的形状。

流量控制设计理念

**1)资源的调用关系。**例如资源的调用链路,资源和资源之间的关系;

**2)运行指标。**例如 QPS、线程池、系统负载等;

**3)控制的效果。**例如直接限流、冷启动、排队等

什么是熔断降级?

​ 除了流量控制以外,降低调用链路中的不稳定资源也是 Sentinel 的使命之一。由于调用关系的复杂性,如果调用链路中的某个资源出现了不稳定,最终会导致请求发生堆积。

​ Sentinel 和 Hystrix 的原则是一致的: 当调用链路中某个资源出现不稳定,例如,表现为 timeout,异常比例升高的时候,则对这个资源的调用进行限制,并让请求快速失败,避免影响到其它的资源,最终产生雪崩的效果。

设计理念

​ **Hystrix 通过线程池的方式,来对依赖资源进行了隔离。**这样做的好处是资源和资源之间做到了最彻底的隔离。缺点是除了增加了线程切换的成本,还需要预先给各个资源做线程池大小的分配。

Sentinel的处理方式:

**1)通过并发线程数进行限制。**和资源池隔离的方法不同,Sentinel 通过限制资源并发线程的数量,来减少不稳定资源对其它资源的影响。

**2)通过响应时间对资源进行降级。**当依赖的资源出现响应时间过长后,所有对该资源的访问都会被直接拒绝,直到过了指定的时间窗口之后才重新恢复。

**3)系统负载保护。**Sentinel 提供了对应的保护机制,让系统的入口流量和系统的负载达到一个平衡,保证系统在能力范围之内处理最多的请求。

工作机制

​ **对于定义需要保护的资源,提供设施对资源进行实时统计和调用链路分析。并根据预设的规则,结合对资源的实时统计信息,对流量进行控制。同时,Sentinel 提供开放的接口,方便定义及改变规则。**另外Sentinel 提供实时的监控系统,可直观了解目前系统的状态。

使用说明

1)在Maven中引入Sentinel的依赖:spring-cloud-starter-alibaba-sentinel

2)配置Sentinel的启动参数:

yml 复制代码
# Sentinel 控制台的地址
spring.cloud.sentinel.transport.dashboard=http://localhost:8080 
# Sentinel 的启动端口
spring.cloud.sentinel.transport.port=8719 
# Sentinel 控制台连接超时时间(ms) spring.cloud.sentinel.transport.dashboard.request-timeout=5000 
# 配置资源的默认规则 
spring.cloud.sentinel.rules.defaults[0].grade=QPS spring.cloud.sentinel.rules.defaults[0].count=10

3)若是需要流量控制,添加流量控制注解。

java 复制代码
@SentinelResource(value = "demoMethod", blockHandler = "handleBlock")
public String demoMethod() {
  return "Hello World";
}
// 自定义的响应结果 也可以写成通用类
public String handleBlock(BlockException ex) {
  return "请求被拦截: " + ex.getClass().getSimpleName();
}

4)若是需要熔断控制,在@SentinelResource注解中,需要指定fallback属性。

java 复制代码
@SentinelResource(value = "demoMethod", blockHandler = "handleBlock", fallbackClass = DemoServiceFallback.class, fallback = "fallback")
public String demoMethod() {
  return "Hello World";
}
public String handleBlock(BlockException ex) {
  return "请求被拦截: " + ex.getClass().getSimpleName();
}

5)Sentinel 支持多种多样的流控规则和热点参数限流策略,可以根据业务场景进行灵活配置。

5-1)QPS 流量控制。

@RateLimiter(10)

5-2)线程数流控。

@ThreadPool(name = "demoMethod", coreSize = 5, maxQueueSize = 10)

​ **5-3)热点参数限流。**可以有效避免因某个参数的恶意使用而导致整个系统崩溃的情况。

@HotParam(value = "skuId", mode = ParamFlowItem.FlowControlMode.QPS, threshold = 100)

优缺点

优点:

1)功能丰富,简单便于使用。

2)提供了多种SPI扩展点,可自定义扩展。

3)开源免费

缺点:

1)仅支持Java

2)文档略显简单,实践中存在一些坑点需要注意。。

​ 坑点,回头补充...

基本概念

1、资源

​ **资源是 Sentinel 的关键概念。只要通过 Sentinel API 定义的代码,就是资源,能够被 Sentinel 保护起来。**大部分情况下,可以使用方法签名,URL,甚至服务名称作为资源名来标示资源。

2、规则

​ 围绕资源的实时状态设定的规则,可以包括流量控制规则、熔断降级规则以及系统保护规则。所有规则可以动态实时调整。

底层实现原理

Sentinel 实现流量控制和熔断降级的原理是通过对应用程序进行拦截,然后根据预定义的规则,来判断该请求是否被允许或者需要进行降级处理。

Sentinel 的拦截器会在应用程序中建立一个责任链,对请求进行逐一拦截。在拦截过程中,Sentinel 会对 Request、Response、Exception 等参数进行统计,根据统计信息来对请求进行熔断或者限流等操作。

衡量系统稳定性主要有以下三个指标:

  • TPS(Transactions Per Second):每秒钟处理的事务数。
  • RT(Response Time):响应时间,即从发送请求到接收到响应的时间。
  • Error Rate:错误率,即发生错误的请求次数占总请求数的比例。

​ Sentinel 根据这三个指标来评估应用程序的健康状况,当这些指标达到某个阈值时,Sentinel 会自动触发相应的流量控制和熔断降级操作。

@SentinelResource

​ 通常使用@SentinelResource来标记一个方法,这个在Sentinel服务中被看成一个Sentinel资源。可以以此切面,查看拦截之后所做的处理。

​ 进入SentinelResource切面后,首先会先获取资源的名称、类型,执行SphU.entry方法,并把资源包装成Entry。其在SphU.entry中做限流和熔断的逻辑处理。

​ 在SphU.entry方法中的执行流程:

1)**获取Sentinel上下文(Context)。**就是Sentinel熔断限流执行的上下文,包含资源调用的节点和Entry信息。Context是线程持有的,利用ThreadLocal与当前线程绑定。而Context主要包含Conetxt名称,Node,Entry,这三个类是Sentinel的核心类,提供了资源调用路径、资源调用统计等信息。

​ Context是当前线程所持有的Sentinel上下文。进入Sentinel的逻辑时,会首先获取当前线程的Context,如果没有则新建。当任务执行完毕后,会清除当前线程的context。Context 代表调用链路上下文,贯穿一次调用链路中的所有 Entry。

Context 维持着入口节点(entranceNode)、本次调用链路的 当前节点(curNode)、调用来源(origin)等信息。Context 名称即为调用链路入口名称。

​ **Node是对一个@SentinelResource标记的资源的统计包装。**可以通过入口节点的childList,可以追溯资源的调用情况。而每个节点都对应一个@SentinelResource标记的资源及其统计数据,例如:passQps,blockQps,rt等数据。

Entry是Sentinel中用来表示是否通过限流的一个凭证,如果能正常返回,则说明你可以访问被Sentinel保护的后方服务,否则Sentinel会抛出一个BlockException。

Entry保存了本次执行entry()方法的一些基本信息,包括资源的Context、Node、对应的责任链等信息,后续完成资源调用后,还需要更具获得的这个Entry去执行一些善后操作,包括退出Entry对应的责任链,完成节点的一些统计信息更新,清除当前线程的Context信息等。

2)获取资源对应的责任链。

​ 默认的责任链中的处理节点包括NodeSelectorSlot、ClusterBuilderSlot、StatisticSlot、FlowSlot、DegradeSlot等。调用链(ProcessorSlotChain)和其中包含的所有Slot都实现了ProcessorSlot接口,采用责任链的模式执行各个节点的处理逻辑,并调用下一个节点。其中,每个资源对应一条单独的责任链。

​ 责任链中获取资源,源码中采用双重双检锁方式获取资源。

3)生成资源调用凭证(Entry)。

​ 生成的Entry是CtEntry。其构造参数包括资源包装(ResourceWrapper)、资源对应的责任链以及当前线程的Context。CtEntry是一个双向链表,构建了Sentinel资源的调用链路。

4)执行责任链中各个节点。

​ 责任链和其中的Slot都实现了ProcessorSlot,责任链的entry方法会依次执行责任链各个slot。

1)**NodeSelectorSlot -- 获取当前资源对应Node,构建节点调用树。**这个Node被用于后续资源调用的统计及限流和熔断条件的判断。

2)ClusterBuilderSlot -- 聚合相同资源不同Context的Node,以供后续限流判断使用。默认的限流条件判断就是依据ClusterNode中的统计信息来进行的。

3)**StatisticSlot -- 资源调用统计。**与之前slot不同的是,StatisticSlot的执行时先触发下一个slot的执行,等下面的slot执行完才会执行自己的逻辑。

4)FlowSlot -- 限流判断。之前在StatisticSlot对相关资源调用做的统计,在当前环节使用。默认情况下,限流使用的节点是当前节点的cluster node。主要分析的限流方式是QPS限流。

​ 限流操作的核心逻辑--限流规则检查器(FlowRuleChecker):

  • 获取资源对应的限流规则

  • 根据限流规则检查是否被限流

  • 如果被限流,则抛出限流异常FlowException。FlowException继承自BlockException

    限流的关键代码(DefaultController):

  • 获取节点的当前qps计数;

  • 判断获取新的计数后是否超过阈值

  • 超过阈值单返回false,表示被限流,后面会抛出FlowException。否则返回true,不被限流。

5)Entry.exit()方法,清空资源。执行流程:

  • 判断要退出的entry是否是当前context的当前entry;
  • 如果要退出的entry不是当前context的当前entry,则不退出此entry,而是退出context的的当前entry及其所有父entry,并抛出异常;
  • 如果要退出的entry是当前context的当前entry(这种是正常情况),先退出当前entry对应的责任链的所有slot。在这一步,StatisticSlot会更新node的success计数和RT计数;
  • 将context的当前entry置为被退出的entry的父entry;
  • 如果被退出entry的父entry为空,且context为默认context,自动退出默认context(清除ThreadLocal)。
  • 清除被退出entry的context引用

​ 如果触发熔断和限流,会抛出BlockException,我们可以指定blockHandler方法来处理BlockException。而对于业务上的异常,我们也可以配置fallback方法来处理被拦截方法调用产生的异常。

NodeSelectorSlot的的作用是:

  • 在资源对应的调用链执行时,获取当前context对应的Node,这个Node代表着这个资源的调用情况。
  • 将获取到的node设为当前node,添加到之前的node后面,形成树状的调用路径。(通过Context中的当前Entry进行)
  • 触发下一个Slot的执行。
小结

​ 三大组件Context、Entry、Node,是Sentinel的核心组件,各类信息及资源调用情况都由这三大类持有。

  • 采用责任链模式完成Sentinel的信息统计、熔断、限流等操作;
  • 责任链中NodeSelectSlot负责选择当前资源对应的Node,同时构建node调用树;
  • 责任链中ClusterBuilderSlot负责构建当前Node对应的ClusterNode,用于聚合同一资源对应不同Context的Node;
  • 责任链中的StatisticSlot用于统计当前资源的调用情况,更新Node与其对用的ClusterNode的各种统计数据;
  • 责任链中的FlowSlot根据当前Node对应的ClusterNode(默认)的统计信息进行限流;
  • 资源调用统计数据(例如PassQps)使用滑动时间窗口进行统计;
  • 所有工作执行完毕后,执行退出流程,补充一些统计数据,清理Context。

Sentinel 高级特性

1、同时支持同步和异步调用

​ Sentinel 可以方便地支持同步和异步调用。 对于同步调用,可以使用 @SentinelResource 注解,在注解中指定需要进行保护的方法,并设置相应的熔断降级、流控规则等限制条件。

​ 对于异步调用,则需要使用 Sentinel 提供的异步 Entry 类实现保护。 在使用异步 Entry 进行保护时,需要在异步调用过程中插入 Sentinel 的拦截器,并在异步操作完成后手动释放相应的资源,以便 Sentinel 统计并记录相应的数据。

java 复制代码
CompletableFuture.supplyAsync(() -> {
  Entry entry = null;
  try {
    entry = SphU.asyncEntry("demoMethod");
    // 异步逻辑
    return "Hello World";
  } catch (BlockException ex) {
    return "blocked by Sentinel: " + ex.getClass().getSimpleName();
  } finally {
    //释放资源
    if (entry != null) {
      entry.exit();
    }
  }
}).thenAccept(result -> System.out.println("result: " + result));

2、支持多种限流模式

Sentinel 支持多种限流模式,可以根据实际需求选择不同的限流算法。

  • **直接模式:**直接对资源进行限制,超出阈值即触发限流。
  • **关联模式:**通过关联资源来进行限流,例如对某个 API 进行流量控制时,可以通过关联访问该 API 的数据库连接池来实现流量控制。通过 setRefResource 和 setStrategy 来关联数据库连接池资源,并设置限流策略为关联模式。
  • **链路模式:**通过对整个链路进行流量控制来保障系统的稳定性。

注意:关联模式和链路模式时,需要在规则中设置相关的关联链接和链路信息。

java 复制代码
// 关联模式示例:
@SentinelResource(value = "demoMethod", blockHandler = "handleBlock")
public void demoMethod(@RequestParam("id") Long id) {
  System.out.println("request id: " + id);
}

@Bean
public RequestOriginParser requestOriginParser() {
  return new DemoRequestOriginParser();
}

public static class DemoRequestOriginParser implements RequestOriginParser {
  @Override
  public String parseOrigin(HttpServletRequest request) {
    String origin = request.getParameter("origin");
    if (StringUtils.isEmpty(origin)) {
      return "unknown";
    }
    return origin;
  }
}

@Configuration
public class SentinelConfig {

  @Autowired
  private RequestOriginParser requestOriginParser;

  @PostConstruct
  public void init() {
    FlowRuleManager.register2(Arrays.asList(
        new FlowRule("demoMethod").setCount(5)
            .setGrade(RuleConstant.FLOW_GRADE_QPS)
            .setLimitApp("default")
            .as(FlowRule.class)
            .setStrategy(RuleConstant.STRATEGY_RELATE)
            .setRefResource("demoDatabase")));
  }

  @Bean
  public SentinelResourceAspect sentinelResourceAspect() {
    return new SentinelResourceAspect();
  }

  @Bean
  public SentinelServletRequestAspect sentinelServletRequestAspect() {
    return new SentinelServletRequestAspect();
  }
}

3、支持多种规则匹配方式

Sentinel 支持多种规则匹配方式,可以根据实际需求选择不同的规则匹配策略。

  • **精确匹配:**精确匹配是最常用的匹配方式,可以根据资源名称、限流参数等精确匹配规则。
  • 子串匹配:通常用于对资源名称进行模糊匹配,例如对某个 API 的所有请求进行限流。
  • **正则匹配:**可以根据正则表达式进行规则匹配,提供更高级别的灵活性。使用 setResourceRegex 方法设置了一个正则匹配规则
java 复制代码
//正则匹配规则的示例
@SentinelResource(value = "demoMethod", blockHandler = "handleBlock")
public void demoMethod(@RequestBody Map<String, Object> data) {
  System.out.println("request data: " + data);
}

@Configuration
public class SentinelConfig {

  @PostConstruct
  public void init() {
    SystemRuleManager.loadRules(Collections.singletonList(
        new SystemRule()
            .setHighestSystemLoad(1.0)
            .setAvgLoad(0.8)
            .setQps(200))));
    ParamFlowRuleManager.loadRules(Collections.singletonList(
        new ParamFlowRule()
            .setParamIdx(0)
            .setGrade(RuleConstant.FLOW_GRADE_QPS)
            .setCount(5)
            .setDurationInSec(1)
            .setParamFlowItemList(Collections.singletonList(
                new ParamFlowItem().setObject("special_object")
                    .setCount(2)))));
    DegradeRuleManager.loadRules(Collections.singletonList(
        new DegradeRule("demoMethod")
            .setCount(100)
            .setTimeWindow(10)
            .setGrade(RuleConstant.DEGRADE_GRADE_RT)
            .setCount(20)
            .setMinRequestAmount(10))));
    FlowRuleManager.loadRules(Collections.singletonList(
        new FlowRule()
            .setGrade(RuleConstant.FLOW_GRADE_QPS)
            .setResourceRegex("/api/.*")
            .setCount(10)
            .setLimitApp("default")
            .as(FlowRule.class))));
  }

  @Bean
  public SentinelResourceAspect sentinelResourceAspect() {
    return new SentinelResourceAspect();
  }
}

其他

以后补充...

总结

​ Sentinel是面向分布式、多语言异构化服务架构的流量治理组件,主要以流量为切入点,从流量路由、流量控制、流量整形、熔断降级、系统自适应过载保护、热点流量防护等多个维度来帮助开发者保障微服务的稳定性。是一个非常不错的流量控制中间件。

相关推荐
黄俊懿1 天前
【深入理解SpringCloud微服务】Sentinel功能详解
后端·spring·spring cloud·微服务·中间件·架构·sentinel
孙克旭_1 天前
第一章 Sentinel
java·开发语言·sentinel
向阳12181 天前
Dubbo接入Sentinel实现限流熔断
sentinel·dubbo
乄bluefox1 天前
基于之前的秒杀功能的优化(包括Sentinel在SpringBoot中的简单应用)
java·spring boot·spring·sentinel
camellias_1 天前
SpringBoot(三十六)SpringBoot使用sentinel自定义注解实现限流
java·spring boot·sentinel
W@Lucky1 天前
谷粒商城篇章12--P326-P339--Sentinel/Sleuth+Zipkin服务链路追踪【分布式高级篇九】
分布式·sentinel·sleuth·链路追踪·zipkin·熔断降级限流
爱加瓦小瑞小瑞4 天前
Sentinel服务保护
sentinel
转测试啦转测试啦8 天前
Redis哨兵(sentinel)
redis·sentinel·php
wclass-zhengge9 天前
SpringCloud篇(服务保护 - Sentinel)
spring·spring cloud·sentinel
cui_win13 天前
Redis高可用-Sentinel(哨兵)
redis·bootstrap·sentinel