Skip to content

高并发项目知识卡片(简历版)

这份卡片按你当前简历的项目强度整理。

目标不是高并发架构全覆盖,而是:

  • 能撑住 黑马点评 的缓存治理、秒杀并发、异步削峰追问
  • 能把 Redis、Lua、Redisson、MySQL 事务和唯一约束串起来
  • 能讲清楚为什么不会超卖、不会重复下单、不会把数据库打垮
  • 能作为普通 Java 后端高并发项目的面试主线

1. 项目总纲

  • 黑马点评是社交电商高并发实战项目
  • 核心不是 CRUD
  • 核心是高频访问和核心交易链路
  • 主要场景:
  • 热点商铺查询
  • 缓存穿透 / 击穿 / 雪崩
  • 秒杀下单
  • 一人一单
  • 异步削峰
  • 分布式锁
  • 数据库一致性兜底

2. 面试主线

  • 用户查热点商铺
  • 先走本地缓存
  • 再走 Redis
  • 再查 MySQL
  • 秒杀下单时
  • 先用 Redis + Lua 做预校验和预扣减
  • 再把订单消息放入队列
  • 后台异步消费落库
  • MySQL 用事务、条件更新、唯一约束兜底
  • 记一句:
  • Redis 抗并发,MySQL 兜一致性

3. 多级缓存

  • 多级缓存 = 本地缓存 + Redis + MySQL
  • 本地缓存常用 Caffeine
  • Redis 做分布式缓存
  • MySQL 是最终数据源
  • 查询顺序:
  • 先查 Caffeine
  • 未命中查 Redis
  • 未命中查 MySQL
  • 查到后回写 Redis 和本地缓存
  • 好处:
  • 降低 Redis 压力
  • 降低数据库压力
  • 提升热点接口响应速度

4. 本地缓存风险

  • 本地缓存速度快
  • 但多实例下有一致性问题
  • 一个实例更新了
  • 其他实例本地缓存可能还是旧数据
  • 常见解决:
  • 设置短 TTL
  • 删除本地缓存
  • Redis Pub/Sub 广播刷新
  • MQ 广播刷新
  • 项目口径:
  • 本地缓存适合热点读,但必须考虑多实例一致性

5. 缓存穿透

  • 缓存穿透:
  • Redis 没有
  • MySQL 也没有
  • 请求还一直打进来
  • 常见原因:
  • 恶意请求不存在的 ID
  • 查询冷门不存在数据
  • 常见方案:
  • 空值缓存
  • 布隆过滤器
  • 参数校验
  • 接口限流
  • 项目口径:
  • 不存在的商铺 ID 可以缓存 NULL,并设置短 TTL

6. 缓存击穿

  • 缓存击穿:
  • 热点 key 失效
  • 大量请求同时打到数据库
  • 常见方案:
  • 互斥锁
  • 逻辑过期
  • 热点预热
  • 项目里常用逻辑过期:
  • 缓存里存数据和逻辑过期时间
  • 未过期直接返回
  • 过期后抢锁重建
  • 抢不到锁直接返回旧数据

7. 逻辑过期

  • 逻辑过期不是 Redis TTL 过期
  • 数据还在 Redis 里
  • 只是业务字段标记已经过期
  • 好处:
  • 热点 key 不会突然消失
  • 高并发下能先返回旧数据
  • 后台异步重建缓存
  • 牺牲一点实时一致性
  • 换取高可用和抗并发
  • 记一句:
  • 逻辑过期适合热点数据,核心是抗并发,不是强一致

8. 缓存雪崩

  • 缓存雪崩:
  • 大量 key 同时失效
  • 或 Redis 整体不可用
  • 结果大量请求打到数据库
  • 常见方案:
  • TTL 加随机值
  • 热点数据预热
  • Redis 高可用
  • 限流降级
  • 多级缓存
  • 熔断保护

9. 数据一致性

  • 缓存和数据库很难做到强一致
  • 常见策略是最终一致
  • 更新数据时:
  • 先更新数据库
  • 再删除缓存
  • 不推荐先删缓存再更新数据库
  • 因为并发读可能把旧数据重新写回缓存
  • 高要求场景可以加:
  • 延迟双删
  • MQ 重试删除
  • binlog 订阅
  • 记一句:
  • 缓存是加速层,数据库才是真相层

10. 秒杀核心问题

  • 秒杀要解决三个问题:
  • 库存不能超卖
  • 用户不能重复下单
  • 数据库不能被瞬时流量打垮
  • 普通做法直接查库扣库存
  • 高并发下压力太大
  • 所以先把校验前置到 Redis
  • 再异步落库

11. Redis + Lua

  • Lua 脚本在 Redis 内部原子执行
  • 可以把多个操作合成一个原子步骤
  • 秒杀脚本通常做:
  • 判断库存是否大于 0
  • 判断用户是否已下单
  • 扣减 Redis 库存
  • 记录用户已下单
  • 写入订单消息
  • 好处:
  • 减少网络往返
  • 保证预校验原子性
  • 把高并发挡在 Redis 层

12. 为什么 Lua 能防超卖

  • 库存判断和扣减在一个脚本里完成
  • Redis 单线程执行命令
  • 同一时刻只有一个脚本在执行
  • 所以不会出现多个线程同时看到库存还有 1
  • 然后都扣减成功的问题
  • 但要注意:
  • Redis 层只是预扣减
  • MySQL 还要最终兜底

13. 一人一单

  • 一人一单可以用 Redis set 预校验
  • key 按优惠券 ID 区分
  • value 存用户 ID
  • Lua 里先 SISMEMBER 判断是否买过
  • 没买过再 SADD 标记
  • MySQL 层也要加唯一约束
  • 防止 Redis 预校验和异步落库之间出现异常
  • 记一句:
  • Redis 防大流量,唯一索引防最终重复

14. 异步削峰

  • 秒杀请求不能都同步打到数据库
  • 可以先在 Redis 完成预校验
  • 再把订单写入队列
  • 后台线程异步消费
  • 好处:
  • 快速响应用户
  • 削平瞬时流量
  • 保护数据库
  • 风险:
  • 消息丢失
  • 重复消费
  • 消费积压
  • 落库失败

15. Redis Stream

  • Redis Stream 可以做消息队列
  • 支持消费者组
  • 支持 pending-list
  • 消费成功后 ACK
  • 如果消费者宕机或处理失败
  • 消息会留在 pending-list
  • 后续可以重新处理
  • 面试口径:
  • Stream 比简单 List 更适合需要确认和失败恢复的异步订单场景

16. Pending List

  • Pending List 保存已投递但未 ACK 的消息
  • 如果消费线程处理时异常
  • 消息不会直接丢
  • 可以从 pending-list 里重新读取
  • 处理成功后再 ACK
  • 作用:
  • 提升可靠性
  • 支持异常恢复
  • 防止订单消息处理一半丢失

17. Redisson 分布式锁

  • Redisson 用于分布式锁兜底
  • 常见场景:
  • 防止同一用户并发下单
  • 防止多个实例同时处理同一资源
  • 它比 setnx + expire 更完善
  • 支持:
  • 原子加锁
  • 可重入
  • 看门狗续期
  • Lua 解锁防误删
  • 但分布式锁不是唯一兜底
  • MySQL 唯一约束仍然需要

18. 锁粒度

  • 锁粒度不能太大
  • 锁整个优惠券并发会很差
  • 常见做法:
  • 按用户 ID 加锁
  • 比如 lock:order:userId
  • 这样只限制同一用户重复下单
  • 不影响不同用户并发下单
  • 记一句:
  • 锁粒度越小,并发能力越好,但业务边界要正确

19. AOP 事务自调用

  • Spring 事务基于代理
  • this 自调用会绕过代理
  • 导致 @Transactional 失效
  • 异步下单时如果事务方法在同类内部直接调用
  • 事务可能不生效
  • 解决:
  • 通过代理对象调用事务方法
  • 或拆到另一个 Service
  • 记一句:
  • 事务方法必须经过 Spring 代理对象调用

20. MySQL 兜底

  • Redis 预扣减不能替代数据库约束
  • MySQL 仍然要兜底:
  • 库存扣减用条件更新
  • where stock > 0
  • 一人一单用唯一约束
  • 订单落库走事务
  • 消费消息要考虑幂等
  • 项目口径:
  • Redis 层拦住大部分并发,MySQL 层保证最终正确

21. 条件更新

  • 扣库存不能先查再改
  • 先查再改有并发窗口
  • 更好的方式:
  • update voucher set stock = stock - 1 where id = ? and stock > 0
  • 数据库保证原子更新
  • 如果影响行数为 0
  • 说明库存不足或并发失败
  • 记一句:
  • 条件更新是数据库层防超卖的最后防线

22. 唯一约束

  • 一人一单不能只靠代码判断
  • 并发下代码判断可能失效
  • 数据库应加唯一约束
  • 比如:
  • user_id + voucher_id
  • 重复插入会失败
  • 应用层捕获异常并返回重复下单
  • 记一句:
  • 唯一约束是防重复下单的最终事实

23. 幂等

  • 异步消费可能重复执行
  • 网络重试也可能重复执行
  • 所以下单落库要幂等
  • 常见手段:
  • 唯一业务键
  • 唯一索引
  • 状态判断
  • 消息 ID 去重
  • 执行后 ACK
  • 失败不 ACK,后续重试

24. 延迟队列

  • 延迟队列适合订单超时取消
  • 下单后发送延迟消息
  • 到期检查订单状态
  • 如果未支付
  • 取消订单
  • 释放库存
  • 注意:
  • 到期后不能直接取消
  • 要先查数据库真实状态
  • 避免用户已支付但消息延迟导致误取消

25. 事件订阅

  • 事件订阅用于业务解耦
  • 比如下单成功后:
  • 发优惠券使用事件
  • 发积分事件
  • 发通知事件
  • 好处:
  • 主流程更短
  • 非核心逻辑异步处理
  • 风险:
  • 最终一致性
  • 消息失败重试
  • 幂等消费

26. 常见危险追问

  • Redis 扣了库存,MySQL 落库失败怎么办?
  • 要补偿或重试,不能只返回成功
  • 消息重复消费怎么办?
  • 用唯一约束或消息幂等去重
  • Redis 宕机怎么办?
  • 降级、限流、恢复后校准库存
  • 本地缓存不一致怎么办?
  • TTL + 广播刷新 + 删除缓存
  • 逻辑过期会不会返回旧数据?
  • 会,所以适合能接受短暂旧值的热点读场景

27. 项目高分口径

  • 黑马点评里我主要围绕高并发读和高并发写做优化
  • 读侧:
  • Caffeine + Redis 多级缓存抗热点流量
  • 空值缓存防穿透
  • 逻辑过期防击穿
  • TTL 随机值和降级防雪崩
  • 写侧:
  • Redis + Lua 做库存和一人一单的原子预校验
  • Redis Stream 异步削峰
  • Redisson 分布式锁控制同一用户并发
  • MySQL 条件更新和唯一约束做最终兜底
  • 这条链路的核心是:
  • 前面抗流量,后面保正确

28. 面试一句话总结

  • 多级缓存解决热点读压力
  • 逻辑过期解决热点 key 击穿
  • Lua 把库存校验和扣减变成 Redis 原子操作
  • Stream 负责异步削峰,Pending List 负责失败恢复
  • Redisson 控制分布式并发,MySQL 条件更新和唯一约束兜最终一致性

相关链接:[[黑马点评]] | [[Redis 知识卡片(简历版)]] | [[MySQL 知识卡片(简历版)]] | [[Java Spring 知识卡片(简历版)]]