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

多服务依赖下的微服务熔断降级策略

2024-01-154.0k 阅读

微服务架构与服务依赖

在微服务架构中,一个大型应用被拆分成多个小型、自治的服务。这些服务独立开发、部署和扩展,通过轻量级通信机制(如 RESTful API)进行交互。这种架构模式带来了许多优势,如提高开发效率、增强系统可扩展性等。然而,随着服务数量的增加,服务之间的依赖关系变得复杂,一个服务的故障可能会级联影响到其他依赖它的服务,甚至导致整个系统的崩溃,这就是所谓的“雪崩效应”。

服务依赖的复杂性

在一个典型的电商微服务架构中,订单服务可能依赖库存服务来检查商品库存,依赖支付服务来处理支付流程,还依赖用户服务来验证用户信息。库存服务又可能依赖于仓储服务获取仓库实际库存数据。当库存服务出现故障时,订单服务可能会因为不断等待库存服务的响应而资源耗尽,进而影响到支付服务和用户服务,最终导致整个电商系统无法正常处理订单。

雪崩效应的产生机制

  1. 资源耗尽:当一个服务不可用,依赖它的服务会不断地重试请求,消耗自身的线程、连接等资源。例如,假设订单服务有 100 个线程用于处理请求,每个线程在等待库存服务响应时被阻塞,当所有线程都被阻塞后,订单服务将无法处理新的订单请求,即使其他依赖的服务(如支付服务)是正常的,整个业务流程也会因为订单服务无法响应而中断。
  2. 级联故障:一个服务的故障会传递给依赖它的服务,然后这些依赖服务又将故障传递给它们的依赖服务,像多米诺骨牌一样,最终导致整个系统瘫痪。比如,库存服务故障导致订单服务不可用,订单服务不可用又影响支付服务,支付服务不可用可能进一步影响财务统计服务等。

熔断降级策略的概念与原理

为了应对多服务依赖下的雪崩效应,熔断降级策略应运而生。熔断降级策略主要包含熔断和降级两个方面。

熔断机制

熔断机制类似于电路中的保险丝,当电路中的电流过大时,保险丝会熔断以保护电路。在微服务中,当对某个服务的请求失败率达到一定阈值(如 80%),就会触发熔断。熔断后,后续对该服务的请求不再直接发送到实际服务,而是快速返回一个预设的 fallback 响应,避免大量无效请求消耗资源。

  1. 熔断状态

    • 关闭状态(Closed):在正常情况下,服务处于关闭状态,所有请求都正常发送到实际服务。此时熔断器监控请求的成功率和失败率。
    • 打开状态(Open):当失败率达到设定的阈值,熔断器切换到打开状态。在打开状态下,所有请求都不再发送到实际服务,而是直接返回 fallback 响应。
    • 半开状态(Half - Open):在打开状态一段时间后(如 10 秒),熔断器进入半开状态。在半开状态下,会允许少量请求(如 10 个)发送到实际服务,如果这些请求中有一定比例(如 60%)成功,熔断器会切换回关闭状态;如果失败率仍然较高,熔断器会再次回到打开状态。
  2. 熔断算法实现 以下是一个简单的基于滑动窗口的熔断算法示例(以 Java 代码为例):

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class CircuitBreaker {
    private static final int DEFAULT_FAILURE_THRESHOLD = 80;
    private static final int DEFAULT_SLIDING_WINDOW_SIZE = 10;
    private static final long DEFAULT_TIMEOUT = 10;
    private final AtomicInteger totalRequests = new AtomicInteger(0);
    private final AtomicInteger failedRequests = new AtomicInteger(0);
    private long lastStateChangeTime = System.currentTimeMillis();
    private State currentState = State.CLOSED;
    private int failureThreshold = DEFAULT_FAILURE_THRESHOLD;
    private int slidingWindowSize = DEFAULT_SLIDING_WINDOW_SIZE;
    private long timeout = DEFAULT_TIMEOUT;

    public CircuitBreaker() {}

    public CircuitBreaker(int failureThreshold, int slidingWindowSize, long timeout) {
        this.failureThreshold = failureThreshold;
        this.slidingWindowSize = slidingWindowSize;
        this.timeout = timeout;
    }

    public boolean allowRequest() {
        if (currentState == State.OPEN) {
            long elapsedTime = System.currentTimeMillis() - lastStateChangeTime;
            if (elapsedTime >= timeout * 1000) {
                currentState = State.HALF_OPEN;
            } else {
                return false;
            }
        }
        if (currentState == State.HALF_OPEN) {
            if (totalRequests.get() < slidingWindowSize) {
                return true;
            } else {
                int failureRate = (failedRequests.get() * 100) / totalRequests.get();
                if (failureRate >= failureThreshold) {
                    currentState = State.OPEN;
                    lastStateChangeTime = System.currentTimeMillis();
                    return false;
                } else {
                    currentState = State.CLOSED;
                    totalRequests.set(0);
                    failedRequests.set(0);
                    return true;
                }
            }
        }
        return true;
    }

    public void recordFailure() {
        totalRequests.incrementAndGet();
        failedRequests.incrementAndGet();
        int failureRate = (failedRequests.get() * 100) / totalRequests.get();
        if (currentState == State.CLOSED && failureRate >= failureThreshold) {
            currentState = State.OPEN;
            lastStateChangeTime = System.currentTimeMillis();
        }
    }

    public void recordSuccess() {
        totalRequests.incrementAndGet();
    }

    private enum State {
        CLOSED, OPEN, HALF_OPEN
    }
}

在实际应用中,可以在服务调用的代理层(如网关或服务间调用的客户端)集成这样的熔断机制。例如,在使用 Spring Cloud Feign 进行服务调用时,可以通过自定义 Feign 的 Client 实现,在请求前调用 allowRequest 方法判断是否允许请求,在请求失败时调用 recordFailure 方法记录失败,在请求成功时调用 recordSuccess 方法记录成功。

降级策略

降级是指当系统整体资源不足或某个服务出现问题时,为了保证核心业务的可用性,主动降低部分非核心业务的功能或服务质量。例如,在电商系统高并发时,为了保证订单处理的核心功能,暂时关闭商品评论的展示功能,将资源优先分配给订单服务。

  1. 自动降级:根据系统的运行指标(如 CPU 使用率、内存使用率、请求响应时间等)自动触发降级。比如,当 CPU 使用率达到 90% 时,自动关闭一些非核心服务。
  2. 手动降级:通过运维人员手动操作,在发现系统出现潜在风险或某个服务异常时,主动进行降级。例如,当发现库存服务出现不稳定迹象时,手动将订单服务对库存服务的依赖降级为返回固定的库存充足的假数据,以保证订单服务的基本可用性。

不同场景下的熔断降级策略实践

电商场景

  1. 订单服务与库存服务:订单服务依赖库存服务检查商品库存。如果库存服务出现故障,订单服务可以通过熔断机制快速返回“库存检查失败,请稍后重试”的提示给用户,避免订单服务资源耗尽。同时,订单服务可以采用降级策略,在库存服务不可用时,根据历史数据或预设规则,判断部分热门商品是否可售,尽量保证部分订单的正常提交。例如,对于一些长期畅销且库存相对稳定的商品,即使库存服务不可用,也允许一定数量的订单提交,同时记录相关信息,待库存服务恢复后进行核对和调整。
  2. 支付服务与银行接口服务:支付服务依赖银行接口服务完成实际支付操作。当银行接口服务出现故障时,支付服务可以触发熔断,返回“支付处理中,请等待短信通知”的信息给用户,并将支付请求记录下来,定期重试。在重试一定次数后,如果仍然失败,可以采用降级策略,提供线下支付方式(如银行转账、货到付款等)给用户,保证电商交易的基本流程能够继续进行。

社交场景

  1. 动态展示服务与图片服务:在社交平台中,动态展示服务依赖图片服务加载用户发布的图片。如果图片服务出现故障,动态展示服务可以触发熔断,对图片部分进行模糊处理或显示“图片加载失败”的占位符,优先保证动态文本内容的正常展示。同时,可以采用降级策略,降低图片质量(如将高清图片转换为低分辨率图片)来减轻图片服务的压力,或者从缓存中加载图片(如果缓存中有最近访问过的图片),以提高动态展示的成功率。
  2. 消息推送服务与第三方推送平台:消息推送服务依赖第三方推送平台(如 Firebase Cloud Messaging、极光推送等)向用户设备推送消息。当第三方推送平台出现故障时,消息推送服务可以触发熔断,记录推送失败的消息和用户信息。然后采用降级策略,通过站内信的方式告知用户有新消息,保证用户不会错过重要信息,同时尝试切换到备用的推送平台(如果有),以恢复推送功能。

熔断降级策略的监控与优化

监控指标

  1. 熔断状态指标:实时监控熔断器的状态(关闭、打开、半开),了解服务的健康状况。例如,通过监控发现某个服务的熔断器频繁处于打开状态,说明该服务可能存在严重问题,需要及时排查。
  2. 请求成功率与失败率:跟踪服务请求的成功率和失败率,判断服务是否稳定。如果失败率持续上升,可能是服务本身出现故障,也可能是依赖的服务出现问题,需要进一步分析。
  3. 资源利用率指标:监控系统的 CPU、内存、网络等资源利用率,在资源紧张时,提前做好降级准备。比如,当 CPU 使用率接近 80% 时,考虑对一些非核心服务进行降级,避免系统因资源耗尽而崩溃。

优化策略

  1. 调整熔断阈值:根据服务的实际运行情况,合理调整熔断的失败率阈值和半开状态下的请求成功比例。如果阈值设置过低,可能会导致熔断器频繁打开,影响服务的正常使用;如果阈值设置过高,可能无法及时发现服务故障,引发雪崩效应。例如,对于一些对可用性要求极高的核心服务,可以适当提高失败率阈值(如 90%),因为短暂的少量失败可能不影响整体业务;而对于一些非核心服务,可以降低阈值(如 70%),以便更快地发现问题并进行处理。
  2. 优化降级方案:不断完善降级策略,确保在降级情况下,用户体验和核心业务功能不受太大影响。例如,在电商场景中,对于库存服务的降级,除了返回固定的库存信息外,可以结合用户的历史购买记录和商品热度,提供更精准的库存预估信息,提高用户满意度。
  3. 服务治理与修复:根据监控数据,及时发现服务故障的根源,对服务进行修复和优化。例如,如果发现某个服务的失败率高是因为代码中的数据库连接池配置不合理,导致频繁的数据库连接超时,就需要调整数据库连接池的参数,如增加最大连接数、设置合理的连接超时时间等,从根本上解决服务故障问题。

熔断降级策略与其他技术的结合

与服务限流的结合

服务限流是指限制单位时间内对某个服务的请求数量,防止服务因请求过多而过载。熔断降级策略与服务限流可以相互配合。例如,在电商促销活动期间,对订单服务的请求量可能会急剧增加。首先通过服务限流,将每秒的请求数量限制在订单服务能够承受的范围内,如每秒 1000 个请求。当请求量超过这个限制时,多余的请求直接返回“请求过多,请稍后重试”的提示。同时,结合熔断机制,如果订单服务依赖的库存服务出现故障,导致订单服务的请求失败率上升,当失败率达到熔断阈值时,触发熔断,进一步保护订单服务。这样,服务限流可以减轻服务的压力,降低熔断发生的概率,而熔断降级策略可以在服务出现故障时提供兜底保障。

与分布式缓存的结合

分布式缓存(如 Redis)可以存储经常访问的数据,减少对后端服务的直接请求。在熔断降级策略中,分布式缓存可以发挥重要作用。例如,当库存服务出现故障时,订单服务可以从 Redis 缓存中获取库存信息(前提是缓存中的数据是最新且可靠的)。同时,在降级情况下,对于一些需要展示的数据(如商品详情),可以从缓存中加载,避免因依赖的服务不可用而无法展示。此外,在熔断打开状态下,缓存还可以存储 fallback 响应,快速返回给请求方,提高响应速度。例如,将“库存检查失败,请稍后重试”的提示信息缓存起来,当熔断器打开时,直接从缓存中返回该信息,减少处理开销。

与分布式追踪的结合

分布式追踪(如 Jaeger、Zipkin 等)可以记录服务之间的调用关系和请求的生命周期,帮助定位服务故障的根源。在熔断降级策略中,分布式追踪可以提供详细的调用链信息,便于分析熔断和降级发生的原因。例如,当某个服务的熔断器打开时,可以通过分布式追踪查看该服务在故障发生前的所有调用链,了解是哪个依赖服务出现问题导致了失败率上升。同时,在优化降级方案时,分布式追踪可以提供请求在各个服务之间的流转时间和性能指标,帮助确定哪些服务是关键瓶颈,从而有针对性地进行优化。比如,通过分布式追踪发现图片服务在加载大尺寸图片时响应时间过长,导致动态展示服务的性能下降,就可以在降级策略中考虑对大尺寸图片进行预处理或转换为小尺寸图片,提高整体系统的性能。