面试知识库

压测相关面试 Q&A#

基于 2026-06-30 真实压测数据整理。配置: 4 uvicorn workers + 3 diagnosis workers,手动并发槽 16,Worker 并发槽 32。

每个问题的回答都遵循 STAR 结构 (Situation → Task → Action → Result),重点数据加粗。


一、压测方法论#

Q1: 你是如何进行压测的?用了什么工具?#

我没用 k6、wrk 这类通用工具,而是用 Python asyncio + httpx 自己写了一套压测脚本。原因有三:

  1. 和业务贴合: 压测需要构造 Alertmanager 标准 payload (带 fingerprint、severity、receiver 等字段) 和诊断提交 payload (带 query、severity、session_id),通用工具构造复杂 JSON 比较别扭。
  2. 多种流量模型: 我需要 flat (固定并发)、ramp (阶梯加压)、sustained (恒定 QPS)、burst (突发脉冲)、mixed (混合流量) 五种模式来模拟不同生产场景,自己写更灵活。
  3. 可观测集成: 脚本在压测前后自动调 /api/v1/queue/status 拉取队列快照 (depth、各优先级分布、并发槽占用、Worker 存活数),可以直观看到压力如何穿过 API 层→队列→Worker 的全链路。

脚本核心用 asyncio.Semaphore 控制并发度,asyncio.gather 批量发压,收集每个请求的状态码和延迟后统一计算 P50/P95/P99/吞吐等指标。

Q2: 这些压测的数据是如何得到的?能保证准确性吗?#

数据来自真实运行——本机启动完整服务栈 (Postgres + Redis + 4 uvicorn worker + 3 diagnosis worker + 5 个 MCP 工具服务),用压测脚本直接打本地端口。

准确性保障:

  • 客户端计时: 每个请求用 time.perf_counter() 记录从发出到收到响应的 wall-clock 时间,包含完整的网络+序列化+业务逻辑耗时。
  • 全量统计: 不采样,每一个请求的状态码和延迟都收集,百分位用排序后直接取索引计算。
  • 可复现: 同样的命令、同样的服务配置,任何人都可以在本地复现。脚本支持 --n--concurrency--qps 等参数精确控制。

局限性我也清楚: 本机压测走的是 loopback 不经过真实网络,所以延迟比生产会低; 另外单机 4 个 uvicorn worker 共享 CPU,并发数太高时会出现本地资源争抢,不能完全模拟多节点部署。

Q3: 为什么不用 k6/wrk/JMeter 这些专业压测工具?#

不排斥这些工具,但这个项目有几个特殊需求用自己的脚本更合适:

需求通用工具自写脚本
构造 Alertmanager v4 payload需要写 JS/Lua 模板直接 Python dict
5 种流量模型 (ramp/burst 等)k6 支持但配置复杂100 行 Python 搞定
压测前后拉队列快照要额外写 hook直接嵌在脚本里
和 CI 集成需要装额外 binary项目已有 httpx 依赖
混合场景 (submit + webhook 并发)需要多场景编排一个 asyncio.gather

如果团队扩大、需要分布式压测 (多台机器同时打) 或更复杂的报告,我会考虑迁到 k6,它的 scenario API 和 threshold 机制确实更成熟。

Q4: 压测场景是怎么设计的?为什么选这 5 种模式?#

每种模式对应一个真实的生产风险:

模式模拟场景验证目标
flat运维团队集体提交诊断 (如大规模故障)API 层在高并发下不崩、入队不丢
ramp逐步增加负载找系统拐点吞吐 vs 并发的关系,定位性能拐点
sustained日常稳态流量 (如 Alertmanager 持续推送)长时间运行下内存/延迟不漂移
burst告警风暴 (网络设备批量掉线)限流是否精准、突发后是否能恢复
mixed真实生产: SRE 手动诊断 + Alertmanager 自动推送两条通道互不干扰、不降级

二、压测数据解读#

Q5: 你的核心压测数据是什么?能不能一句话总结?#

一句话: Webhook 稳态 50 QPS 全量通过 P99 < 37ms,突发 30 倍流量 (300 QPS) 限流零误杀,回落秒级恢复,全链路 8000+ 请求零 5xx。

核心数据卡片:

场景请求数成功率吞吐P50P99
Webhook 稳态 50qps × 30s1,500100%48.1 req/s13ms37ms
Webhook 突发 300qps × 8s2,40019.3% (限流正常)275.9 req/s4ms30ms
Submit 限额内20100%312.8 req/s37ms46ms
Submit 500 并发冲击5004% (=精确限额)738.8 req/s118ms228ms
混合 (submit+webhook) 30s2,55062.5%41.7 req/s9ms29ms

Q6: Webhook 稳态 100% 成功但突发只有 19.3%,这个数据怎么解释?#

这恰恰说明限流在正确工作:

  • 稳态 50 QPS: 低于限额 (50/sec/IP + 500/min/source),所以 1500 个请求全部放行,P99 仅 37ms。
  • 突发 300 QPS: 远超 50/sec 的 IP 限额,Lua 脚本精准拦截超额部分,只有窗口内第一批请求通过。

关键指标: 拦截的 1936 个请求全部返回 429 + Retry-After 头,没有 500/502/连接超时,说明限流保护了后端不被打穿。突发结束后 10 QPS 回落阶段又恢复 100% 成功,说明系统没有被突发打出亚健康状态。

Q7: submit 接口 500 并发只有 20 个成功,是不是性能太差了?#

不是性能差,是 限流设计如此: 单 IP 每分钟最多 20 个手动诊断请求 (rate_limit_manual_per_ip_per_min=20)。

500 个请求来自同一个 IP、在同一个分钟窗口内,所以只有前 20 个拿到 200,其余 480 个被 429。这个 20 是精确值——不是 19 也不是 21,说明 Lua 原子脚本的计数是准确的。

而且注意被拦截的 480 个请求的处理延迟——整体 738.8 req/s 的吞吐,说明 429 是快速返回的 (不消耗后端资源),限流层没有成为瓶颈。

Q8: 阶梯加压测试里 c=100 时吞吐最高 (663 req/s),c=200 反而降到 243,为什么?#

典型的并发过高导致资源争抢:

并发吞吐P50分析
20300 req/s49msCPU 空闲多,请求排队少
100663 req/s100ms甜蜜点: pipeline 效率最高
200243 req/s453ms瓶颈: 本机 4 worker 共享 CPU,上下文切换开销激增

c=100 是这个配置下的性能甜蜜点。c=200 时 P50 从 100ms 涨到 453ms (4.5 倍),说明请求在 uvicorn worker 内部排队等 CPU 了。如果是多节点部署 (每节点 4 worker, 前面加 LB),这个拐点会线性右移。

Q9: 混合场景下 submit 和 webhook 有互相影响吗?#

从数据看没有:

通道单独跑 P99混合跑 P99变化
submit46ms44ms-4% (波动范围内)
webhook37ms27ms-27% (混合 QPS 更高但更均匀)

原因: submit 和 webhook 是不同的 FastAPI 路由,限流用不同的 scope key (manual vs webhook_key / webhook_src),进入的是同一个 Redis Streams 但用不同的 priority level。两条通道逻辑隔离,只在 uvicorn worker 和 Redis 连接池层面共享资源。在目前的 QPS 下,这些共享资源远未饱和。


三、限流设计深度#

Q10: 限流为什么用固定窗口而不是滑动窗口或令牌桶?#

三个考虑:

  1. 简单够用: 这是运维平台不是交易系统,固定窗口的 “窗口边界突刺” (理论上窗口交界处瞬时 2x) 对我们可接受。正常使用不会刚好在窗口边界集中请求。
  2. 单次 Redis 往返: 一个 Lua 脚本 INCR + EXPIRE 搞定,不需要多次 ZADD/ZRANGEBYSCORE (滑动窗口) 或 MULTI/EXEC (令牌桶)。
  3. 可演进: 接口是 hit(scope, identity, limit, window_sec)(bool, retry_after),后端 Lua 换成滑动窗口,调用方一行不改。

如果面试追问 “窗口边界怎么办”: 实际限额设为 20/min,即使边界突刺到 40/min,这个量级对后端毫无压力。真正的保护在下一层——分布式并发槽 (限的是正在执行的 LLM 调用,不是入队请求)。

Q11: 为什么限流用 Lua 脚本而不是 Redis 的 INCR + 单独 EXPIRE?#

这是一个真实踩过的坑:

问题: 如果用两步操作 INCR keyif count == 1: EXPIRE key window,在高并发下会出竞态: 多个请求同时看到 count == 1,都去 EXPIRE,窗口被反复重置。极端情况下 EXPIRE 丢失 (网络闪断),key 永不过期,该 IP 永远被封。

解法: 合成一个 Lua 脚本原子执行:

local n = redis.call('INCR', KEYS[1])
if n == 1 then
  redis.call('EXPIRE', KEYS[1], tonumber(ARGV[1]))
end
return n
lua

Redis 执行 Lua 是单线程原子的,不可能有两个请求同时拿到 n == 1

我在 tests/test_rate_limiter.py 里有对应的测试: 并发 5 个请求打 limit=5 的窗口,验证恰好 5 个通过 0 个多放。

Q12: Fail-open 策略——Redis 挂了就不限流,LLM 不会被打爆吗?#

分层设计——限流层 fail-open 不意味着没有保护:

请求 → [IP 限流] → [入队] → [分布式并发槽] → [LLM/RAG/MCP]
           ↑                      ↑
        fail-open              也 fail-open (但此时 Redis 挂了队列也不工作)
plaintext

关键洞察: 限流层和队列用的是同一个 Redis。如果 Redis 挂了:

  • IP 限流 fail-open → 请求放进来
  • 但入队操作 (XADD 到 Redis Streams) 也会失败 → 请求走不到 Worker
  • Worker 的 XREADGROUP 也拿不到任务

所以 Redis 挂了,限流层放行的请求会在入队环节失败返回 500,LLM 根本不会被调到。两层 fail-open 在 Redis 不可用时效果等价于”全链路不可用”,而不是”无限流打爆 LLM”。

Q13: 429 响应里的 Retry-After 是怎么算的?#

retry_after = window_sec - (now % window_sec)
python

固定窗口的桶号是 now // window_sec,当前窗口的剩余时间就是 window_sec - (now % window_sec)。客户端拿到这个值后等那么多秒,新窗口计数器归零,就可以重试了。

这个值写在 HTTP 响应头 Retry-After 和 body 的 retry_after 字段里,前端可以直接用来做倒计时 UI。


四、分布式并发控制#

Q14: 为什么需要分布式并发槽?asyncio.Semaphore 不够吗?#

不够,一旦多进程就失效:

uvicorn --workers 4
  ├── worker-1: Semaphore(2)  ← 各有各的
  ├── worker-2: Semaphore(2)
  ├── worker-3: Semaphore(2)
  └── worker-4: Semaphore(2)
  总并发 = 2 × 4 = 8   ← 配置写的是 2,实际跑了 8
plaintext

加上 3 个独立 diagnosis worker 进程,共 7 个进程,如果每个都本地限 2,全局就是 14 个 LLM 调用同时跑,会超 API 的 RPM/TPM 配额。

所以用 Redis ZSET 做全局槽:

  • 所有进程共享一个 aiops:limiter:worker_diagnosis ZSET
  • Lua 原子脚本: 清过期 → 数当前 → 没满则 ZADD 占槽
  • token 格式 hostname:pid:uuid,可以看出哪个进程占了哪个槽

Q15: 并发槽的 Lua 脚本具体做了什么?#

一个 Lua 脚本原子完成三步:

-- 1. 清掉已过期的槽 (score < 当前时间)
redis.call('ZREMRANGEBYSCORE', KEYS[1], '-inf', now_ms)
-- 2. 数还有多少存活槽
local count = redis.call('ZCARD', KEYS[1])
-- 3. 没满就占一个 (score = 当前时间 + TTL)
if count < limit then
  redis.call('ZADD', KEYS[1], now_ms + ttl_ms, token)
  return 1  -- 抢到了
end
return 0    -- 满了
lua

用 Redis 服务端的 TIME 命令取时间戳,避免各客户端时钟漂移。整个操作在 Redis 单线程里原子执行,不存在 “check-then-act” 竞态。

Q16: 进程崩溃了槽位怎么办?会不会永久泄漏?#

不会。每个槽有 TTL (默认 90 秒):

  • 正常运行: 后台任务每 30 秒心跳续期 (Lua _REFRESH_LUA 把 score 往后推)
  • 进程崩溃: 心跳停止 → 90 秒后 score 过期 → 下次有人 acquire 时被 ZREMRANGEBYSCORE 清掉

TTL 90s > 心跳 30s × 2,有一跳的容错余量。诊断任务最长 600 秒 (10 分钟),心跳会持续续期,不会被误清。

Q17: Pause/Resume 是什么?什么场景用?#

当诊断流程中遇到需要人工审批的高危操作时:

Worker 占槽 → Agent 跑诊断 → 遇到高危命令 → 等待审批 (可能几分钟到几小时)
plaintext

如果等审批时一直占着槽,其他任务就被卡住了。所以设计了 Pause/Resume:

await handle.pause()   # 释放槽 + 停心跳 (让给别人用)
# ... 等待人工审批 ...
await handle.resume()  # 重新抢槽 (满了会阻塞等待)
python

通过 ContextVar 暴露 current_slot,tool_runner 在检测到需要审批时可以拿到当前槽句柄并 pause。


五、队列与优先级#

Q18: 为什么用 Redis Streams 而不是 Kafka?#

维度Redis StreamsKafka
运维成本项目已有 Redis (限流/缓存),零额外部署需要 ZooKeeper/KRaft + broker 集群
日吞吐量我们的场景 5k-20k 条/天设计目标百万级/天
优先级4 条 stream 模拟 4 级优先级,Worker 按优先级顺序 XREADGROUP原生不支持,要 N 个 topic + 消费权重
消费确认XACK 逐条确认,pending 可重试offset commit,重平衡可能重复
持久性AOF 持久化,够用多副本分区,更强

一句话: Kafka 是 “大炮打蚊子”。Redis Streams 在当前规模下够用,而且和限流、并发槽共享同一个 Redis 实例,运维复杂度低。

如果日吞吐超过 10 万,或者需要跨数据中心消费,会考虑迁 Kafka。

Q19: 4 级优先级是怎么实现的?#

用 4 条独立的 Redis Stream:

aiops:incident:critical   ← severity=critical/page/p0
aiops:incident:high       ← severity=warning/p1/p2
aiops:incident:normal     ← severity=info 或手动提交
aiops:incident:low        ← severity=低优先级
plaintext

Worker 的消费策略:

  1. Phase 1 (非阻塞): 按 critical → high → normal → low 顺序 XREADGROUP count=1,有消息就拿
  2. Phase 2 (阻塞): 如果全空,XREADGROUP BLOCK 5000ms 等新消息

这样 critical 告警即使后到,也会被优先消费。

Q20: 压测数据怎么验证优先级插队?#

在 burst 压测后看队列快照:

depth=215 by_level={'critical': 50, 'high': 56, 'normal': 53, 'low': 56}
pending=3  ← 3 个 Worker 正在消费
plaintext

告警用 --severity mix 随机分配,4 个级别的堆积量大致均匀。但 Worker 消费总是先清 critical,可以在队列深度变化曲线中观察到 critical 最先回落。


六、全链路架构#

Q21: 请求从进来到被 Worker 执行,完整链路是怎样的?#

压力在每一层被逐级消化:

  • API 层: 限流挡掉超额流量
  • 队列层: Redis Streams 削峰,API 快速返回
  • 执行层: 分布式并发槽限制真正的 LLM 调用数

Q22: 你说 “API 快速返回”——submit 的 P50 才 37ms,这是因为没调 LLM 对吧?#

对。submit 接口是纯 “入账+入队”:

  1. Pydantic 校验 JSON (< 1ms)
  2. Lua 限流检查 (1 次 Redis 往返,~1ms)
  3. Postgres INSERT (一次网络往返,~5-10ms)
  4. Redis XADD (一次往返,~1ms)
  5. 返回 task_id

不调 LLM、不做 RAG 检索、不调 MCP 工具。这就是”队列削峰”的设计意图——把昂贵的诊断推到后台 Worker 异步执行,API 层做最轻量的事。

如果走同步 SSE 入口 (/diagnose),则 API 进程内跑完整诊断 (30s~5min),这时候就需要并发槽来控制了。


七、系统瓶颈与调优#

Q23: 你这个系统的瓶颈在哪?#

分层来看:

瓶颈当前上限扩容方式
API 接入uvicorn worker 数 × CPU~700 req/s (本机 4 worker)加节点 + LB
限流Redis 单线程10 万+ QPS (Redis 自身)不是瓶颈
队列写入Redis XADD几十万 QPS不是瓶颈
队列消费Worker 数量3 个 Worker加 Worker 进程/节点
诊断执行LLM API 配额 (RPM/TPM)百炼约 200 RPM换更高配额 / 多 Key 轮转

真正的瓶颈是 LLM API 配额,不是系统本身。这也是为什么我把执行并发槽 (32) 设得远低于系统吞吐能力——不是系统扛不住,是 LLM 调用量要控制。

Q24: 如果要把这套系统从本机搬到生产环境,需要改什么?#

按优先级:

  1. 多节点 API: Kubernetes Deployment replicas=3+,前面加 Ingress/LB。限流已经基于 Redis 全局,多实例天然支持。
  2. Worker 横向扩展: Worker 进程可以运行在任意节点上,共享同一个 Redis 的并发槽。加节点只需增加 WORKER_DIAGNOSIS_CONCURRENCY 配额。
  3. Redis 高可用: 现在单节点,生产上 Sentinel 或 Redis Cluster。限流和队列都依赖 Redis,单点是最大风险。
  4. Postgres 连接池: 用 PgBouncer,多 Worker 的场景下直连会打满连接数。
  5. 监控: Prometheus 采集 API 延迟 / 队列深度 / 槽占用 / Worker lag,Grafana 看板 + AlertManager 告警。
  6. 限流策略调整: 滑动窗口替代固定窗口 (消除窗口边界突刺);按用户/租户而不是纯 IP 限流。

Q25: 并发槽从 2 改到 16/32,凭什么选这个数?#

基于 LLM API 配额倒推:

百炼 RPM ≈ 200 (经测试的实际约束)
单次诊断平均 LLM 调用数 ≈ 5-8 次 (Planner + Executor iterations + Reporter)
单次诊断耗时 ≈ 30s-2min

Worker 并发 32 × 平均 6 次调用 × (60s / 60s每次诊断) ≈ 192 RPM ← 接近但不超百炼上限
手动并发 16 + Worker 并发 32 = 最大可能 48 个同时诊断 ← 不会真的同时都在调 LLM
plaintext

留有余量: 不是所有 48 个诊断都同时在调 LLM (有的在等工具返回、有的在做 RAG 检索),实际 LLM 并发会低于 48。

Q26: 压测延迟里,哪些是网络延迟,哪些是业务逻辑延迟?#

本机 loopback 网络延迟约 0.1ms,可以忽略。所以压测延迟 ≈ 纯业务逻辑:

操作耗时
Pydantic 校验< 1ms
Lua 限流 (1 次 Redis eval)~1ms
Postgres INSERT~5-10ms
Redis XADD~1ms
FastAPI 序列化响应< 1ms
合计 (P50)~13ms (webhook) / ~37ms (submit)

submit 比 webhook 慢是因为 submit 的校验逻辑更多 (session_id 处理、severity 映射等)。生产环境需要加上真实网络延迟 (同 Region ~1-3ms,跨 Region ~30-100ms)。


八、容错与降级#

Q27: 压测过程中出过事故吗?#

跑了 8000+ 请求,全部都是 200 或 429,零 5xx、零连接超时、零进程崩溃

但之前开发过程中踩过坑:

  • Lua 竞态 (已修复): 早期限流用 INCR + 单独 EXPIRE,并发压测时出现过窗口被反复重置的 bug,改成 Lua 原子脚本后再没出现。
  • Worker 不重连 (已知限制): Redis 重启后 Worker 的连接会断,进程直接退出。目前靠 start.sh 的启动顺序规避,生产上应该加重连逻辑或用 supervisor 进程管理。

Q28: 如果某个 Worker 执行诊断时突然被 kill -9,会发生什么?#

四层兜底:

  1. 并发槽 TTL 过期: 心跳停止 → 90s 后槽位被自动回收 → 不会永久泄漏
  2. Redis Streams pending: 消息已 XREADGROUP 但没 XACK → 留在 pending list → 其他 Worker 可以 XCLAIM 接管
  3. Postgres 任务状态: task status 停在 running → 定时扫描器发现 timeout (600s) 后标记失败 → 重新入队 (最多 3 次)
  4. DLQ: 重试 3 次仍失败 → 进入死信队列 → 人工排查

Q29: 你怎么验证 “零 5xx” 不是偶然的?#

  1. 多次复跑: 5 种场景各跑了多次,结论一致。
  2. 覆盖极端: burst 打了 300 QPS (正常的 30 倍)、flat 打了 500 并发,都是故意超出正常范围。
  3. 错误分类完备: 脚本统计了所有状态码分布,只有 {200: N, 429: M} 两种,没有第三种。
  4. 连接异常也统计: except Exception 会标记为 code=0,最终结果里没有出现过 0。

九、与简历的衔接#

Q30: 简历上写 “663 req/s”,面试官可能问 “这是什么条件下的?”#

准确回答: 本机 macOS,4 uvicorn worker,100 并发打 webhook 入口 (只做入账+入队,不调 LLM),663 req/s 是阶梯加压中 c=100 那一级的数据。c=200 时降到 243 req/s,说明 100 是单机甜蜜点。

如果追问”生产环境能到多少”: 线性估算,3 节点各 4 worker = 12 worker,理论上 ~2000 req/s。但实际受 Postgres 写入、Redis 带宽、网络延迟影响,需要在真实环境再跑一轮。

Q31: 面试官问 “这个数字有什么意义”,怎么回答?#

对运维诊断平台来说,入口吞吐不是核心指标,核心是”诊断任务的全链路 SLA”。663 req/s 证明的是:

  1. API 层不是瓶颈: 即使告警风暴 (网络设备批量掉线,几百条告警同时涌入),入口也扛得住。
  2. 削峰设计有效: 高并发进来的请求被快速入队,不会因为 API 层卡住导致告警丢失。
  3. 限流精准: 超额流量被干净地 429 掉,不会穿透到后端。

真正有价值的数字是 “P99 37ms 的稳态延迟”“突发后秒级恢复”——说明系统在正常负载下足够快,异常负载下能自保且快速恢复。

Q32: 面试官问 “这个项目你实际在生产跑过吗”,如果没有怎么回答?#

诚实说: 这是个人项目,没有在生产环境跑过。但我在设计时按生产标准来:

  1. 压测验证: 不只是写完就算,用多种流量模型做了真实压测,拿到量化数据。
  2. 容错设计: fail-open、TTL 过期回收、心跳续期、DLQ——这些不是纸上谈兵,都有代码和测试。
  3. 可运维: 队列深度/并发槽/Worker 存活数都暴露在 /queue/status,和 Prometheus 集成只差一个 exporter。
  4. 参数化: 所有限额都通过环境变量配置,不硬编码,部署时可以按实际环境调整。

和生产的差距我也清楚: 缺少多节点压测、缺少持续运行的稳定性验证、缺少真实网络环境下的尾延迟数据。如果给我一个月,我优先补 Redis 高可用和 Worker 自动重连。


十、设计取舍与反思#

Q33: 你觉得这套限流+队列+并发槽的设计过度了吗?#

对”个人项目”来说确实重了,但设计意图是 展示工程深度:

如果不做后果
不做 IP 限流一个脚本就能刷爆 API,占满 LLM 配额
不做队列高并发时 API 进程被 LLM 长调用拖垮
不做分布式并发槽多 Worker 同时打 LLM,超 RPM 限额

三层缺任一层,系统在 10 QPS 以上就会出问题。如果只能保留一个,我选队列削峰——它解决的是最根本的”快接口 vs 慢诊断”矛盾。

Q34: 如果重来,你会改什么?#

  1. 滑动窗口限流: 固定窗口的边界突刺虽然在当前场景可接受,但换成滑动窗口 Lua 脚本只多几行代码,没理由不换。
  2. Worker 自动重连: 当前 Worker 进程 Redis 断连直接退出,应该加 backoff 重试 + supervisor 重启。
  3. 分级 QPS 控制: 现在只有 “单 IP 20/min” 这种粗粒度,生产上应该支持租户级别的配额,甚至对接外部的 API Gateway。
  4. 压测脚本输出: 目前只打印文本,应该输出 JSON/CSV 方便画图对比历史数据。

Q35: 这套架构最大的亮点和最大的风险分别是什么?#

亮点: 三层递进式压力治理 (IP 限流 → 队列削峰 → 并发槽),每层都用 Lua 保证原子性,每层都 fail-open 不成为新的单点。有真实压测数据而不是 “理论上能扛住”。

风险: Redis 单点。限流、队列、并发槽全押在一个 Redis 上。Redis 挂了三层同时失效。生产必须上 Sentinel 或 Cluster,这是目前最大的待补项。