微服务组件 Sentinel(一)

前言

在服务提供者不可用的时候,会出现大量重试的情况:用户重试、代码逻辑重试,这些重试最终导致:-步加大请求流量。 所以归根结底导致雪崩效应的最根本原因是:大量请求线程同步等待造成
的资源耗尽。当服务调用者使用同步调用时,会产生大量的等待线程占用系统资源。一-旦线程 资源被耗尽服务调用者提供的服务也将处于不可用状态,于是服务雪崩效应产生了。

解决方案
稳定性(Reliability)、恢复性(Resilience)

常见的容错机制:

  • 超时机制
在不做任何处理的情况下,服务提供者不可用会导致消费者请求线程强制等待,而造成系统资源耗尽。加入超时机制,一旦超时,就释放资源。由于释放资源速度较快,一定程度上可以抑制资源耗尽
的问题。
  • 限流机制
例如一个服务的 QPS500,当有 800QPS访问时,300QPS 直接拒绝请求
  • 隔离
原理:用户的请求将不再直接访问服务,而是通过线程池中的空闲线程来访问服务,如果线程池已满,则会进行降级处理,用户的请求不会被阻塞,至少可以看到一个执行结果(例如返回友好的提示信息),而不是无休止的等待或者看到系统崩溃。
  • 信号隔离
制并发访问,防止阻塞扩散,与线程隔离最大不同在于执行依赖代码的线程依然是请求线程,该线程需要通过信号申请,如果客户端是可信的且可以快速返回,可以使用信号隔离替换线程隔离,降低开销。信号量的大小可以动态调整,线程池大小不可以。
  • 服务熔断
`远程服务不稳定或网络抖动时暂时关闭,就叫服务熔断。`
现实世界的断路器大家肯定都很了解,断路器实时监控电路的情况,如果发现电路电流异常,就会跳闸,从而防止电路被烧毁。
软件世界的断路器可以这样理解:实时监测应用,如果发现在一定时间内失败次数/失败率达到一 定阈值,就“跳闸”,断路器打开,此时,请求直接返回,而不去调用原本调用的逻辑。跳闸一段时间后(例如10秒),断路器会进入半开状态,这是一个瞬间态,此时允许一次请求调用该调的逻辑,如果成功,则断路器关闭,应用正常调用;如果调用依然不成功,断路器继续回到打开状态,过段时间再进入半开状态尝试通过“跳闸”, 应用可以保护自己,而且避免浪费资源;而通过半开的设计,可实现应用的“自我修复”。
所以,同样的道理,`当依赖的服务有大量超时时,在让新的请求去访问根本没有意义,只会无畏的消耗现有资源`。比如我们设置了超时时间为1s,如果短时间内有大量请求在1s内都得不到响应,就意味着这个服务出现了异常,此时就没有必要再让其他的请求去访问这个依赖了,这个时候就应该使用断路器避免资源浪费。
  • 服务降级
有服务熔断,必然要有服务降级。
所谓降级,就是当某个服务熔断之后,服务将不再被调用,此时客户端可以自己准备一个本地的fallback(回退)回调,返回一个缺省值。例如: (备用接口/缓存/mock数据)。这样做,虽然服务水平下降,但好歹可用,比直接挂掉要强,当然这也要看适合的业务场景。

一、什么是 Sentinel

Sentinel 是阿里巴巴开源的,面向分布式架构的高可用防护组件,是流量防卫兵。提供了多维度的流控降级能力,以不同的运行指标为基准,例如可以针对 QPS,并发线程数,异常,响应时间,系统负载等不同运行指标进行自动降级,排队,快速失败。同时也提供了秒级实时监控与动态规划管理。
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式服务架构的流量控制组件,主要以流量为切入点从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定性。

Sentinel 具有以下特征

  • 丰富的应用场景: Sentinel 承接了阿里巴巴近10年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、实时熔断下游不可用应用等。
  • 完备的实时监控: Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至500台以下规模的集群的汇总运行情况。
  • 广泛的开源生态: Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与Spring Cloud、Dubbo、 gRPC的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入Sentinel。
  • 完善的SPI扩展点: Sentinel 提供简单易用、完善的SPI扩展点。您可以通过实现扩展点,快速的定制逻辑。例如定制规则管理、适配数据源等。
Sentinel Hystrix
隔离策略 信号量隔离 线程池隔离/信号量隔离
熔断降级策略 基于响应时间或失败比例 基于失败比率
实时指标实现 滑动窗口 滑动窗口(基于RxJava)
规则配置 支持多种数据源 支持多种数据源
拓展性 多个拓展性 插件的形式
基于注解的支持 支持 支持
限流 基于 QPS,支持基于调用关系的限流 有限的支持
流量整形 支持慢启动、匀速器模式 不支持
系统负载保护 支持 不支持
控制台 开箱即用,可配置规则,查看秒级监控,机器发现等 不完善
常见框架的适配 Servlet、SpringCloud、Dubbo、GRpc Servlet、SpringCloud Netflix

二、Sentinel 流控体验

2.1、Sentinel 快速开始

引入依赖

<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.4</version>
</dependency>

controller 层代码,定义资源,一般资源名称和接口名称保持一致,并且为资源设置流控规则

@RestController
@Slf4j
public class HelloController {
    public static final String RESOURCE_NAME = "hello";
    public static final String USER_RESOURCE_NAME = "user";
    public static final String DEGRADE_RESOURCE_NAME = "degrade";

    // 进行流控
    @RequestMapping("/hello")
    public String hello() {
        Entry entry = null;
        try {
            // sentinel 针对资源进行限制的
            entry = SphU.entry(RESOURCE_NAME);
            // 被保护的业务逻辑
            String str = "hello world";
            log.info("======"+str+"======");
            return str;
        } catch (BlockException e) {
            // 资源访问阻止,被限流或降级
            // 进行相应的处理操作
            log.info("block!");
            return "被流控了!";
        } catch (Exception e) {
            // 若需要配置降级规则,需要通过这种方式记录业务异常
            Tracer.traceEntry(e,entry);
        } finally {
            if (entry != null) {
                entry.exit();
            }
        }
        return null;
    }

    /**
     * 定义规则
     *
     * spring 的初始化方法
     */
    @PostConstruct // init method
    public 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 1
        rule.setCount(1);
        rules.add(rule);

        // 加载配置好的规则
        FlowRuleManager.loadRules(rules);
    }
}

请求http://localhost:8060/hello,当请求过于频繁时,被流控

微服务组件 Sentinel

2.2、@SentinelResource

上面的代码虽然实现了流控,但是我们可以发现,Sentinel 与代码的耦合性太强了,使用@SentinelResource注解

1、引入依赖

<!--如果要使用 @SentinelResource-->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-annotation-aspectj</artifactId>
    <version>1.8.4</version>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.24</version>
</dependency>

2、设置 bean

// 注解支持的配置 Bean
@Bean
public SentinelResourceAspect sentinelResourceAspect() {
    return new SentinelResourceAspect();
}

3、资源限流设置

@RestController
@Slf4j
public class HelloController {
    public static final String RESOURCE_NAME = "hello";
    public static final String USER_RESOURCE_NAME = "user";
    public static final String DEGRADE_RESOURCE_NAME = "degrade";

    /**
     * 定义规则
     * <p>
     * spring 的初始化方法
     */
    @PostConstruct // init method
    public static void initFlowRules() {
        // 流控规则
        List<FlowRule> rules = new ArrayList<>();

        // 通过 @SentinelResource 来定义资源并配置剪辑和流控的处理方法
        FlowRule rule2 = new FlowRule();
        // 设置受保护的资源
        rule2.setResource(USER_RESOURCE_NAME);
        // 设置流控规则 QPS
        rule2.setGrade(RuleConstant.FLOW_GRADE_QPS);
        // 设置受保护的资源的阈值
        rule2.setCount(1);
        rules.add(rule2);

        // 加载配置好的规则
        FlowRuleManager.loadRules(rules);
    }

    /**
     * @SentinelResource 改善接口中资源定义和被流控降级后的处理方法
     * 怎么使用
     * 1、添加依赖
     * 2、配置Bean SentinelResourceAspect
     * value 定义资源
     * blockHandler 设置流控降级后的处理方法(默认该方法必须设置在一个同一个类中)
     * 如果不想在同一个类中,可以设置 blockHandlerClass,并且方法必须是静态方法
     * fallback 当接口出现异常,就可以交给 fallback 指定的方法进行处理
     */
    @RequestMapping("/user")
    @SentinelResource(value = USER_RESOURCE_NAME, blockHandlerClass = User.class, blockHandler = "blockHandlerForGetUser")
    public User getUser(String id) {
        return new User("hudu");
    }
}

4、pojo 类

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private String username;

    /**
     * 注意
     * 1、一定要 public
     * 2、返回值一定要和原方法保持一致,包含原方法的参数
     * 3、可以在参数最后添加 BlockException 可以区分是什么规则的处理方法
     */
    public static User blockHandlerForGetUser(String id, BlockException ex) {
        ex.printStackTrace();
        return new User("流控!");
    }
}

当访问频繁时,被流控了

微服务组件 Sentinel

fallback 测试

    /**
     * @SentinelResource 改善接口中资源定义和被流控降级后的处理方法
     * 怎么使用
     * 1、添加依赖
     * 2、配置Bean SentinelResourceAspect
     * value 定义资源
     * blockHandler 设置流控降级后的处理方法(默认该方法必须设置在一个同一个类中)
     * 如果不想在同一个类中,可以设置 blockHandlerClass,并且方法必须是静态方法
     * fallback 当接口出现异常,就可以交给 fallback 指定的方法进行处理
     *
     * blockHandler 和 fallback 同时指定了,则 blockHandler 优先级更高
     *
     * 可以通过 exceptionsToIgnore 排除不处理的异常
     */
    @RequestMapping("/user")
    @SentinelResource(value = USER_RESOURCE_NAME,
            blockHandlerClass = User.class, blockHandler = "blockHandlerForGetUser",
            fallbackClass = User.class, fallback = "fallbackForGetUser",
            exceptionsToIgnore = {ArithmeticException.class})
    public User getUser(String id) {
        int a = 1 / 0;
        return new User("hudu");
    }

pojo 类代码

public static User fallbackForGetUser(String id,Throwable e) {
    e.printStackTrace();
    return new User("异常处理");
}

再次请求接口,发现已经被异常处理的方法处理了

微服务组件 Sentinel

微服务组件 Sentinel

当我们刷新频率过高时,会发现接口被流控了,当 blockHandler 和 fallback 同时指定了,则 blockHandler 优先级更高。

三、Sentinel 降级体验

设置降级规则

@PostConstruct
public void initDegradeRule() {
    // 降级规则
    List<DegradeRule> degradeRules = new ArrayList<>();
    DegradeRule degradeRule = new DegradeRule();
    degradeRule.setResource(DEGRADE_RESOURCE_NAME);
    // 设置降级规则策略,异常数
    degradeRule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);
    // 设置阈值,异常数 2
    degradeRule.setCount(2);
    // 触发熔断的最小请求数量
    degradeRule.setMinRequestAmount(2);
    // 统计时长,单位 ms,默认 1000 一秒
    degradeRule.setStatIntervalMs(60*1000);

    // 一分钟内:执行了2次,出现了2次异常,就会触发熔断

    // 熔断持续的时长(时间窗口)单位 s,
    // 一旦触发了熔断,再次请求对应的接口就会直接调用 降级方法
    // 熔断时长多了之后,进入半开状态,恢复接口请求调用,如果第一次请求就异常,再次熔断,不会根据设置的条件进行判定
    degradeRule.setTimeWindow(10);

    degradeRules.add(degradeRule);
    DegradeRuleManager.loadRules(degradeRules);
}
@RequestMapping("/degrade")
@SentinelResource(value = DEGRADE_RESOURCE_NAME,entryType = EntryType.IN,blockHandlerClass = User.class,blockHandler = "blockHandlerForFb")
public User degrade(String id) {
    // 异常数
    throw new RuntimeException("异常");
}
public static User blockHandlerForFb(String id, BlockException ex) {
    return new User("降级");
}

请求http://localhost:8060/degrade接口第四次,发现被降级

微服务组件 Sentinel

在等十秒,可以请求,但是异常之后直接熔断降级。

流控规则一般会设置在服务提供方,熔断降级规则一般设置在服务消费端

本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!