限流熔断策略实现:从单点防护到级联保护,高可用架构的流量安全网
限流熔断策略实现从单点防护到级联保护高可用架构的流量安全网一、流量洪峰下的级联崩溃当限流缺失引发雪崩高并发系统中单个服务的过载可能引发级联故障。上游服务 A 调用下游服务 BB 响应变慢导致 A 的线程池被占满A 也开始拒绝请求依赖 A 的服务 C 随之不可用。这种雪崩效应在微服务架构中尤为严重——服务间的调用链越长级联故障的影响范围越大。限流和熔断是防止级联故障的两道防线。限流控制进入服务的请求速率防止过载熔断在服务异常时快速失败避免故障扩散。但两者的实现并不简单——限流阈值如何动态调整熔断器如何判断服务是否恢复分布式环境下如何保证限流的一致性二、限流与熔断的架构设计限流和熔断需要协同工作限流在入口处控制流量熔断在出口处保护调用链。两者共享指标数据形成闭环的流量防护体系。flowchart TD A[请求入口] -- B[限流器] B -- B1[令牌桶: 控制平均速率] B -- B2[滑动窗口: 控制瞬时速率] B -- B3[分布式限流: Redis 集群协调] B1 -- C{限流判断} B2 -- C B3 -- C C --|通过| D[业务处理] C --|拒绝| E[限流响应: 429] D -- F[下游调用] F -- G[熔断器] G -- G1[关闭状态: 正常调用] G -- G2[打开状态: 快速失败] G -- G3[半开状态: 试探恢复] G1 -- H{调用是否成功?} H --|成功| I[更新成功计数] H --|失败| J[更新失败计数] J -- K{失败率超阈值?} K --|是| G2 K --|否| G1 G2 -- L[冷却期后进入半开] L -- G3 G3 -- M{试探请求成功?} M --|成功| G1 M --|失败| G2 style B fill:#e8f5e9 style G fill:#fff3e02.1 令牌桶限流器// TokenBucketLimiter.java — 令牌桶限流器 // 设计意图控制请求的平均速率允许短时突发 // 适用于 API 限流场景令牌补充速率恒定 import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; public class TokenBucketLimiter { private final long capacity; // 桶容量最大令牌数 private final double refillRate; // 令牌补充速率个/毫秒 private final AtomicLong tokens; // 当前令牌数 private final AtomicReferenceLong lastRefillTime; public TokenBucketLimiter(long capacity, double permitsPerSecond) { this.capacity capacity; this.refillRate permitsPerSecond / 1000.0; this.tokens new AtomicLong(capacity); this.lastRefillTime new AtomicReference(System.currentTimeMillis()); } public boolean tryAcquire() { return tryAcquire(1); } public boolean tryAcquire(long requestedTokens) { long currentTime System.currentTimeMillis(); // 补充令牌 refill(currentTime); // 尝试消费令牌CAS 保证线程安全 while (true) { long currentTokens tokens.get(); if (currentTokens requestedTokens) { return false; // 令牌不足限流 } if (tokens.compareAndSet(currentTokens, currentTokens - requestedTokens)) { return true; // 消费成功 } // CAS 失败重试 } } private void refill(long currentTime) { while (true) { long lastTime lastRefillTime.get(); long elapsedMs currentTime - lastTime; if (elapsedMs 0) return; // 计算应补充的令牌数 long tokensToAdd (long) (elapsedMs * refillRate); if (tokensToAdd 0) return; long newTokens Math.min(capacity, tokens.get() tokensToAdd); // CAS 更新补充时间和令牌数 if (lastRefillTime.compareAndSet(lastTime, currentTime)) { tokens.set(newTokens); return; } } } public long getAvailableTokens() { refill(System.currentTimeMillis()); return tokens.get(); } }2.2 滑动窗口限流器// SlidingWindowLimiter.java — 滑动窗口限流器 // 设计意图精确控制时间窗口内的请求总数 // 相比固定窗口避免了窗口边界的突发问题 import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.atomic.AtomicInteger; public class SlidingWindowLimiter { private final long windowSizeMs; // 窗口大小毫秒 private final int maxRequests; // 窗口内最大请求数 private final ConcurrentLinkedQueueLong timestamps; private final AtomicInteger currentCount; public SlidingWindowLimiter(long windowSizeMs, int maxRequests) { this.windowSizeMs windowSizeMs; this.maxRequests maxRequests; this.timestamps new ConcurrentLinkedQueue(); this.currentCount new AtomicInteger(0); } public boolean tryAcquire() { long now System.currentTimeMillis(); long windowStart now - windowSizeMs; // 清理过期的时间戳 while (true) { Long oldest timestamps.peek(); if (oldest null || oldest windowStart) { break; } if (timestamps.poll() ! null) { currentCount.decrementAndGet(); } } // 检查是否超过限制 if (currentCount.get() maxRequests) { return false; } // 记录当前请求的时间戳 timestamps.add(now); currentCount.incrementAndGet(); return true; } public int getCurrentCount() { // 先清理过期数据再返回计数 long windowStart System.currentTimeMillis() - windowSizeMs; while (true) { Long oldest timestamps.peek(); if (oldest null || oldest windowStart) break; if (timestamps.poll() ! null) currentCount.decrementAndGet(); } return currentCount.get(); } }三、熔断器实现3.1 三状态熔断器// CircuitBreaker.java — 三状态熔断器 // 设计意图在下游服务异常时快速失败避免故障扩散 // 通过半开状态试探恢复防止服务恢复后流量瞬间涌入再次过载 import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; public class CircuitBreaker { public enum State { CLOSED, // 关闭正常调用 OPEN, // 打开快速失败 HALF_OPEN // 半开试探恢复 } private final String name; private final AtomicReferenceState state; private final AtomicInteger successCount; private final AtomicInteger failureCount; private final AtomicInteger halfOpenSuccessCount; private final int failureThreshold; // 失败次数阈值 private final double failureRateThreshold; // 失败率阈值 private final int minRequests; // 最小请求数统计才有意义 private final long openTimeoutMs; // 打开状态持续时间 private final int halfOpenMaxRequests; // 半开状态最大试探请求数 private volatile long lastFailureTime; private volatile long openedAt; public CircuitBreaker(String name, CircuitBreakerConfig config) { this.name name; this.state new AtomicReference(State.CLOSED); this.successCount new AtomicInteger(0); this.failureCount new AtomicInteger(0); this.halfOpenSuccessCount new AtomicInteger(0); this.failureThreshold config.getFailureThreshold(); this.failureRateThreshold config.getFailureRateThreshold(); this.minRequests config.getMinRequests(); this.openTimeoutMs config.getOpenTimeoutMs(); this.halfOpenMaxRequests config.getHalfOpenMaxRequests(); } public boolean allowRequest() { State currentState state.get(); switch (currentState) { case CLOSED: return true; case OPEN: // 检查冷却期是否结束 if (System.currentTimeMillis() - openedAt openTimeoutMs) { // 尝试转换为半开状态 if (state.compareAndSet(State.OPEN, State.HALF_OPEN)) { halfOpenSuccessCount.set(0); return true; } } return false; case HALF_OPEN: // 限制试探请求数量 return halfOpenSuccessCount.get() halfOpenMaxRequests; default: return false; } } public void recordSuccess() { State currentState state.get(); switch (currentState) { case CLOSED: successCount.incrementAndGet(); break; case HALF_OPEN: int count halfOpenSuccessCount.incrementAndGet(); // 试探请求全部成功关闭熔断器 if (count halfOpenMaxRequests) { if (state.compareAndSet(State.HALF_OPEN, State.CLOSED)) { resetCounters(); } } break; default: break; } } public void recordFailure() { State currentState state.get(); lastFailureTime System.currentTimeMillis(); switch (currentState) { case CLOSED: failureCount.incrementAndGet(); // 检查是否达到熔断条件 if (shouldOpen()) { if (state.compareAndSet(State.CLOSED, State.OPEN)) { openedAt System.currentTimeMillis(); } } break; case HALF_OPEN: // 试探失败重新打开熔断器 if (state.compareAndSet(State.HALF_OPEN, State.OPEN)) { openedAt System.currentTimeMillis(); } break; default: break; } } private boolean shouldOpen() { int total successCount.get() failureCount.get(); // 请求数不足不判断 if (total minRequests) return false; // 检查失败次数阈值 if (failureCount.get() failureThreshold) return true; // 检查失败率阈值 double failureRate (double) failureCount.get() / total; return failureRate failureRateThreshold; } private void resetCounters() { successCount.set(0); failureCount.set(0); } public State getState() { return state.get(); } public String getName() { return name; } }3.2 熔断器配置// CircuitBreakerConfig.java — 熔断器配置 // 设计意图不同服务对故障的容忍度不同 // 需要为每个下游服务配置独立的熔断参数 public class CircuitBreakerConfig { private int failureThreshold 10; // 连续失败 10 次触发熔断 private double failureRateThreshold 0.5; // 失败率 50% 触发熔断 private int minRequests 20; // 至少 20 次请求才统计 private long openTimeoutMs 30000; // 打开状态持续 30 秒 private int halfOpenMaxRequests 3; // 半开状态放行 3 个试探请求 // 静态工厂方法快速创建常见配置 public static CircuitBreakerConfig strict() { return new CircuitBreakerConfig() .setFailureThreshold(5) .setFailureRateThreshold(0.3) .setMinRequests(10) .setOpenTimeoutMs(60000); } public static CircuitBreakerConfig lenient() { return new CircuitBreakerConfig() .setFailureThreshold(20) .setFailureRateThreshold(0.7) .setMinRequests(50) .setOpenTimeoutMs(10000); } // getter 和 setter省略部分 public int getFailureThreshold() { return failureThreshold; } public double getFailureRateThreshold() { return failureRateThreshold; } public int getMinRequests() { return minRequests; } public long getOpenTimeoutMs() { return openTimeoutMs; } public int getHalfOpenMaxRequests() { return halfOpenMaxRequests; } public CircuitBreakerConfig setFailureThreshold(int v) { failureThreshold v; return this; } public CircuitBreakerConfig setFailureRateThreshold(double v) { failureRateThreshold v; return this; } public CircuitBreakerConfig setMinRequests(int v) { minRequests v; return this; } public CircuitBreakerConfig setOpenTimeoutMs(long v) { openTimeoutMs v; return this; } public CircuitBreakerConfig setHalfOpenMaxRequests(int v) { halfOpenMaxRequests v; return this; } }四、边界分析与架构权衡分布式限流的一致性单机限流器无法在多实例间协调配额。Redis 分布式限流可以解决但引入了外部依赖和额外延迟。权衡方案是对精度要求不高的场景使用本地限流每实例分配 1/N 配额对精度要求高的场景使用 Redis 限流。熔断器的误触发网络抖动可能导致短时失败率升高触发误熔断。解决方案是设置最小请求数阈值和滑动窗口统计避免少量请求就触发熔断。同时熔断器的打开超时不宜过短给下游服务恢复的时间。半开状态的流量冲击半开状态放行的试探请求如果全部成功熔断器关闭后大量积压请求可能瞬间涌入下游导致二次过载。建议在半开状态逐步增加放行比例而非一次性全部放行。限流与熔断的协调限流是入口防护熔断是出口防护。如果限流阈值设置过高流量会先触发下游熔断如果限流阈值设置过低正常流量被拒绝。两者需要基于容量测试数据协同调整。五、总结限流和熔断是高可用架构的流量安全网限流在入口控制流量速率熔断在出口防止故障扩散。令牌桶控制平均速率滑动窗口控制瞬时速率三状态熔断器实现快速失败与自动恢复。落地建议限流器优先使用本地实现多实例场景按配额分配熔断器为每个下游服务独立配置核心服务使用严格策略非核心服务使用宽松策略半开状态逐步放行试探请求避免恢复瞬间的流量冲击基于容量测试数据协同调整限流阈值和熔断参数。