设计模式与限流熔断:策略模式选择限流算法、状态模式管理熔断状态、责任链处理请求

说实话,微服务架构里最让人头疼的问题之一,就是怎么防止一个服务挂了把整个系统拖垮。我早年做电商系统时,就遇到过双十一流量把订单服务打崩,然后连锁反应导致所有服务都不可用——那场面,真是惨不忍睹。

后来我总结了一句话:限流是保命,熔断是断臂,降级是妥协。今天我们就聊聊怎么用设计模式把这套机制做得优雅、可扩展。

整体架构:三个模式各司其职

先看一张图,理清这三个模式的分工:

限流熔断架构:三种设计模式协同 客户端请求 责任链模式:请求处理管道 限流检查 → 熔断检查 → 降级处理 → 实际调用 策略模式:限流算法选择 令牌桶算法 漏桶算法 滑动窗口算法 计数器算法 状态模式:熔断状态管理 关闭 打开 半开 失败 超时 成功 三种模式组合:策略模式选算法 → 状态模式管熔断 → 责任链串联流程

说白了,这三个模式就像是一个流水线:责任链是传送带,策略模式是传送带上的不同工具头,状态模式是传送带的开关控制器。

策略模式:灵活切换限流算法

限流算法有很多种,每种都有适用场景。我习惯用策略模式把它们封装起来,这样切换算法就像换灯泡一样简单。

核心思路:定义一个限流策略接口,每种算法一个实现类,运行时动态选择。

// 限流策略接口
public interface RateLimitStrategy {
    boolean tryAcquire();
    String getName();
}

// 令牌桶算法实现
public class TokenBucketStrategy implements RateLimitStrategy {
    private final long capacity;      // 桶容量
    private final long refillRate;    // 每秒填充令牌数
    private long tokens;              // 当前令牌数
    private long lastRefillTime;      // 上次填充时间

    public TokenBucketStrategy(long capacity, long refillRate) {
        this.capacity = capacity;
        this.refillRate = refillRate;
        this.tokens = capacity;
        this.lastRefillTime = System.currentTimeMillis();
    }

    @Override
    public synchronized boolean tryAcquire() {
        refill();
        if (tokens > 0) {
            tokens--;
            return true;
        }
        return false;
    }

    private void refill() {
        long now = System.currentTimeMillis();
        long elapsed = now - lastRefillTime;
        long newTokens = elapsed * refillRate / 1000;
        if (newTokens > 0) {
            tokens = Math.min(capacity, tokens + newTokens);
            lastRefillTime = now;
        }
    }

    @Override
    public String getName() { return "TokenBucket"; }
}

// 滑动窗口算法实现
public class SlidingWindowStrategy implements RateLimitStrategy {
    private final long windowSize;    // 窗口大小(毫秒)
    private final long maxRequests;   // 窗口内最大请求数
    private final Queue<Long> timestamps = new LinkedList<>();

    public SlidingWindowStrategy(long windowSize, long maxRequests) {
        this.windowSize = windowSize;
        this.maxRequests = maxRequests;
    }

    @Override
    public synchronized boolean tryAcquire() {
        long now = System.currentTimeMillis();
        // 移除过期的时间戳
        while (!timestamps.isEmpty() && 
               now - timestamps.peek() > windowSize) {
            timestamps.poll();
        }
        if (timestamps.size() < maxRequests) {
            timestamps.offer(now);
            return true;
        }
        return false;
    }

    @Override
    public String getName() { return "SlidingWindow"; }
}

我的经验:令牌桶适合处理突发流量,漏桶适合平滑流量。我在做支付系统时,对账接口用令牌桶,用户支付接口用滑动窗口——因为支付场景对突发更敏感。

状态模式:管理熔断器的生命周期

熔断器有三个状态:关闭(正常)→ 打开(熔断)→ 半开(试探)。用状态模式来管理,状态转换逻辑清晰,不会乱。

// 熔断器上下文
public class CircuitBreaker {
    private CircuitBreakerState state;
    private final int failureThreshold;   // 失败阈值
    private final long timeout;           // 熔断超时时间
    private int failureCount;
    private long lastFailureTime;

    public CircuitBreaker(int failureThreshold, long timeout) {
        this.failureThreshold = failureThreshold;
        this.timeout = timeout;
        this.state = new ClosedState(this);
    }

    public void setState(CircuitBreakerState state) {
        this.state = state;
    }

    public boolean allowRequest() {
        return state.allowRequest();
    }

    public void recordSuccess() {
        state.recordSuccess();
    }

    public void recordFailure() {
        state.recordFailure();
    }

    // getter/setter 省略...
}

// 状态接口
public interface CircuitBreakerState {
    boolean allowRequest();
    void recordSuccess();
    void recordFailure();
}

// 关闭状态
public class ClosedState implements CircuitBreakerState {
    private final CircuitBreaker breaker;

    public ClosedState(CircuitBreaker breaker) {
        this.breaker = breaker;
    }

    @Override
    public boolean allowRequest() {
        return true;  // 正常放行
    }

    @Override
    public void recordSuccess() {
        breaker.setFailureCount(0);  // 成功则重置失败计数
    }

    @Override
    public void recordFailure() {
        breaker.setFailureCount(breaker.getFailureCount() + 1);
        if (breaker.getFailureCount() >= breaker.getFailureThreshold()) {
            breaker.setState(new OpenState(breaker));
            breaker.setLastFailureTime(System.currentTimeMillis());
            System.out.println("熔断器打开!");
        }
    }
}

// 打开状态
public class OpenState implements CircuitBreakerState {
    private final CircuitBreaker breaker;

    public OpenState(CircuitBreaker breaker) {
        this.breaker = breaker;
    }

    @Override
    public boolean allowRequest() {
        long now = System.currentTimeMillis();
        if (now - breaker.getLastFailureTime() > breaker.getTimeout()) {
            breaker.setState(new HalfOpenState(breaker));
            return true;  // 超时后放行一个请求试探
        }
        return false;  // 直接拒绝
    }

    @Override
    public void recordSuccess() { /* 熔断中不处理 */ }

    @Override
    public void recordFailure() { /* 熔断中不处理 */ }
}

// 半开状态
public class HalfOpenState implements CircuitBreakerState {
    private final CircuitBreaker breaker;

    public HalfOpenState(CircuitBreaker breaker) {
        this.breaker = breaker;
    }

    @Override
    public boolean allowRequest() {
        return true;  // 放行试探请求
    }

    @Override
    public void recordSuccess() {
        breaker.setState(new ClosedState(breaker));
        breaker.setFailureCount(0);
        System.out.println("熔断器恢复关闭!");
    }

    @Override
    public void recordFailure() {
        breaker.setState(new OpenState(breaker));
        breaker.setLastFailureTime(System.currentTimeMillis());
        System.out.println("试探失败,熔断器再次打开!");
    }
}

我曾经踩过的坑:半开状态下只放行一个请求是不够的。高并发场景下,一个请求成功不代表服务恢复了。我后来改成半开状态放行一定比例的请求(比如10%),连续成功N次才关闭。这个比例和次数需要根据业务调整。

责任链模式:串联整个处理流程

责任链模式把限流、熔断、降级、实际调用串起来。每个处理器只关心自己的事,处理完交给下一个。

// 处理器抽象
public abstract class RequestHandler {
    protected RequestHandler next;

    public void setNext(RequestHandler next) {
        this.next = next;
    }

    public abstract void handle(Request request);

    protected void passToNext(Request request) {
        if (next != null) {
            next.handle(request);
        }
    }
}

// 限流处理器
public class RateLimitHandler extends RequestHandler {
    private final RateLimitStrategy strategy;

    public RateLimitHandler(RateLimitStrategy strategy) {
        this.strategy = strategy;
    }

    @Override
    public void handle(Request request) {
        if (!strategy.tryAcquire()) {
            System.out.println("限流拒绝: " + request.getId());
            return;  // 直接返回,不继续传递
        }
        System.out.println("限流通过: " + strategy.getName());
        passToNext(request);
    }
}

// 熔断处理器
public class CircuitBreakerHandler extends RequestHandler {
    private final CircuitBreaker breaker;

    public CircuitBreakerHandler(CircuitBreaker breaker) {
        this.breaker = breaker;
    }

    @Override
    public void handle(Request request) {
        if (!breaker.allowRequest()) {
            System.out.println("熔断拒绝: " + request.getId());
            return;
        }
        try {
            passToNext(request);
            breaker.recordSuccess();  // 调用成功
        } catch (Exception e) {
            breaker.recordFailure();  // 调用失败
            throw e;
        }
    }
}

// 降级处理器
public class DegradeHandler extends RequestHandler {
    @Override
    public void handle(Request request) {
        try {
            passToNext(request);
        } catch (Exception e) {
            System.out.println("降级处理: " + request.getId());
            // 返回兜底数据或缓存数据
        }
    }
}

// 实际业务处理器
public class BusinessHandler extends RequestHandler {
    @Override
    public void handle(Request request) {
        System.out.println("执行业务: " + request.getId());
        // 实际业务逻辑...
    }
}

// 使用示例
public class Client {
    public static void main(String[] args) {
        // 1. 创建策略
        RateLimitStrategy strategy = new TokenBucketStrategy(100, 50);

        // 2. 创建熔断器
        CircuitBreaker breaker = new CircuitBreaker(5, 10000);

        // 3. 构建责任链
        RequestHandler rateLimit = new RateLimitHandler(strategy);
        RequestHandler circuitBreaker = new CircuitBreakerHandler(breaker);
        RequestHandler degrade = new DegradeHandler();
        RequestHandler business = new BusinessHandler();

        rateLimit.setNext(circuitBreaker);
        circuitBreaker.setNext(degrade);
        degrade.setNext(business);

        // 4. 处理请求
        for (int i = 0; i < 200; i++) {
            Request request = new Request("req-" + i);
            rateLimit.handle(request);
        }
    }
}

三种模式的协作关系

设计模式 职责 变化点 扩展方式
策略模式 选择限流算法 算法实现 新增策略实现类
状态模式 管理熔断状态 状态转换规则 新增状态实现类
责任链模式 串联处理流程 处理器顺序和数量 新增处理器实现类

核心要点:这三种模式组合起来,每个模式只解决一个问题。策略模式管「怎么限」,状态模式管「熔不熔」,责任链管「谁先谁后」。改动一个不影响其他,这就是设计模式的价值。

实际项目中的注意事项

嗯,这里有几个坑我得提醒你:

  • 限流和熔断的阈值要分开配置。我见过有人把限流阈值设得比熔断阈值还低,结果限流先触发,熔断永远用不上——这就不对了。限流是预防,熔断是兜底。
  • 责任链的顺序很重要。限流应该在熔断前面,因为限流成本低(只是计数),熔断需要记录失败次数。先做成本低的检查,能快速拒绝的就别往后传。
  • 状态模式的状态转换要考虑并发。熔断器的状态是全局共享的,多线程环境下要用锁或者原子变量。我习惯用 AtomicReference<CircuitBreakerState> 来保证线程安全。
  • 降级处理不要抛异常。降级本身就是异常处理的一部分,再抛异常就失去意义了。降级方法应该返回默认值、缓存数据或者空结果。

说实话,这套组合拳我用了好几年,从单体应用到微服务都验证过。你想想看,如果不用设计模式,把这些逻辑都写在一个类里——限流逻辑、熔断逻辑、降级逻辑、业务逻辑全混在一起——那代码维护起来得多痛苦?

设计模式不是炫技,是让代码有「弹性」。就像限流熔断本身是为了让系统有弹性一样,设计模式让代码结构也有弹性。这个道理,我做了十几年架构才真正想明白。

我的建议:刚开始别想着一次把所有模式都用上。先从责任链开始,把限流和熔断串起来。跑通了再加策略模式切换算法,最后加状态模式管理熔断状态。循序渐进,别贪多。