MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Spring Cloud Alibaba 服务限流与熔断实战

2021-06-082.2k 阅读

一、微服务架构中的服务限流与熔断概述

1.1 微服务架构面临的挑战

在当今的软件开发领域,微服务架构已成为构建大型分布式系统的主流选择。与传统的单体架构相比,微服务架构将一个大型应用拆分成多个小型、独立的服务,每个服务都可以独立开发、部署和扩展。这种架构模式带来了诸多好处,如提高开发效率、增强系统的可维护性和可扩展性等。

然而,微服务架构也引入了新的挑战。随着服务数量的增加,系统的复杂性显著上升。服务之间的依赖关系变得错综复杂,一个服务的故障可能会引发连锁反应,导致整个系统的瘫痪。此外,高并发场景下,大量请求可能会使某个服务不堪重负,从而影响整个系统的性能。

1.2 服务限流的概念与作用

服务限流是应对上述挑战的重要手段之一。它的核心思想是通过限制单位时间内进入系统或某个服务的请求数量,来防止系统因过载而崩溃。例如,在电商促销活动期间,大量用户同时访问商品详情页、下单等服务,如果不对请求进行限流,这些服务可能会因为资源耗尽而无法正常响应,导致用户体验极差。

服务限流能够确保系统在高负载情况下仍能保持部分功能可用,避免因瞬间流量过大而导致的雪崩效应。它就像是给系统安装了一个“阀门”,通过合理调节“阀门”的开度,控制流入系统的请求流量。

1.3 服务熔断的概念与作用

服务熔断是另一个关键的容错机制。当某个服务出现故障或响应时间过长时,为了防止故障的扩散,系统会暂时切断对该服务的调用,就像电路中的保险丝一样,在电流过大时自动熔断以保护电路。

例如,在一个包含多个微服务的系统中,A 服务依赖 B 服务。如果 B 服务由于某种原因(如网络故障、资源耗尽等)无法正常响应,A 服务如果持续尝试调用 B 服务,会消耗大量的资源(如线程、连接等),最终导致 A 服务自身也无法正常工作。服务熔断机制会在检测到 B 服务故障时,快速切断 A 服务对 B 服务的调用,A 服务可以执行一些降级策略(如返回默认值、提示友好的错误信息等),从而保证自身的可用性,避免故障进一步蔓延到其他依赖 A 服务的组件。

二、Spring Cloud Alibaba 介绍

2.1 Spring Cloud Alibaba 简介

Spring Cloud Alibaba 是阿里巴巴提供的微服务开发一站式解决方案,是 Spring Cloud 的一个子项目。它将阿里巴巴在分布式应用开发中的经验和技术,以 Spring Cloud 组件的形式进行封装,方便开发者在 Spring Cloud 生态中使用这些技术。

Spring Cloud Alibaba 提供了丰富的组件,涵盖了服务注册与发现、配置管理、流量控制、熔断降级等多个方面。其中,与服务限流和熔断密切相关的组件有 Sentinel 和 Hystrix(虽然 Hystrix 已进入维护模式,但在一些项目中仍有使用)。Sentinel 是阿里巴巴开源的面向分布式服务架构的轻量级流量控制框架,具有实时流量控制、熔断降级、系统自适应保护等功能。

2.2 Spring Cloud Alibaba 的优势

使用 Spring Cloud Alibaba 进行微服务开发具有诸多优势。首先,它集成了阿里巴巴成熟的技术,如 Nacos 用于服务注册与发现,Sentinel 用于流量控制和熔断降级等,这些技术在阿里巴巴内部经过了大规模生产环境的验证,具有高度的稳定性和可靠性。

其次,Spring Cloud Alibaba 与 Spring Cloud 生态无缝集成,开发者可以很方便地将其融入到现有的 Spring Cloud 项目中,无需进行大规模的架构调整。同时,它提供了丰富的 API 和易于使用的控制台,降低了开发和运维的难度。

例如,通过 Sentinel 控制台,开发者可以直观地查看服务的实时流量数据、设置限流规则、熔断规则等,大大提高了开发效率和系统的可观测性。

三、Spring Cloud Alibaba 服务限流实战

3.1 引入 Sentinel 依赖

在 Spring Boot 项目中使用 Sentinel 进行服务限流,首先需要在 pom.xml 文件中引入相关依赖。假设项目使用 Maven 构建,添加如下依赖:

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

如果项目还需要使用 Sentinel 控制台来管理规则,还需添加如下依赖:

<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel - transport - web - servlet</artifactId>
</dependency>

上述依赖添加完成后,Maven 会自动下载所需的库文件。

3.2 配置 Sentinel

application.yml 文件中进行 Sentinel 的基本配置,例如配置 Sentinel 控制台的地址:

spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8080

这里假设 Sentinel 控制台运行在本地的 8080 端口。

3.3 定义限流规则

3.3.1 基于 QPS 的限流

Sentinel 支持多种限流模式,其中基于每秒查询率(QPS)的限流是最常见的一种。可以通过代码方式或 Sentinel 控制台来定义基于 QPS 的限流规则。

通过代码方式定义的示例如下:

package com.example.demo.controller;

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {

    @GetMapping("/test")
    public String test() {
        try (Entry entry = SphU.entry("testResource")) {
            // 业务逻辑
            return "Hello, Sentinel!";
        } catch (BlockException e) {
            // 限流后的处理逻辑
            return "限流了,请稍后再试";
        }
    }
}

然后在启动类或配置类中添加如下代码来初始化限流规则:

package com.example.demo;

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 org.springframework.context.annotation.Configuration;

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

@Configuration
public class SentinelConfig {

    @PostConstruct
    public void initFlowRules() {
        List<FlowRule> rules = new ArrayList<>();
        FlowRule rule = new FlowRule();
        rule.setResource("testResource");
        rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
        rule.setCount(2); // 每秒允许通过的请求数为 2
        rules.add(rule);
        FlowRuleManager.loadRules(rules);
    }
}

上述代码中,定义了一个名为 testResource 的资源,并设置其 QPS 限流阈值为 2,即每秒最多允许 2 个请求通过该资源对应的接口。

3.3.2 基于线程数的限流

除了基于 QPS 的限流,Sentinel 还支持基于线程数的限流。当某个服务处理请求所需的线程资源较多时,通过限制线程数可以防止系统因为线程耗尽而崩溃。

通过代码方式定义基于线程数的限流规则示例如下:

package com.example.demo.controller;

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ThreadLimitController {

    @GetMapping("/threadTest")
    public String threadTest() {
        try (Entry entry = SphU.entry("threadTestResource")) {
            // 业务逻辑
            return "Thread limit test";
        } catch (BlockException e) {
            // 限流后的处理逻辑
            return "线程数限流了,请稍后再试";
        }
    }
}

在配置类中初始化基于线程数的限流规则:

package com.example.demo;

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 org.springframework.context.annotation.Configuration;

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

@Configuration
public class ThreadLimitConfig {

    @PostConstruct
    public void initThreadFlowRules() {
        List<FlowRule> rules = new ArrayList<>();
        FlowRule rule = new FlowRule();
        rule.setResource("threadTestResource");
        rule.setGrade(RuleConstant.FLOW_GRADE_THREAD);
        rule.setCount(5); // 允许的最大线程数为 5
        rules.add(rule);
        FlowRuleManager.loadRules(rules);
    }
}

上述代码设置了名为 threadTestResource 的资源,基于线程数的限流阈值为 5,即最多允许 5 个线程同时处理该资源对应的请求。

3.4 使用 Sentinel 控制台管理限流规则

虽然通过代码方式可以定义限流规则,但在实际生产环境中,使用 Sentinel 控制台来动态管理限流规则更加方便。

启动 Sentinel 控制台后,访问 http://localhost:8080 (假设控制台运行在本地 8080 端口),登录后可以看到应用列表。选择对应的应用,进入“流控规则”页面,可以创建、修改和删除限流规则。

在创建规则时,可以选择资源名(对应代码中的资源标识)、限流模式(QPS 或线程数)、阈值等参数。例如,要对 test 接口设置 QPS 为 3 的限流规则,在控制台操作如下:

  1. 资源名选择与代码中 SphU.entry("testResource") 对应的 testResource
  2. 限流模式选择“QPS”。
  3. 阈值填写“3”。

这样就通过控制台设置了一个新的限流规则,无需重启应用即可生效。

四、Spring Cloud Alibaba 服务熔断实战

4.1 引入 Hystrix 或 Sentinel 熔断依赖

如果选择使用 Hystrix 进行熔断,在 pom.xml 中添加如下依赖:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring - cloud - starter - netflix - hystrix</artifactId>
</dependency>

如果使用 Sentinel 进行熔断,前面引入的 spring - cloud - starter - alibaba - sentinel 依赖已经包含了熔断相关功能,无需额外添加。

4.2 使用 Hystrix 实现服务熔断

4.2.1 启用 Hystrix

在 Spring Boot 应用的启动类上添加 @EnableHystrix 注解来启用 Hystrix:

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;

@SpringBootApplication
@EnableHystrix
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

4.2.2 定义熔断方法

在需要熔断保护的方法上添加 @HystrixCommand 注解,并指定熔断后的降级方法。例如:

package com.example.demo.service;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.stereotype.Service;

@Service
public class ExampleService {

    @HystrixCommand(fallbackMethod = "fallbackMethod")
    public String exampleMethod() {
        // 可能会失败的业务逻辑,例如调用其他服务
        if (Math.random() > 0.5) {
            throw new RuntimeException("模拟服务调用失败");
        }
        return "正常响应";
    }

    public String fallbackMethod() {
        return "服务不可用,执行降级方法";
    }
}

上述代码中,exampleMethod 方法可能会因为模拟的服务调用失败而抛出异常,当异常发生次数达到一定阈值(Hystrix 有默认配置,也可自定义)时,Hystrix 会触发熔断,后续请求将直接调用 fallbackMethod 方法,返回降级后的响应。

4.3 使用 Sentinel 实现服务熔断

4.3.1 配置熔断规则

在 Sentinel 控制台中,进入应用的“熔断规则”页面来配置熔断规则。例如,要对某个服务接口设置熔断规则:

  1. 资源名填写对应的接口资源标识。
  2. 熔断策略可以选择“慢调用比例”“异常比例”“异常数”等。
    • 若选择“慢调用比例”,设置慢调用 RT(响应时间)阈值,例如 500ms,即响应时间超过 500ms 的调用被视为慢调用。同时设置慢调用比例阈值,如 0.5,表示当慢调用比例超过 50%时触发熔断。
    • 若选择“异常比例”,设置异常比例阈值,如 0.2,表示当请求的异常比例超过 20%时触发熔断。
    • 若选择“异常数”,设置异常数阈值,如 10,表示当异常数累计达到 10 次时触发熔断。
  3. 熔断时长设置熔断后多久尝试恢复调用,例如设置为 10s,即熔断 10 秒后 Sentinel 会尝试恢复对该资源的调用。

4.3.2 代码实现

在代码中,同样可以使用 Sentinel 的 SphU 来进行熔断保护。例如:

package com.example.demo.controller;

import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class SentinelDegradeController {

    @GetMapping("/sentinelDegrade")
    public String sentinelDegrade() {
        try (Entry entry = SphU.entry("sentinelDegradeResource")) {
            // 业务逻辑
            if (Math.random() > 0.5) {
                throw new RuntimeException("模拟服务调用失败");
            }
            return "正常响应";
        } catch (DegradeException e) {
            // 熔断后的处理逻辑
            return "服务熔断,执行降级逻辑";
        } catch (BlockException e) {
            // 其他限流等阻断情况的处理
            return "被限流或其他阻断,处理逻辑";
        }
    }
}

上述代码中,当触发熔断时(如根据在 Sentinel 控制台设置的异常比例等熔断策略达到触发条件),会捕获 DegradeException 异常,执行相应的降级逻辑。

五、服务限流与熔断的高级应用

5.1 限流与熔断的结合使用

在实际项目中,服务限流和熔断通常结合使用,以提供更强大的容错能力。例如,先通过限流控制进入服务的请求数量,防止服务因请求过多而资源耗尽,出现大量慢请求或异常。当服务出现故障(如大量异常或慢调用)时,熔断机制介入,暂时切断对故障服务的调用,避免故障扩散。

假设在一个电商订单服务中,订单创建接口首先通过 Sentinel 设置 QPS 限流为 100,即每秒最多处理 100 个订单创建请求。同时,设置熔断规则为异常比例超过 30%时触发熔断,熔断时长为 30 秒。当订单服务受到大量请求冲击时,限流机制会将请求数量控制在合理范围内。如果由于某些原因(如数据库故障)导致订单创建接口出现大量异常,异常比例超过 30%,则会触发熔断,在接下来的 30 秒内,新的订单创建请求将直接返回熔断降级后的提示信息,如“订单服务繁忙,请稍后再试”,而不会继续尝试调用实际的订单创建逻辑,从而保证整个系统的稳定性。

5.2 动态调整限流与熔断规则

在生产环境中,系统的负载情况和业务需求可能会动态变化。因此,动态调整限流与熔断规则是非常必要的。

Sentinel 控制台提供了方便的动态规则调整功能。运维人员或开发人员可以根据系统的实时监控数据(如 CPU 使用率、内存使用率、请求成功率等),在 Sentinel 控制台实时调整限流和熔断规则。例如,在电商促销活动开始时,预计订单服务的请求量会大幅增加,可以提前在 Sentinel 控制台将订单创建接口的 QPS 限流阈值从平时的 100 提高到 500。同时,根据活动前的压测结果,调整熔断规则,如将异常比例阈值从 30%调整为 40%,以适应高并发场景下可能出现的更多异常情况。

除了通过控制台手动调整,还可以通过 API 实现自动化的规则调整。例如,结合 Prometheus 和 Grafana 等监控工具,当监控到系统的某些指标达到特定阈值时,自动调用 Sentinel 的 API 来动态修改限流和熔断规则,实现系统的自适应调整。

5.3 服务间的限流与熔断策略设计

在微服务架构中,服务之间存在复杂的依赖关系。因此,设计合理的服务间限流与熔断策略至关重要。

对于不同层次的服务,可以采用不同的限流和熔断策略。例如,对于基础服务(如数据库连接服务、缓存服务等),可以设置较为严格的限流规则,以保护这些关键资源不被耗尽。同时,对调用这些基础服务的上层服务设置合适的熔断规则,防止基础服务故障影响上层服务的可用性。

在服务依赖链中,要考虑级联熔断的情况。例如,A 服务调用 B 服务,B 服务调用 C 服务。如果 C 服务出现故障,B 服务触发熔断,此时 A 服务也应该尽快触发熔断,而不是持续尝试调用 B 服务,避免 A 服务因等待 B 服务响应而消耗过多资源。可以通过设置合理的熔断传播机制来实现这一点,例如在 A 服务中设置较短的调用 B 服务的超时时间,当 B 服务熔断时,A 服务能快速感知并执行自身的熔断降级逻辑。

另外,对于一些关键业务流程涉及的服务,要确保限流和熔断策略不会影响核心业务的正常运行。例如,在电商支付流程中,支付确认服务不能因为限流或熔断而导致用户支付失败。可以对这类关键服务设置更高的限流阈值或更宽松的熔断条件,同时准备备用方案(如备用支付渠道),以保证业务的连续性。

六、常见问题与解决方案

6.1 限流与熔断误判问题

在某些情况下,可能会出现限流或熔断的误判。例如,在网络波动期间,服务的响应时间可能会短暂变长,导致 Sentinel 误判为慢调用,触发熔断。

解决方案是合理设置熔断和限流的参数。对于熔断,可以适当增加熔断判断的统计时间窗口,避免因短暂的异常或慢调用就触发熔断。例如,将慢调用比例熔断策略中的统计时间窗口从默认的 10 秒增加到 30 秒,这样可以更准确地判断服务是否真的出现了性能问题。对于限流,要根据服务的实际性能和资源情况,结合历史数据和压测结果,精确设置限流阈值,避免设置过低导致正常请求被限流,或设置过高而无法起到限流保护作用。

6.2 熔断后恢复缓慢问题

当服务熔断后,可能会出现恢复缓慢的情况,导致长时间内服务不可用。这可能是由于熔断恢复的探测机制不合理,或者服务故障根本原因未解决,导致反复熔断。

解决方法是优化熔断恢复的探测机制。可以采用逐步增加请求量的方式来探测服务是否恢复正常,而不是一次性大量请求。例如,在熔断时长结束后,先以极低的 QPS(如 1)发送请求,如果连续多次请求成功,则逐步增加 QPS,直到恢复到正常水平。同时,要及时排查服务故障的根本原因,如是否是服务器资源不足、代码逻辑错误等,确保服务真正恢复正常,避免反复熔断。

6.3 与其他框架的兼容性问题

Spring Cloud Alibaba 的限流与熔断组件可能会与项目中使用的其他框架存在兼容性问题。例如,与某些自定义的负载均衡框架或日志框架可能产生冲突。

解决方案是在项目选型和架构设计阶段,充分考虑各框架之间的兼容性。在引入新框架时,进行充分的测试,包括功能测试、性能测试和兼容性测试。如果发现兼容性问题,查看各框架的官方文档,了解是否有已知的解决方案或配置调整方法。例如,对于负载均衡框架与 Sentinel 的兼容性问题,可以参考 Sentinel 的官方文档,调整负载均衡策略的配置,使其与 Sentinel 的限流熔断机制协同工作。同时,关注框架的版本更新,及时升级到兼容性更好的版本。

通过以上对 Spring Cloud Alibaba 服务限流与熔断的实战介绍、高级应用以及常见问题的分析与解决,希望能帮助开发者在微服务架构中更好地应用这些技术,构建更加稳定、可靠的分布式系统。在实际项目中,要根据业务需求和系统特点,灵活运用限流与熔断机制,并不断优化和调整相关策略,以应对复杂多变的生产环境。