高并发项目知识卡片(简历版)
这份卡片按你当前简历的项目强度整理。
目标不是高并发架构全覆盖,而是:
- 能撑住
黑马点评的缓存治理、秒杀并发、异步削峰追问 - 能把 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 知识卡片(简历版)]]