设计模式与限流熔断:策略模式选择限流算法、状态模式管理熔断状态、责任链处理请求
说实话,微服务架构里最让人头疼的问题之一,就是怎么防止一个服务挂了把整个系统拖垮。我早年做电商系统时,就遇到过双十一流量把订单服务打崩,然后连锁反应导致所有服务都不可用——那场面,真是惨不忍睹。
后来我总结了一句话:限流是保命,熔断是断臂,降级是妥协。今天我们就聊聊怎么用设计模式把这套机制做得优雅、可扩展。
整体架构:三个模式各司其职
先看一张图,理清这三个模式的分工:
说白了,这三个模式就像是一个流水线:责任链是传送带,策略模式是传送带上的不同工具头,状态模式是传送带的开关控制器。
策略模式:灵活切换限流算法
限流算法有很多种,每种都有适用场景。我习惯用策略模式把它们封装起来,这样切换算法就像换灯泡一样简单。
核心思路:定义一个限流策略接口,每种算法一个实现类,运行时动态选择。
// 限流策略接口
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>来保证线程安全。 - 降级处理不要抛异常。降级本身就是异常处理的一部分,再抛异常就失去意义了。降级方法应该返回默认值、缓存数据或者空结果。
说实话,这套组合拳我用了好几年,从单体应用到微服务都验证过。你想想看,如果不用设计模式,把这些逻辑都写在一个类里——限流逻辑、熔断逻辑、降级逻辑、业务逻辑全混在一起——那代码维护起来得多痛苦?
设计模式不是炫技,是让代码有「弹性」。就像限流熔断本身是为了让系统有弹性一样,设计模式让代码结构也有弹性。这个道理,我做了十几年架构才真正想明白。
我的建议:刚开始别想着一次把所有模式都用上。先从责任链开始,把限流和熔断串起来。跑通了再加策略模式切换算法,最后加状态模式管理熔断状态。循序渐进,别贪多。