断路器模式:微服务容错的核心防线

断路器模式,说白了就是给系统装上一个「保险丝」。

我在微服务架构里摸爬滚打这些年,最怕的就是一个服务挂了,然后像多米诺骨牌一样,把整个系统拖垮。断路器就是用来解决这个问题的。它监控服务调用的失败率,一旦达到阈值,就「跳闸」——直接拒绝后续请求,而不是让它们继续去撞南墙。

断路器的三种状态

断路器有三个状态:关闭(Closed)打开(Open)半开(Half-Open)。理解这三个状态,你就掌握了断路器的一半。

  • 关闭(Closed):正常状态。请求正常通过,但断路器会统计失败次数。一旦失败率达到阈值,就切换到打开状态。
  • 打开(Open):熔断状态。所有请求直接返回错误,不调用下游服务。等待一个超时时间后,进入半开状态。
  • 半开(Half-Open):试探状态。允许少量请求通过,如果成功,说明服务恢复了,回到关闭状态;如果失败,回到打开状态。

核心要点:关闭是正常,打开是保护,半开是试探。这三个状态构成了一个有限状态机,循环往复。

熔断策略:什么时候跳闸?

熔断策略不是拍脑袋定的。我个人习惯用三个参数来控制:

参数 说明 典型值
失败阈值 连续失败次数或失败率 5次 或 50%
时间窗口 统计失败率的时间范围 10秒
超时时间 打开状态持续多久后进入半开 30秒

举个例子:在10秒内,如果请求失败率达到50%,断路器就打开。然后等30秒,再放一个请求过去试试水。

注意:阈值设得太低容易误触发,设得太高又起不到保护作用。我曾经在一个高并发项目里把失败阈值设成3次,结果一次网络抖动就让断路器频繁跳闸,反而影响了正常业务。后来改成基于滑动窗口的失败率统计,才稳定下来。

恢复机制:如何优雅地恢复?

恢复机制的核心就是「半开状态」的试探逻辑。这里有个细节:

  • 半开状态下,只放行一个请求(或者少量请求)。
  • 如果这个请求成功,断路器认为服务恢复了,重置计数器,回到关闭状态。
  • 如果失败,立即回到打开状态,重新计时。

为什么要只放一个?你想想看,如果服务其实还没恢复,放进去一堆请求,那不是又把它打垮了吗?

我的经验:半开状态的试探请求最好用「健康检查」接口,而不是业务接口。因为业务接口可能因为数据问题失败,但服务本身是健康的。我曾经踩过这个坑——业务接口偶尔超时,导致断路器反复打开/关闭,后来改成专门的健康检查接口就稳了。

微服务中的容错实践

在微服务架构里,断路器通常和重试超时限流配合使用。它们的关系是这样的:

  • 超时:防止请求卡死。
  • 重试:处理临时故障。
  • 限流:控制请求速率。
  • 断路器:防止雪崩。

我一般建议的顺序是:先超时,再重试(最多1-2次),如果还失败,就触发断路器。断路器打开后,不要再重试,直接返回降级结果。

代码示例:Java 实现

下面是一个简单的断路器实现,用 Java 写的。核心逻辑都在 CircuitBreaker 类里。

public class CircuitBreaker {
    private State state = State.CLOSED;
    private int failureCount = 0;
    private final int failureThreshold = 5;
    private final long timeout = 30000; // 30秒
    private long lastFailureTime;

    public enum State { CLOSED, OPEN, HALF_OPEN }

    public synchronized boolean allowRequest() {
        switch (state) {
            case CLOSED:
                return true;
            case OPEN:
                if (System.currentTimeMillis() - lastFailureTime > timeout) {
                    state = State.HALF_OPEN;
                    return true;
                }
                return false;
            case HALF_OPEN:
                return true;
        }
        return false;
    }

    public synchronized void recordSuccess() {
        if (state == State.HALF_OPEN) {
            state = State.CLOSED;
            failureCount = 0;
        }
    }

    public synchronized void recordFailure() {
        failureCount++;
        lastFailureTime = System.currentTimeMillis();
        if (failureCount >= failureThreshold) {
            state = State.OPEN;
        }
    }
}

这段代码虽然简单,但已经包含了断路器的核心逻辑。实际项目中,你还需要考虑线程安全、滑动窗口统计、异步回调等。

代码示例:C++ 实现

C++ 版本更注重性能和资源管理。我用 std::atomic 来保证状态切换的原子性。

#include <atomic>
#include <chrono>

class CircuitBreaker {
public:
    enum class State { CLOSED, OPEN, HALF_OPEN };

    bool allowRequest() {
        State s = state.load();
        if (s == State::CLOSED) return true;
        if (s == State::OPEN) {
            auto now = std::chrono::steady_clock::now();
            if (now - lastFailureTime > timeout) {
                state.compare_exchange_strong(s, State::HALF_OPEN);
                return true;
            }
            return false;
        }
        // HALF_OPEN
        return true;
    }

    void recordSuccess() {
        State expected = State::HALF_OPEN;
        if (state.compare_exchange_strong(expected, State::CLOSED)) {
            failureCount = 0;
        }
    }

    void recordFailure() {
        failureCount++;
        lastFailureTime = std::chrono::steady_clock::now();
        if (failureCount >= failureThreshold) {
            state.store(State::OPEN);
        }
    }

private:
    std::atomic<State> state{State::CLOSED};
    std::atomic<int> failureCount{0};
    const int failureThreshold = 5;
    const std::chrono::seconds timeout{30};
    std::chrono::steady_clock::time_point lastFailureTime;
};

C++ 版本里用了 compare_exchange_strong,这是无锁编程的常用手法。嗯,这里要注意:半开状态下的并发控制要小心,多个线程同时进入半开状态可能导致重复试探。

断路器状态机流程图

下面这张图展示了断路器的状态流转,我画得比较直观。

关闭 (Closed) 请求正常通过 打开 (Open) 请求直接拒绝 半开 (Half-Open) 试探性放行 失败达到阈值 超时后自动进入 试探成功 试探失败

这张图里,三个状态之间的转换条件很清楚。你想想看,如果没有半开状态,服务恢复后断路器怎么知道?只能靠人工干预,那就太被动了。

避坑指南

最后分享几个我踩过的坑:

  • 不要和重试混用:断路器打开后,不要再重试。我曾经见过一个系统,断路器打开了,但重试逻辑还在拼命发请求,结果下游服务被活活打死。
  • 超时时间要合理:半开状态的超时时间不能太短,否则服务还没完全恢复就被再次熔断。也不能太长,否则影响恢复速度。我一般设为30-60秒。
  • 监控要到位:断路器状态变化一定要有日志和告警。我曾经因为没加日志,一个断路器默默熔断了两个小时,业务方反馈了才知道。

总结一下:断路器模式是微服务容错的最后一道防线。它用三个状态、一个熔断策略、一个恢复机制,帮我们挡住了雪崩。实际使用时,记得结合超时、重试、限流一起用,效果更好。

好了,这一章就到这里。代码示例虽然简单,但核心思想都在里面了。你在项目里用的时候,可以根据实际情况调整参数和策略。