Dianping项目答题卡#
一句话答案#
“趣逛”是一个本地生活社交电商平台(Spring Boot + Redis + Kafka),核心亮点是四层秒杀防超卖(Lua 预扣→Kafka 异步→Redisson 分布式锁→DB 乐观锁)、三层点赞校验(布隆过滤器→ZSet 热数据→DB 兜底)、以及通过跨文件代码审查发现的 10 个 Bug。
核心要点
一、项目三级概述
| 版本 | 内容 |
|---|---|
| 30字 | 本地生活平台,四层秒杀防超卖,Redis 多数据结构实战 |
| 50字 | Spring Boot + Redis + Kafka 本地生活社交电商。四层秒杀并发控制、三层点赞校验(布隆→ZSet→DB)、Feed 推模式+游标分页。通过代码审查发现 10 个跨文件 Bug |
| 100字 | 基于 Spring Boot 2.3 的本地生活社交电商”趣逛”。秒杀模块实现四层并发控制:Redis Lua 原子预扣库存→Kafka 异步下单→Redisson 分布式锁防重复→DB 乐观锁兜底。独立设计点赞子系统:布隆过滤器挡 95% 无效请求→ZSet 热数据 200 条→DB 兜底。Feed 流采用推模式+ZSet 游标分页。通过端到端链路追踪发现 10 个跨文件一致性 Bug(含 P0 级 Lua 类型错误和事务缺失) |
二、技术栈速查
| 组件 | 用途 |
|---|---|
| Spring Boot 2.3.12 / Java 8 | 主框架 |
| Redis (String/Hash/ZSet/Set/GEO/Bitmap) | 缓存/锁/排行/点赞/签到/附近搜索 |
| Redisson | 分布式锁(watchdog 自动续期) |
| Kafka | 秒杀异步下单、点赞异步持久化 |
| MySQL + MyBatis-Plus | 持久层 |
| Caffeine | 本地缓存 L1 |
三、核心亮点与 STAR
亮点1:四层秒杀防超卖
| 层次 | 实现 | 作用 |
|---|---|---|
| L1 Redis Lua | Hash 存 stock+时间窗+已购数,原子预扣 | 微秒级拦截,承接并发 |
| L2 Kafka 异步 | 预扣成功→发 MQ→消费者创建订单 | 读写分离,削峰 |
| L3 Redisson 锁 | 用户粒度分布式锁,watchdog 续期 | 防同一用户重复下单 |
| L4 DB 乐观锁 | WHERE stock > N | 最终安全网 |
STAR:响应时间从 DB 直写 100ms+ 降到 Redis 微秒级;排查中发现 rollback.lua WRONGTYPE 和字段不一致 Bug。
亮点2:三层点赞校验(独立设计)
| 层次 | 实现 | 效果 |
|---|---|---|
| L1 布隆过滤器 | 100 万容量、1% 误判率、~1.2MB | 挡住 ~95% 无效请求 |
| L2 Redis ZSet | 每用户/文章热数据最多 200 条 | 热数据快速判断 |
| L3 MySQL | 兜底查询 | 最终一致 |
Kafka 异步持久化 + Caffeine+Redis 多级缓存。独立包结构(Controller/Service/Mapper),预留微服务拆分。
亮点3:CacheClient 通用工具类
两种策略:
- 互斥锁(保一致性):缓存miss → 获取锁 → 查DB → 写缓存 → 释放锁
- 逻辑过期(保可用性):返回过期数据 → 异步重建缓存
多级缓存:Caffeine L1(100条/2h)→ Redis L2 → MySQL L3
亮点4:Feed 流推模式 + 游标分页
- 推模式:发布时 push 到所有粉丝 inbox(ZSet,score=时间戳)
- 游标分页:维护 minTime + offset 处理同分数元素,解决动态数据集翻页重复问题
亮点5:跨文件代码审查(核心差异化)
方法:端到端追踪请求链路(Controller→Service→Redis/Lua→Kafka→DB) 发现 10 个 Bug,含 3 个 P0/P1 级别:
四、已知 Bug 速查(面试主动暴露)
| 级别 | Bug | 位置 | 影响 |
|---|---|---|---|
| P0 | rollback.lua 对 Hash 类型用 String 命令(INCRBY) → WRONGTYPE | rollback.lua:12 | 库存回滚完全失败 |
| P0 | createVoucherOrder() 缺少 @Transactional | VoucherOrderServiceImpl:81 | 步骤2失败→扣库存无订单 |
| P1 | seckill.lua 非幂等 | seckill.lua | Kafka 重试导致重复扣减 |
| P1 | Kafka 手动 ACK 未调用(配了 manual 模式) | KafkaOrderConsumer:37-61 | 重启后重复消费全部消息 |
| P1 | buyCount 字段名不一致 | rollback.lua:18 vs seckill.lua | 回滚时限购数不恢复 |
| P2 | CacheClient.unlock() 缺少 owner 校验 | CacheClient:174-176 | 可能误删他人锁 |
| P2 | articleLikeZsetKey 存 articleId 而非 userId | LikeBehaviorServiceImpl:173 | ”谁点了赞”列表数据错误 |
| P3 | 验证码硬编码 “123123” | UserServiceImpl:87 | 开发遗留 |
面试策略:主动讲 2-3 个 Bug → 展示质量意识 → 解释修复方案或为什么留着
五、10 大必问追问 + 标准答法
-
“超卖怎么防?” → 四层控制:Lua 原子预扣(核心)→ Kafka 异步削峰 → Redisson 用户粒度锁 → DB WHERE stock>0 兜底。
-
“为什么用 Lua 不用 Redis 事务?” → MULTI/EXEC 不是原子的(WATCH 乐观锁高并发冲突大),Lua 脚本在 Redis 单线程里原子执行,一个脚本完成 check+扣减。
-
“Kafka 消费失败怎么办?” → 当前已知问题:ACK 未调用导致重启重复消费。正确方案:消费成功后手动 ack + 消费端幂等(订单号唯一索引去重)+ 死信队列兜底。
-
“缓存穿透/击穿/雪崩怎么处理?” → CacheClient 封装两种策略:穿透用空值缓存(TTL 2min);击穿用互斥锁(保一致)或逻辑过期(保可用);雪崩用随机 TTL 打散。
-
“分布式锁怎么演进的?” → V1 GET+DELETE(竞态)→ V2 加 owner 校验(非原子)→ V3 Lua 原子比较删除 → V4 Redisson(watchdog 续期+可重入)。
-
“Feed 流为什么用推模式?” → 场景是中小规模社交(粉丝数有限),推模式写扩散但读简单、延迟低。大V场景需要推拉结合。游标分页用 ZREVRANGEBYSCORE + offset 解决同分重复。
-
“点赞系统怎么保证一致性?” → 布隆过滤器只防重(有误判),最终一致性靠 DB 唯一索引。Kafka 异步写 DB 有延迟,接受最终一致。如果 Kafka 消费失败走重试+死信队列。
-
“这和黑马点评有什么区别?” → 基础结构参考了教程,但三个核心增量是我的:①点赞子系统三层校验完全独立设计 ②Kafka 异步架构自己搭建 ③跨文件代码审查发现 10 个 Bug(含 P0 级类型错误和事务缺失)。
-
“发现的 Bug 怎么不修?” → 部分已修(如 owner 校验补上),部分保留做面试素材(如 rollback.lua WRONGTYPE),因为这些 Bug 本身就是面试官想考的跨语言一致性问题。
-
“如果重新设计你会改什么?” → ①Lua 脚本加幂等键防 Kafka 重试双扣 ②Kafka 消费端正确调 ACK + 幂等表 ③加 Prometheus 监控库存/消费延迟 ④缓存一致性用 Canal 监听 binlog 替代手动双删。
六、业务包装术语
| 代码概念 | 面试表达 |
|---|---|
| Shop | 门店 |
| Voucher seckill | 限时特惠抢购 |
| Blog | 种草笔记 |
| Follow | 社交关注 |
| Like | 内容互动 |
| Sign-in | 每日打卡 |
⚠️ 不要提”黑马”/“hmdp”。被问到直说”参考了开源教程结构,核心增量是自己的”。
面试回答(2分钟版)
“趣逛”是我做的一个本地生活社交电商平台,技术栈是 Spring Boot + Redis + Kafka + Redisson。我重点讲三个亮点。第一是秒杀模块的四层防超卖:最核心的是 Redis Lua 脚本原子预扣库存,一个脚本里完成库存判断、扣减和限购检查,预扣成功后发 Kafka 异步创建订单,消费端用 Redisson 分布式锁防同一用户重复下单,最后 DB 乐观锁做兜底。第二是我独立设计的点赞子系统:布隆过滤器挡掉 95% 无效请求,ZSet 存热数据快速判重,DB 做最终一致性兜底,整个模块独立包结构可以直接拆微服务。第三是我做的跨文件代码审查,端到端追踪请求链路发现了 10 个 Bug,比如 rollback.lua 对 Hash 类型错用了 String 命令导致回滚失败,还有创建订单方法缺少 @Transactional 导致库存扣了但订单没创建。这些都是单文件 review 发现不了的跨语言一致性问题。Redis 在这个项目里用了 6 种数据结构:String 做缓存、Hash 做秒杀库存、ZSet 做排行榜和 Feed 流、Set 做共同关注、GEO 做附近搜索、Bitmap 做签到统计。
追问与易错
追问方向:
- “Redisson 和自己写的锁有什么区别?”→ Redisson 提供 watchdog 自动续期(防业务未完成锁过期)、可重入锁、红锁(多节点防单点故障)、公平锁等完善机制;自己写的锁容易遗漏续期和异常释放问题
- “布隆过滤器误判怎么办?”→ 误判=把不存在的判为存在,放行后由 ZSet/DB 兜底校验不影响正确性;删除不支持是布隆过滤器天然限制,需要删除能力可用 Counting Bloom Filter 或定期重建
- “Redis 数据和 DB 不一致怎么办?”→ Cache-Aside 模式先更新 DB 再删缓存 + 延迟双删兜底(删缓存→更新DB→延迟再删缓存)+ Canal 监听 binlog 异步更新缓存作为终极方案
- “Kafka 消息丢失怎么防?”→ 生产端 acks=all + retries 保证写入副本;消费端手动 ACK(处理完才提交 offset);极端场景用本地消息表做最终一致性兜底
- “这个项目的 QPS 能到多少?”→ 设计上 Redis Lua 原子操作单机可扛数万 QPS,整体架构通过缓存+MQ 异步可支撑万级并发;但未做正式压测,诚实回答设计目标和理论值
易错点:
- ❌ 说”我完全自己写的”——诚实说基础结构参考教程,重点讲增量
- ❌ 隐瞒 Bug——主动暴露 Bug 比隐藏更加分,展示工程质量意识
- ❌ 夸大规模——这是练习项目,不要说”支撑百万用户”
- ❌ 说不出 Bug 细节——必须记住关键 Bug 的文件名、行为和原因
- ✅ 核心卖点:Redis 多数据结构实战 + Bug 审查质量意识 + 独立设计点赞子系统