某核心接口 QPS 8 万,用 Redis 集群(32 分片)缓存商品详情,命中率 96%。大促前压测发现两件事:一是 Redis 集群 CPU 到 80%,主要集中在几个热 key 上;二是缓存击穿瞬间 DB 被打到 3 万 QPS。你负责这次大促的稳定性。请说明:你怎么定位并治理热 key、怎么设计防击穿、怎么估算缓存与 DB 的容量上限,以及什么情况下你会考虑不用缓存或换方案。
面试官在考什么
判断候选人能否把缓存问题拆成热 key、击穿、穿透、一致性四类不同成因,并在容量成本、DB 兜底能力、数据一致性要求之间做出取舍,而不是只会“加锁加机器”。
回答框架 · 前两步
- 热 key 定位:用 redis-cli --hotkeys、客户端埋点或代理层统计找出具体 key,区分是单 key 热点还是分片倾斜,并区分读热、写热。
- 热 key 治理:本地缓存 + 多级缓存、key 加随机后缀打散、多副本读、必要时热点数据下沉到应用内存并设短 TTL。
还有 3 条答案要点、 3 层追问、 5 个常见坑, 以及针对你简历的追问路径,在定制报告里。
同一能力域的其他题
- 订单表 orders 约 2000 万行,字段有 id(主键)、user_id、status(只有 3 个取值:待支付/已支付/已取消)、created_at、amount,现已有单列索引 idx_user_id(user_id)。线上核心接口执行:SELECT * FROM orders WHERE user_id = ? AND status = ? ORDER BY created_at DESC LIMIT 20; EXPLAIN 显示 key=idx_user_id、rows≈3 万、Extra 里有 Using filesort。你会怎么调整索引?为什么建 (user_id, status, created_at) 而不是 (user_id, created_at, status)?
- 一张 2000 万行的订单表 orders(id PK, user_id, status, created_at, amount),status 只有 5 个取值,其中 'pending' 约占 3%。已有索引 PRIMARY(id)、(user_id, status)、(created_at)。现在有这样一条查询:SELECT * FROM orders WHERE status='pending' ORDER BY created_at DESC LIMIT 20。它在生产上要跑 3 秒左右,DBA 说它在全表扫描。请你判断为什么会走全表扫描,并给出你的索引方案,说明为什么是它。
- 订单表 3 亿行,MySQL 8.0 + InnoDB。核心查询是 `SELECT ... FROM orders WHERE merchant_id=? AND status IN (1,2) ORDER BY created_at DESC LIMIT 20`,QPS 约 800,P99 从 30ms 涨到 1.2s。现有联合索引是 idx_mch(merchant_id, status, created_at),主键 order_id。你接手这个慢查询,要求说明:你第一步看什么来确认瓶颈、为什么现有索引没能避免排序、你改什么(索引还是查询)、怎么评估改动对线上写入和主从延迟的影响,以及如果业务不愿意改查询语义你怎么办。
- 表 orders(id BIGINT PRIMARY KEY, user_id BIGINT, status VARCHAR(16), created_at DATETIME, amount INT),已有索引 idx_user(user_id),表里 2000 万行,绝大多数订单 status='paid'。线上查询:SELECT * FROM orders WHERE user_id=? AND status='paid' ORDER BY created_at DESC LIMIT 20; EXPLAIN 结果是 type=ref、key=idx_user、rows≈18000、Extra='Using where; Using filesort'。你会怎么优化这条 SQL?如果要建复合索引,字段顺序怎么定,为什么这样定?
- 库存表 stock(sku varchar 主键, qty int),隔离级别为 RR。扣减写法 A:UPDATE stock SET qty = qty - 1 WHERE sku = ? AND qty >= 1,然后判断 affected rows 是否为 1。写法 B:先 SELECT qty FROM stock WHERE sku = ?,在应用层判断 qty >= 1,再执行 UPDATE stock SET qty = qty - 1 WHERE sku = ?,两个操作都在同一个事务里。现在库存只有 1,两个并发请求各扣 1,分别用 A 和 B 会不会超卖?为什么?
- MySQL InnoDB,隔离级别 REPEATABLE READ。下单扣库存的代码是这样写的:先在事务里执行 SELECT stock FROM product WHERE id=1(普通 SELECT,不带 FOR UPDATE),应用层判断返回值 > 0,然后执行 UPDATE product SET stock = 5 WHERE id=1 把算好的值写回去。压测时这个逻辑出现了超卖。请解释为什么在 RR 下还会超卖,并给出至少两种修复方案,分别说明它们的代价。