并发与限流(系统稳定性)
并发与限流(系统稳定性)
🛡️ 并发与限流(系统稳定性)面试回答
面试官您好,关于并发与限流在系统稳定性中的原理和应用,我结合核心原理、经典算法、工程落地和实战经验这几块来回答哈👇
核心本质:限流是系统稳定性的第一道防线
并发指系统同时处理请求的承载能力,而限流是主动对请求速率 / 并发数做上限管控的防护手段,目的是在流量洪峰时,保证核心服务不被打垮、不出现雪崩,属于 “丢车保帅” 的稳定性策略。
📌 四大经典限流算法(核心考点)
面试高频考察原理、优缺点和场景选型,我按落地频率排序:
1.固定窗口计数器
原理:按时间窗口(比如 1 秒)统计请求数,超过阈值就拒绝。
痛点:窗口边界会出现 “突刺问题”,比如 0.9-1.0 秒和 1.0-1.1 秒各打满阈值,瞬间流量是阈值的 2 倍。
适用:粗粒度、对精度要求不高的场景。
2.滑动窗口计数器
原理:把大时间窗口拆成多个小格子,随时间滑动统计,解决固定窗口的边界突刺问题。
特点:精度越高,拆分的格子越多,内存开销越大。
适用:接口级精准 QPS 限制。
3.漏桶算法
原理:请求像水一样注入漏桶,桶以固定速率流出处理;桶满了就直接拒绝。
特点:强制匀速处理,完全不支持突发流量,核心作用是 “流量整形”。
适用:消息队列削峰填谷、第三方接口调用限速。
4.令牌桶算法
原理:按固定速率往桶里放令牌,请求进来先拿令牌,拿到才能执行;桶满了令牌不再新增。
特点:支持突发流量(桶里存的令牌可以一次性用完),同时也能限制平均速率。
适用:绝大多数业务接口限流,也是工业界最常用的方案。
| 算法类型 | 实现难度 | 突发流量支持 | 时间精度 | 典型适用场景 |
|---|---|---|---|---|
| 固定窗口计数器 | 极低 | ❌ 边界突刺严重 | 低 | 简单粗粒度限流 |
| 滑动窗口计数器 | 中等 | ⭕ 基本解决突刺 | 中 | 接口精准 QPS 限制 |
| 漏桶算法 | 中等 | ❌ 强制匀速 | 高 | 流量整形、削峰填谷 |
| 令牌桶算法 | 中等 | ✅ 支持可控突发 | 高 | 业务接口通用限流 |
⚡ Java 工程落地实现
分单机限流和分布式限流两个层级:
1. 单机限流:Guava RateLimiter(令牌桶实现)
Java 生态最常用的单机限流工具,开箱即用,支持平滑突发、预热等特性:
// 初始化:每秒生成100个令牌(即限制QPS=100)
RateLimiter rateLimiter = RateLimiter.create(100);
public void handleRequest() {
// 非阻塞尝试获取1个令牌,获取失败直接降级
if (rateLimiter.tryAcquire()) {
// 正常执行业务逻辑
doBusiness();
} else {
// 限流兜底:返回友好提示或降级数据
throw new BizException("系统繁忙,请稍后再试");
}
}补充:如果是限制并发线程数而非 QPS,用JUC的Semaphore信号量即可。
2. 分布式限流:集群统一管控
多实例场景下单机限流无效,主流方案有两种:
- Redis + Lua 脚本:利用 Redis 单线程特性 + Lua 原子执行,实现滑动窗口 / 令牌桶,是中小厂最常用的自研方案
- 组件级方案:网关层用 Nginx 限流、服务层用 Alibaba Sentinel,支持热点参数限流、集群限流、可视化控制台
💡 限流不是孤立的:和高可用体系的联动
限流只是稳定性体系的一环,真实项目中是多层防护配合:
面试加分项:实战踩坑经验
- 多级限流才靠谱:只在服务层限流等于 “裸奔”,一定要在网关层做第一道全局限流,拦截大部分无效流量
- 阈值不能拍脑袋:必须基于压测结果 + 线上水位动态调整,同时预留 20%-30% 的冗余量
- 区分读写限流:写接口阈值更严格,读接口可适当放宽;核心接口和非核心接口要分级限流
- 拒绝要有礼貌:不能直接抛 500 错误,要返回业务友好提示,或者走排队、降级兜底逻辑
真实面试模拟
真实面试模拟
面试官 👨💼:
你好,看到你简历上写了“主导智能客服推理服务的稳定性建设”。那我们聊聊 AI 原理与应用里的并发与限流吧。先问个基础的:你觉得 AI 推理服务在做并发控制时,和传统 Web 服务有什么不同?为什么不能简单照搬那一套?
候选人 🤓:
面试官好。我的体会是,AI 推理是个昂贵的稀缺资源。传统 CRUD 请求是毫秒级,线程池随便上千;但 AI 推理一个请求动辄几百毫秒甚至几秒,而且它吃的是 GPU 显存,模型能并行的路数非常有限。一旦流量冲进来,传统服务可能只是响应变慢,AI 服务很可能直接 OOM 崩溃,恢复还特别慢。所以必须用限流来充当“保护阀”,不能硬扛。😣
面试官 👨💼:
理解挺到位。那你在项目里是怎么落地的?能画一下整体架构吗?
候选人 🤓:
可以,我画个很土的漏斗图吧,核心是三层防护 💪:
用户请求
│
▼
┌─────────────┐
│ 网关限流层 │ ← 令牌桶,粗暴限 QPS,快速拒绝
└──────┬──────┘
▼
┌─────────────┐
│ 业务并发控制 │ ← 信号量 + 有界队列,控制同时在途数
└──────┬──────┘
▼
┌─────────────┐
│ GPU推理池 │ ← 最多并行4路,动态批处理
└─────────────┘我第一层用 Guava RateLimiter 或 Sentinel,给 AI 接口单独限 50 QPS。第二层用 Semaphore 限制同时在进行中的推理请求为 4 个,因为我们的 GPU 实测并行 4 路是吞吐和时延的甜点。第三层模型引擎自带队列,我们再压测找到背压点,堵住上游。
面试官 👨💼:
第一层为什么选令牌桶,而不是漏桶或者滑动窗口?有什么考量?
候选人 🤓:
因为 AI 请求耗时波动大,漏桶会强行平滑输出,导致请求在队列里排长队,最后全都超时报错,体验很差。令牌桶允许一定的突发(只要桶里有牌就能马上处理),同时又限制了平均速率,这样我们能实现“快速失败”:没拿到令牌的直接返回“服务繁忙,请稍后”,用户反而能接受。滑动窗口更精准,但实现重,一般直接用成熟的中间件就行。👌
面试官 👨💼:
那光有 QPS 限流就够吗?刚刚你提到还有信号量,它解决什么问题?
候选人 🤓:
不够!因为 QPS 限的是入口速率,但请求耗时差异大。比如 50 QPS 里突然来了 40 个长文本生成请求,每个要跑 3 秒,系统一样被打垮。所以我加了一个 Semaphore 信号量,限制同时占用的推理槽位。像这样:
Semaphore gpuSlots = new Semaphore(4);
if (!gpuSlots.tryAcquire(300, TimeUnit.MILLISECONDS)) {
return fallbackResponse; // 直接降级
}
try {
return modelInference(input);
} finally {
gpuSlots.release();
}再配合有界线程池 + CallerRunsPolicy,队列最多排 100 个,超了就由调用线程自己执行降级逻辑。这样既保护 GPU,又防止内存堆积,非常丝滑。🧈
面试官 👨💼:
挺周全的。那如果 GPU 真的崩了,或者模型推理全部超时,你怎么办?
候选人 🤓:
必须上熔断降级。我用 Sentinel 配两条规则:
- 慢调用比例 > 50%,就熔断 10 秒,所有请求直接返回一个兜底回复:“我正在努力学习中,请稍等~😊”
- 异常比例 > 30%,直接切到静态 FAQ 搜索引擎,保证最基本的回答能力。
这样用户永远看不到 500 或者白屏,降级体验也是温暖的。💬
面试官 👨💼:
如何观察这套体系是否健康?有没有做可视化监控?
候选人 🤓:
必须得有,我把限流效果画成了这样一张图:
请求量
│ ╱╲ ╱╲
│╱ ╲╱╱ ╲_____ 实际进入模型的量
│─────────────── 限流阈值 50 QPS
│ ××拒绝××
└───────────────→ 时间用 Prometheus 采集 rejected_count 和 gpu_queue_length,Grafana 面板展示。一旦排队数超过 80% 就告警,我们就手动扩容或者触发临时降级开关。看着曲线平稳,心里才踏实。📊
面试官 👨💼:
很好,最后一个问题:你觉得这套方案最大的价值是什么?用一句话总结。
候选人 🤓:
用“快速失败 + 降级兜底”来保护昂贵的 GPU 推理资源,换用户一个永远可用的稳定体验。就算有问题了,也不至于系统全部不可用,还可以继续使用😎🛡️
面试官 👨💼:
不错,思路清晰,落地也实在。我这边没有问题了,谢谢你。
候选人 🤓:
谢谢面试官,期待后续沟通!🙌
