Sentinel: 在分布式系统中流控和熔断降级的处理实现

177 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第31天,点击查看活动详情

一、Sentinel 是什么

image.png 随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式服务架构的流量控制组件,主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定性。

源码地址:github.com/alibaba/Sen…

官方文档:github.com/alibaba/Sen…

1-1、Sentinel的特征:

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

1-2、Sentinel和Hystrix对比

image.png

二、Sentinel快速开始

github.com/alibaba/Sen…

在官方文档中,定义的Sentinel进行资源保护的几个步骤:

  1. 定义资源
  2. 定义规则
  3. 检验规则是否生效
Entry entry = null;
// 务必保证 finally 会被执行
try {
    // 资源名可使用任意有业务语义的字符串  开启资源的保护
    entry = SphU.entry("自定义资源名");
    // 被保护的业务逻辑    method
    // do something...
} catch (BlockException ex) {
// 资源访问阻止,被限流或被降级   Sentinel定义异常  流控规则,降级规则,热点参数规则。。。。   服务降级(降级规则)
// 进行相应的处理操作
} catch (Exception ex) {
// 若需要配置降级规则,需要通过这种方式记录业务异常    RuntimeException     服务降级   mock  feign:fallback 
    Tracer.traceEntry(ex, entry);
} finally{
        // 务必保证 exit,务必保证每个 entry 与 exit 配对
        if(entry!=null){
        entry.exit();
        }
}

2-1、Sentinel资源保护的方式

2-1-1、API实现

首先创建一个springboot项目,可以不依赖之前的父项目,sentinel不依赖spring Cloud Alibaba,可以单独进行使用,可以单独放在分布式服务里面使用。

2-1-1-1、引入依赖

<!--web 场景启动器-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--导入sentinel依赖-->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-core</artifactId>
    <version>1.8.0</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

2-1-1-2、编写测试逻辑

package com.jony.sentinel.controller;

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.Tracer;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;

@RestController
@Slf4j
public class TestController {

    private static final String RESOURCE_NAME = "hello";

    @RequestMapping(value = "/hello")
    public String hello() {

        Entry entry = null;
        try {
            // 资源名可使用任意有业务语义的字符串,比如方法名、接口名或其它可唯一标识的字符串。
            entry = SphU.entry(RESOURCE_NAME);
            // 被保护的业务逻辑
            String str = "hello world";
            log.info("====="+str);
            return str;
        } catch (BlockException e1) {
            // 资源访问阻止,被限流或被降级
            //进行相应的处理操作
            log.info("block!");
        } catch (Exception ex) {
            // 若需要配置降级规则,需要通过这种方式记录业务异常
            Tracer.traceEntry(ex, entry);
        } finally {
            if (entry != null) {
                entry.exit();
            }
        }
        return null;
    }

    /**
     * 定义流控规则
     */
    //通过添加@PostConstruct注解,设置在当前Bean在初始化的时候,先加载这个方法
    @PostConstruct
    private static void initFlowRules(){
        List<FlowRule> rules = new ArrayList<>();
        FlowRule rule = new FlowRule();
        //设置受保护的资源
        rule.setResource(RESOURCE_NAME);
        // 设置流控规则 QPS
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        // 设置受保护的资源阈值
        // Set limit QPS to 20.
        rule.setCount(1);
        rules.add(rule);
        // 加载配置好的规则
        FlowRuleManager.loadRules(rules);
    }
}

方法说明:hello是对外暴露的接口,initFlowRules为初始化Sentinel规则的方法。
1、在initFlowRules方法中,设置了相关属性,目前方法中设置了每秒QPS为1,具体属性说明如下: image.png

2、在hello方法中,设置了资源名称,这样sentinel控制台中就可以看到对应的资源监控信息。

测试效果:

每秒刷新一下页面的情况下是可以正常访问的 image.png

刷新频繁的情况下,就会发生熔断的触发,如下: image.png

到此sentinel的简答使用就结束了,后面再介绍在Spring Cloud Alibaba中的使用

2-1-1-3、缺点

  • 业务侵入性很强,需要在controller中写入非业务代码.
  • 配置不灵活 若需要添加新的受保护资源 需要手动添加 init方法来添加流控规则

2-1-2、@SentinelResource注解的使用

通过上面的代码虽然可以实现sentinel,但由于对代码的侵入性太强,如果是这样肯定会被抛弃,因此同时使用@SentinelResource就可以实现,下面来看一下是如何实现的

2-1-2-1、引入依赖

<!--使用@SentinelResource 注解的依赖-->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-annotation-aspectj</artifactId>
    <version>1.8.0</version>
</dependency>

通过上面的依赖代码,可以看到其是依赖切面来实现的。

2-1-2-2、配置切面支持

image.png

2-1-2-3、@SentinelResource的使用

可以看下github上面的说明:github.com/alibaba/Sen…

image.png

2-1-2-3-1、给接口添加@SentinelResource注解

image.png

2-1-2-3-2、添加流控处理方法

image.png

2-1-2-3-2-3、添加流控规则

流控规则,在github说明中,提供了如下属性 image.png

image.png

2-1-2-3-2-4、测试

未被流控的处理结果: image.png

被流控处理的结果: image.png

2-1-2-3-2-5、特别说明

特别地,若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑。若未配置 blockHandlerfallback 和 defaultFallback,则被限流降级时会将 BlockException 直接抛出(若方法本身未定义 throws BlockException 则会被 JVM 包装一层 UndeclaredThrowableException)。

2-1-2-3-3、添加降级规则

如下,为熔断降级规则相关参数 image.png

2-1-2-3-3-1、熔断规则
/**
 * 设置降级规则
 */
@PostConstruct
private static void initDegradeRule() {
    List<DegradeRule> rules = new ArrayList<>();
    DegradeRule rule = new DegradeRule();
    rule.setResource("editUser");
    //设置熔断策略,0:调用比例 1:异常比例 2:异常数策略
    rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO);
    //慢调用比例模式下为慢调用临界 RT(超出该值计为慢调用)
    rule.setCount(2);
    //触发熔断的最小请求数
    rule.setMinRequestAmount(2);
    //统计时长:一分钟内,执行了2此请求,并且2次异常就触发熔断
    rule.setStatIntervalMs(60*1000);
    //熔断时长,单位为 s
    //一旦触发熔断,再次请求对应的接口,如果第一次就发生异常就会直接调用降级
    //熔断时长过去之后,就会进入一个半开状态,也就是恢复接口的请求调用,
    //      如果第一次还是异常,就再次降级,上面设定的条件就不起作用了
    rule.setTimeWindow(10);
    rules.add(rule);
    DegradeRuleManager.loadRules(rules);
}
2-1-2-3-3-2、添加请求方法

image.png

因为使用了blockHandlerForGetUser方法,因此需要注意的是,当前方法的参数,需要和blockHandlerForGetUser的参数一致,blockHandlerForGetUser方法可以额外添加BlockException参数。

image.png

2-1-2-3-3-3、测试

访问接口,前三次都是如下错误信息: image.png

第四次接口实现降级处理: image.png

第五次显示异常信息: image.png

第六次显示降级处理: image.png

通过以上就证明了上面的结论:如果接口触发降级规则,在下次半开状态请求,如果第一次就触发异常,就会再次降级,之前设置的降级规则就不会起作用。

2-1-2-4、总结

通过以上处理,我们的代码就不必过于侵入业务代码,只需要添加@SentinelResource注解,就可以进行处理。

另外流控规则一般设置在服务提供方;降级规则一般设置在服务的消费端。