图 1:本文封面——流量洪峰面前,三板斧各守一段防线。
「如何设计一个高并发系统」是面试常客,那道题的解析主轴是整体分层架构,本文不重复展开;今天换个角度,聊真正决定系统死活的三件事:限流、熔断、降级。
结论先行:三板斧的分工是「限流守入口、熔断守出口、降级兜全局」。限流挡住超过处理能力的流量,熔断切断对不稳定下游的调用,降级在资源紧张时牺牲次要功能保住核心链路。三者共用同一套指标与预案,构成完整的自我保护体系。
限流:入口的闸门,先讲算法。
限流的本质是回答「单位时间放多少请求进来」。四种主流算法各有性格:固定窗口计数最简单,但窗口边界会放进两倍流量;滑动窗口把统计区间切小格,摊平边界毛刺;漏桶按恒定速率出水,流量被强行整形,代价是突发被抹平;令牌桶按速率发令牌、允许积攒,桶内有余量时突发可以瞬间通过——既限速又容忍突发,是工程上最常用的形态。
以开源组件 Sentinel 的限流规则为参照:阈值类型分 QPS 与并发线程数两种,控制效果有直接拒绝、Warm Up(冷启动缓慢放量)、匀速排队(对应漏桶)三种。
熔断:出口的分界线,盯下游健康。
熔断解决另一个方向的问题:不是你被流量打垮,而是你的下游病了。下游变慢、错误率升高,你的线程堆积在等待上,最终把自己拖死,故障顺着调用链向上传导,这就是雪崩。熔断器是调用端的自我保护:监控下游调用的失败率,越过阈值就断开调用,请求直接快速失败。
熔断器是一个三态状态机。闭合状态正常放行并统计失败情况;失败率(或慢调用比例)超阈值跳到打开状态,请求快速失败;经过冷却窗口进入半开状态,放少量探针试探:成功回闭合,失败重新打开。以 Sentinel 的熔断规则为参照:策略有慢调用比例、异常比例、异常数三种,触发要求统计时长内请求数先超过最小样本量——样本不够即使全挂也不熔断,避免零星请求制造假警报。
降级:全局的取舍,保住核心。
降级是产品经理也得点头的那一个:资源不够时主动关掉次要功能,把容量让给核心链路。常见动作包括返回缓存旧数据、兜底静态内容、关闭非核心入口。降级的难点不在技术而在预案——哪些功能可降级、降级成什么样、谁拨开关,必须提前写成预案并演练。
三板斧怎么用代码落地?
下面这份代码实现了一个令牌桶和一个三态熔断器,实测洪峰拦截与「下游故障—熔断—恢复」的过程。用 Node.js 22.15.0 直接运行(node protection.js)即可:
'use strict';
// 令牌桶限流器:按速率发令牌,拿到令牌才放行
class TokenBucket {
constructor(ratePerSec, capacity) {
this.rate = ratePerSec;
this.capacity = capacity;
this.tokens = capacity;
this.lastRefill = Date.now();
}
tryAcquire() {
const now = Date.now();
this.tokens = Math.min(this.capacity, this.tokens + ((now - this.lastRefill) / 1000) * this.rate);
this.lastRefill = now;
if (this.tokens >= 1) { this.tokens -= 1; return true; }
return false;
}
}
// 熔断器:闭合 -> 打开 -> 半开 三态
class CircuitBreaker {
constructor({ failureThreshold = 0.5, minRequests = 4, cooldownMs = 100 } = {}) {
this.state = 'CLOSED';
this.window = [];
this.cooldownMs = cooldownMs;
this.failureThreshold = failureThreshold;
this.minRequests = minRequests;
this.openedAt = 0;
}
call(fn, fallback) {
if (this.state === 'OPEN') {
if (Date.now() - this.openedAt < this.cooldownMs) return fallback('熔断中,快速失败');
this.state = 'HALF_OPEN'; // 冷却结束,放探针
}
try {
const result = fn();
this.record(true);
if (this.state === 'HALF_OPEN') this.state = 'CLOSED'; // 探针成功
return result;
} catch (err) {
this.record(false);
if (this.state === 'HALF_OPEN') { this.trip(); } // 探针失败,重新熔断
return fallback(err.message);
}
}
record(ok) {
this.window.push(ok);
if (this.window.length > this.minRequests) this.window.shift();
if (this.window.length >= this.minRequests) {
const failures = this.window.filter((x) => !x).length;
if (failures / this.window.length >= this.failureThreshold) this.trip();
}
}
trip() { this.state = 'OPEN'; this.openedAt = Date.now(); this.window = []; }
}
// 演示 1:每秒 2 个令牌、容量 2,瞬间打 6 个请求
const bucket = new TokenBucket(2, 2);
const results = [];
for (let i = 1; i <= 6; i++) results.push(bucket.tryAcquire() ? '放行' : '拒绝');
console.log(`令牌桶(速率2/秒,容量2)连续 6 个请求:${results.join('、')}`);
// 演示 2:下游 4 次连续故障触发熔断,冷却后探针恢复
const breaker = new CircuitBreaker({ failureThreshold: 0.5, minRequests: 4, cooldownMs: 50 });
let downstreamHealthy = false;
const downstream = () => {
if (!downstreamHealthy) throw new Error('下游超时');
return '下游正常返回';
};
const fallback = (why) => `降级响应(${why})`;
for (let i = 1; i <= 4; i++) {
console.log(`第 ${i} 次调用:${breaker.call(downstream, fallback)},熔断器=${breaker.state}`);
}
console.log(`熔断后立刻再调:${breaker.call(downstream, fallback)},熔断器=${breaker.state}`);
await new Promise((r) => setTimeout(r, 60)); // 等过冷却窗口
downstreamHealthy = true;
console.log(`冷却后探针调用:${breaker.call(downstream, fallback)},熔断器=${breaker.state}`);
console.log(`恢复后再调:${breaker.call(downstream, fallback)},熔断器=${breaker.state}`);
实测输出:
令牌桶(速率2/秒,容量2)连续 6 个请求:放行、放行、拒绝、拒绝、拒绝、拒绝
第 1 次调用:降级响应(下游超时),熔断器=CLOSED
第 2 次调用:降级响应(下游超时),熔断器=CLOSED
第 3 次调用:降级响应(下游超时),熔断器=CLOSED
第 4 次调用:降级响应(下游超时),熔断器=OPEN
熔断后立刻再调:降级响应(熔断中,快速失败),熔断器=OPEN
冷却后探针调用:下游正常返回,熔断器=CLOSED
恢复后再调:下游正常返回,熔断器=CLOSED
两段输出对应两个核心行为:令牌桶容量耗尽后立即拒绝,守住入口水位;熔断器攒够四次失败样本才打开,冷却期内请求不再打到下游,冷却后一个探针完成恢复。注意熔断打开期间走的就是降级路径(fallback 兜底响应)——熔断负责发现「该降级了」,降级负责回答「降级成什么样」。
图 2:三板斧的分工与熔断器状态机全貌。
常见误区有三个。 一是熔断阈值过于敏感,零星抖动就熔断——所以成熟组件都带最小样本量门槛。二是降级没有预案,洪峰时临时讨论「关哪个功能」,错过最佳窗口。三是限流阈值拍脑袋:不压测直接写个整数,要么没拦住、要么误杀正常流量,阈值应来自压测水位再打折扣。
行动建议:给核心接口压测定水位,限流阈值按水位打折;给所有下游调用挂熔断与兜底;把可降级功能列成清单并预演。你的系统里三板斧配齐了吗,降级预案演练过吗?欢迎聊聊。