Sentinel 框架详解

一、简介

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

Sentinel 提供了以下功能:

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

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

二、Sentinel 的原理

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

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

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

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

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

三、Sentinel 快速入门

以下是 Sentinel 快速入门的几个步骤:

  1. 首先,在 Maven 中引入 Sentinel 的依赖:

    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        <version>2.2.1.RELEASE</version>
    </dependency>
    
  2. 在 Spring Boot 中配置 Sentinel 的启动参数:

    spring.cloud.sentinel.transport.dashboard=http://localhost:8080
    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
    

    其中,spring.cloud.sentinel.transport.dashboard 配置了 Sentinel 控制台的地址,spring.cloud.sentinel.transport.port 配置了 Sentinel 的启动端口。

  3. 在需要进行流量控制的方法上添加注解:

    @SentinelResource(value = "demoMethod", blockHandler = "handleBlock")
    public String demoMethod() {
          
          
      return "Hello World";
    }
    
    public String handleBlock(BlockException ex) {
          
          
      return "请求被拦截: " + ex.getClass().getSimpleName();
    }
    

    在上述代码中,我们使用 @SentinelResource 注解对 demoMethod 方法进行了流量控制,并设置了 fallback 方法为 handleBlock。当触发限流时,就会执行 handleBlock 方法来返回自定义的响应结果。

四、使用 Sentinel 进行熔断降级

Sentinel 不仅能够进行流量控制,还能够进行熔断降级。当系统出现一定程度的异常时,就会触发熔断降级策略来保证系统的可用性。以下是使用 Sentinel 进行熔断降级的几个步骤:

  1. 在业务方法上添加 @SentinelResource 注解,并指定 fallbackClassfallback 属性值:

    @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();
    }
    

    在上述代码中,我们指定了 fallbackClassfallback 属性来定义 fallback 方法的实现类和方法名。当服务出现熔断降级时,就会执行 fallback 方法来返回自定义的响应结果。

  2. 定义 fallback 方法及其实现类:

    public class DemoServiceFallback {
          
          
      public static String fallback() {
          
          
        return "请求被熔断降级";
      }
    }
    

    在上述代码中,我们定义了 fallback 方法及其实现类 DemoServiceFallback。当服务出现熔断降级时,就会执行 fallback 方法来返回自定义的响应结果。

五、Sentinel 的流控规则和热点参数限流

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

流控规则

Sentinel 的流控规则有以下几种:

  1. QPS 流量控制:通过对 API 进行 QPS 控制,即限定接口在一定时间内能够处理的请求次数。

    @SentinelResource(value = "demoMethod", blockHandler = "handleBlock")
    @RateLimiter(10)
    public String demoMethod() {
          
          
      return "Hello World";
    }
    
    public String handleBlock(BlockException ex) {
          
          
      return "请求被拦截: " + ex.getClass().getSimpleName();
    }
    

    在上述代码中,我们使用 @RateLimiter 注解对接口进行了 QPS 流控,并设置了限流阈值为 10。

  2. 线程数流控:通过对线程池中的线程数进行限制,来避免线程池过载。

    @SentinelResource(value = "demoMethod", blockHandler = "handleBlock")
    @ThreadPool(name = "demoMethod", coreSize = 5, maxQueueSize = 10)
    public String demoMethod() {
          
          
      return "Hello World";
    }
    
    public String handleBlock(BlockException ex) {
          
          
      return "请求被拦截: " + ex.getClass().getSimpleName();
    }
    

    在上述代码中,我们使用 @ThreadPool 注解对线程池进行了流控,并设置了线程池的核心线程数为 5。

热点参数限流

热点参数限流是 Sentinel 的一个重要特性,可以有效避免因某个参数的恶意使用而导致整个系统崩溃的情况。例如,假设有一个商品详情接口,其中的参数 skuId 很可能存在热点,即某些具体的商品 skuId 会被大量请求。如果不进行限流,当出现某个特定 skuId 的恶意攻击时,系统可能会崩溃。

以下是一个使用 Sentinel 实现热点参数限流的示例:

@SentinelResource(value = "demoMethod", blockHandler = "handleBlock")
@HotParam(value = "skuId", mode = ParamFlowItem.FlowControlMode.QPS, threshold = 100)
public String demoMethod(@RequestParam Long skuId) {
    
    
  return "Hello World";
}

在上述代码中,我们使用 @HotParam 注解对 skuId 参数进行限流,并设置了限流阈值为 100 QPS。这样,当某个 skuId 的请求超过 100 QPS 时,就会触发 Sentinel 的限流机制。通过这种方式,我们可以避免因某个热点参数的异常使用而导致整个系统崩溃的情况。

六、Sentinel 的优缺点

Sentinel 作为一个成熟的分布式系统的流量防卫兵,具有以下优点:

  • 功能丰富:Sentinel 提供了流量控制、熔断降级、系统保护、实时监控等全方位的服务质量保障。
  • 易于使用:Sentinel 提供了友好的 Web 界面,可以方便的进行规则的配置和管理。
  • 高度可定制:Sentinel 提供了多种 SPI 扩展点,可以根据业务需求进行自定义扩展。
  • 开源免费:Sentinel 是完全开源的,可以免费使用。

当然,Sentinel 还存在以下一些缺点:

  • 不支持语言多样性:Sentinel 目前只支持 Java 应用,对于其他编程语言的应用不够友好。
  • 文档略显简单:Sentinel 的文档虽然已经比较全面,但是在一些实践场景中仍然存在一些坑点需要注意。

七、Sentinel 高级特性

同时支持同步和异步调用

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

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

例如,以下是一个使用异步 Entry 进行保护的示例:

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));

在上述代码中,我们首先使用 SphU.asyncEntry 方法创建一个异步 Entry,然后在异步逻辑中执行业务操作。要注意的是,当异步操作完成时,需要手动调用 entry.exit() 方法释放相应的资源。

支持多种限流模式

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

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

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

例如,以下是一个使用关联模式进行流量控制的示例:

@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();
  }
}

在上述代码中,我们使用 @SentinelResource 注解进行流量控制,并通过 setRefResource 和 setStrategy 来关联数据库连接池资源,并设置限流策略为关联模式。

支持多种规则匹配方式

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

  • 精确匹配:精确匹配是最常用的匹配方式,可以根据资源名称、限流参数等精确匹配规则。
  • 子串匹配:通常用于对资源名称进行模糊匹配,例如对某个 API 的所有请求进行限流。
  • 正则匹配:可以根据正则表达式进行规则匹配,提供更高级别的灵活性。

例如,以下是一个使用正则匹配规则的示例:

@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();
  }
}

在上述代码中,我们使用 setResourceRegex 方法设置了一个正则匹配规则,对所有以 /api/ 开头的资源进行流量控制。

八、使用案例

使用 Sentinel 和 Spring Cloud Gateway 实现网关限流

首先,我们需要在项目中引入 Sentinel 和 Spring Cloud Gateway 的依赖:

<!-- 引入 Sentinel -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

<!-- 引入 Spring Cloud Gateway -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

然后,我们可以在 application.yml 文件中添加 Sentinel 和 Spring Cloud Gateway 的相关配置:

spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8250

server:
  port: 8080

spring:
  cloud:
    gateway:
      routes:
        - id: test_route
          uri: http://httpbin.org
          predicates:
            - Path=/anything/**

在上面的配置中,我们指定了 Sentinel 的 dashboard 地址和 Spring Cloud Gateway 的端口号,在 Spring Cloud Gateway 中添加了一个名为 test_route 的路由,匹配路径为 /anything/**,并将该路由转发到 http://httpbin.org

接下来,我们需要为 Spring Cloud Gateway 添加 Sentinel 规则,以控制请求的流量:

@Bean
public SentinelGatewayFilterFactory sentinelGatewayFilterFactory() {
    
    
    return new SentinelGatewayFilterFactory();
}

@Bean
public GatewayFilterChain gatewayFilterChain(RouteLocator routeLocator,
                                             List<GatewayFilterFactory> gatewayFilters) {
    
    
    DefaultGatewayFilterChain chain = new DefaultGatewayFilterChain(routeLocator.getRoutes(), gatewayFilters);
    chain.add(0, sentinelGatewayFilterFactory.apply(new Object()));
    return chain;
}

在上面的代码片段中,我们创建了一个名为 sentinelGatewayFilterFactory 的 Bean,用于创建 Sentinel 的网关过滤器,并添加到 Gateway 中。

最后,我们需要在 Sentinel dashboard 中进行规则配置,以控制请求流量:

  • 打开 Sentinel 控制台,在左侧导航栏中,选择 Flow,并点击 新增
  • Resource 输入框中输入 /anything/**
  • 限流阈值 输入框中输入限制请求流量的最大数值。
  • 点击 新增

使用 Sentinel 和 RocketMQ 实现消息流量控制

首先,我们需要在项目中引入 Sentinel 和 RocketMQ 的依赖:

<!-- 引入 Sentinel -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

<!-- 引入 RocketMQ -->
<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-spring-boot-starter</artifactId>
    <version>2.0.3</version>
</dependency>

然后,在 application.yml 文件中添加 Sentinel 和 RocketMQ 的相关配置:

spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8250

rocketmq:
  name-server: localhost:9876
  producer:
    group: my-group

其中,我们指定了 Sentinel 的 dashboard 地址和 RocketMQ 的 NameServer 地址,以及 RocketMQ 的生产者组名。

接下来,我们需要为生产者添加 Sentinel 规则,以控制消息发送的流量:

@Slf4j
@Service
public class MyProducer {
    
    

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    @PostConstruct
    public void init() {
    
    
        // 添加 Sentinel 规则
        String resourceName = "myTopic:myTag";
        String ruleKey = "myRuleKey";
        int threshold = 100;
        DegradeRule rule = new DegradeRule(resourceName, ruleKey, threshold);
        rule.setGrade(RuleConstant.DEGRADE_GRADE_RT);
        rule.setTimeWindow(10);
        List<DegradeRule> rules = new ArrayList<>();
        rules.add(rule);
        DegradeRuleManager.loadRules(rules);
    }

    public void sendMessage(String message) {
    
    
        try {
    
    
            rocketMQTemplate.convertAndSend("myTopic", "myTag", message);
        } catch (Exception e) {
    
    
            log.error("发送消息失败,message: {}", message, e);
        }
    }
}

在上面的代码中,我们为 myTopic:myTag 资源添加了一条 Sentinel 规则,该规则的作用是:当该资源的 RT(响应时间)超过 10 毫秒时,将触发熔断,拒绝进一步的请求,防止影响消息系统的正常运行。规则的阈值为 100,即当一秒钟内超过 100 条消息时会触发限流。

最后,在消费者中也需要添加 Sentinel 的规则,以控制消息消费的流量:

@Slf4j
@Service
public class MyConsumer {
    
    

    @RocketMQMessageListener(topic = "myTopic", consumerGroup = "my-group")
    @SentinelResource(value = "myTopic:myTag", blockHandler = "handleBlockedMessage")
    public void handleMessage(@Payload String message) {
    
    
        log.info("接收到消息:{}", message);
    }

    public void handleBlockedMessage(String message, BlockException e) {
    
    
        log.error("消息被拒绝,message: {}", message);
    }
}

在上面的代码中,我们为 myTopic:myTag 资源添加了 Sentinel 规则,并指定了发生 Sentinel 规则限制时的处理方法。在消费过程中,如果超过 Sentinel 规则的阈值,则会触发限流,拒绝进一步的消息消费。

猜你喜欢

转载自blog.csdn.net/u012581020/article/details/130961808