外观
00 - 项目总览与架构
知识图谱
┌─────────────────────────────────────────────────────────────────┐
│ hmdp - 黑马点评 │
│ Spring Boot 2.3.12 / Java 8 / Maven │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │
│ │ com.hmdp │ │ com.like │ │ External Services │ │
│ │ (主应用) │ │ (点赞子系统) │ │ │ │
│ ├──────────────┤ ├──────────────┤ │ ┌────────────────┐ │ │
│ │ Controller │ │ Controller │ │ │ MySQL │ │ │
│ │ ├─ Shop │ │ └─ Like │ │ │ (数据持久化) │ │ │
│ │ ├─ Blog │ ├──────────────┤ │ ├────────────────┤ │ │
│ │ ├─ Voucher │ │ Service │ │ │ Redis │ │ │
│ │ ├─ VouOrder │ │ ├─ LikeBhvr │ │ │ (缓存/锁/ID) │ │ │
│ │ ├─ User │ │ ├─ LikeArtCt │ │ ├────────────────┤ │ │
│ │ ├─ Follow │ │ └─ LikeUsrCt │ │ │ Kafka │ │ │
│ │ └─ ShopType │ ├──────────────┤ │ │ (异步消息队列) │ │ │
│ ├──────────────┤ │ Mapper │ │ ├────────────────┤ │ │
│ │ Service │ │ Event(Kafka) │ │ │ Redisson │ │ │
│ │ ├─ Shop │ └──────────────┘ │ │ (分布式锁框架) │ │ │
│ │ ├─ Blog │ │ └────────────────┘ │ │
│ │ ├─ VouOrder │ └──────────────────────┘ │
│ │ ├─ User │ │
│ │ ├─ Follow │ ┌──────────────────────────────────────┐ │
│ │ └─ Voucher │ │ 横切关注点 (Cross-Cutting) │ │
│ ├──────────────┤ │ │ │
│ │ Utils │ │ Auth: RefreshTokenInterceptor │ │
│ │ ├─CacheClient│ │ LoginInterceptor │ │
│ │ ├─SimpleRedis│ │ Cache: CacheClient (穿透/击穿/雪崩) │ │
│ │ │ Lock │ │ CaffeineCache (本地L1) │ │
│ │ ├─RedisIdWrkr│ │ Lock: SimpleRedisLock / Redisson │ │
│ │ └─UserHolder │ │ ID: RedisIdWorker (分布式ID) │ │
│ ├──────────────┤ │ MQ: KafkaOrderProducer/Consumer │ │
│ │ Event(Kafka) │ └──────────────────────────────────────┘ │
│ │ ├─Producer │ │
│ │ └─Consumer │ │
│ └──────────────┘ │
└─────────────────────────────────────────────────────────────────┘技术栈一览
| 层次 | 技术 | 用途 |
|---|---|---|
| 框架 | Spring Boot 2.3.12 | 应用骨架 |
| 语言 | Java 8 | 开发语言 |
| ORM | MyBatis-Plus | 数据库访问,IService/ServiceImpl 模式 |
| 数据库 | MySQL | 持久化存储 |
| 缓存 | Redis (StringRedisTemplate) | 分布式缓存、分布式锁、分布式ID、GEO查询 |
| 本地缓存 | Caffeine | L1 本地热点缓存 |
| 分布式锁 | Redisson | 可重入锁、看门狗续期 |
| 消息队列 | Kafka | 秒杀异步下单、点赞事件处理 |
| 序列化 | Jackson (GenericJackson2JsonRedisSerializer) | Redis value 序列化 |
| 工具库 | Hutool, Lombok | 简化开发 |
| 构建 | Maven | 依赖管理与构建 |
模块划分
主应用 com.hmdp
| 模块 | 核心功能 | 关联文档 |
|---|---|---|
| 商铺 Shop | 缓存查询(穿透/击穿)、GEO 附近商户 | 01-Redis缓存、09-GEO |
| 博客 Blog | 发布、点赞(ZSet)、Feed流推送 | 05-Feed流 |
| 优惠券 Voucher | 三种券(普通/限购/秒杀)、秒杀下单 | 03-秒杀系统 |
| 用户 User | 登录注册、签到(Bitmap)、限流 | 06-认证鉴权 |
| 关注 Follow | 关注/取关、共同关注(Set交集) | 08-关注与社交 |
点赞子系统 com.like
独立于主应用的模块,有自己的 Controller / Service / Mapper / Event。通过 @SpringBootApplication(scanBasePackages = "com") 统一扫描。
核心特点:三层判重(布隆过滤器 + Redis ZSet + DB)、Kafka 异步持久化、本地缓存预热。
详见 → 07-点赞子系统
请求生命周期
HTTP Request
│
▼
RefreshTokenInterceptor (order=0, /**)
│ 提取 Token → Redis Hash → UserDTO → ThreadLocal
│ 刷新 Token TTL
▼
LoginInterceptor (order=1, 排除公开路径)
│ 检查 ThreadLocal 是否有用户
│ 无用户 → 401
▼
Controller
│ 参数校验,调用 Service
▼
Service
│ 业务逻辑,操作 Redis / DB / Kafka
▼
返回 Result(success/fail)
│
▼
afterCompletion: UserHolder.removeUser() 清理 ThreadLocalRedis 数据结构全景
| 数据结构 | Key 模式 | 用途 | 详见 |
|---|---|---|---|
| String | cache:shop:{id} | 商铺缓存 | 01 |
| String | login:code:{phone} | 验证码 | 06 |
| String | lock:shop:{id} | 缓存重建互斥锁 | 02 |
| Hash | login:token:{UUID} | 用户会话 | 06 |
| Hash | seckill:stock:{voucherId} | 秒杀库存/时间窗口 | 03 |
| Hash | seckill:buyCount:{voucherId} | 用户购买计数 | 03 |
| Set | seckill:order | 秒杀订单ID集合 | 03 |
| Set | follows:{userId} | 关注列表 | 08 |
| ZSet | blog:liked:{blogId} | 博客点赞用户(按时间) | 05 |
| ZSet | feed:{userId} | 用户Feed收件箱 | 05 |
| ZSet | likeZset:userId:{id} | 用户点赞文章列表 | 07 |
| ZSet | likeZset:articleId:{id} | 文章被赞用户列表 | 07 |
| Bitmap | sign:{userId}:yyyyMM | 签到记录 | 06 |
| GEO | shop:geo:{typeId} | 商铺地理位置 | 09 |
| BloomFilter | like-behavior-bloom-filter | 点赞行为快速判重 | 07 |
Lua 脚本清单
| 脚本 | 位置 | 用途 | 详见 |
|---|---|---|---|
| seckill.lua | src/main/resources/seckill.lua | 原子扣减库存、校验时间窗口和限购 | 03 |
| rollback.lua | src/main/resources/rollback.lua | 秒杀失败时回滚 Redis 数据 | 03 |
| unlock.lua | src/main/resources/unlock.lua | 分布式锁原子释放(compare-and-delete) | 02 |
| updateLikeCount.lua | src/main/resources/updateLikeCount.lua | 原子更新点赞计数 | 07 |
Kafka Topic 清单
| Topic | 生产者 | 消费者 | 用途 | 详见 |
|---|---|---|---|---|
| createOrder | VoucherOrderServiceImpl | KafkaOrderConsumer | 秒杀异步下单 | 03, 10 |
| save-order-failed-topic | KafkaOrderConsumer | KafkaOrderConsumer | 下单失败回滚 | 03, 10 |
| TOPIC_LIKE_BEHAVIOR | LikeBehaviorServiceImpl | KafkaLikeConsumer | 点赞事件 | 07, 10 |
| TOPIC_SAVE_DB_FAILED | KafkaLikeConsumer | KafkaLikeConsumer | 点赞持久化失败 | 07, 10 |
面试 Q&A
Q1: 请介绍一下你的项目
这是一个类似大众点评的社交点评平台,核心功能包括商铺查询与缓存、优惠券秒杀、博客Feed流、用户关注、点赞等。
技术上,使用 Spring Boot + Redis + Kafka + Redisson 构建。重点解决了几个典型分布式问题:
- 缓存穿透/击穿/雪崩:封装了通用 CacheClient,提供三种策略
- 高并发秒杀:Redis Lua 脚本原子扣减 + Kafka 异步落库,实现读写分离
- 分布式锁:从手写 SimpleRedisLock 演进到 Redisson
- Feed流:基于 Redis ZSet 的推模式 + 游标分页
- 点赞系统:独立子系统,布隆过滤器 + ZSet + DB 三层判重
追问:项目有几个模块?为什么点赞要独立出来?
两个 Java 根包:
com.hmdp(主应用)和com.like(点赞子系统)。点赞独立出来是因为它在架构上可以作为独立微服务拆分——有自己的 Controller、Service、Mapper、Kafka Event,与主应用只通过 UserService 共享用户数据。这种设计为将来服务化做好了准备。
再追问:两个包是怎么被 Spring 同时扫描到的?
HmDianPingApplication上配置了@SpringBootApplication(scanBasePackages = "com"),同时@MapperScan({"com.hmdp.mapper", "com.like.mapper"})扫描两个 mapper 包。
Q2: 项目中用到了哪些 Redis 数据结构?分别解决什么问题?
几乎用到了 Redis 全部核心数据结构:
- String:缓存商铺数据、验证码存储、互斥锁、计数器
- Hash:用户会话Token、秒杀库存信息
- Set:关注列表(用于 SINTER 求共同关注)、秒杀订单集合
- ZSet:博客点赞排行、Feed流收件箱、点赞记录
- Bitmap:用户签到(每天1位,空间极优)
- GEO:附近商户查询(5km 范围搜索)
- Bloom Filter(Redisson):点赞行为快速判重
追问:为什么用 ZSet 而不是 List 做 Feed 流?
ZSet 支持按 score(时间戳)范围查询 + 自动排序,而 List 只支持下标访问。Feed 流需要按时间倒序分页,且要处理同一时刻多条消息的情况,ZSet 的
ZREVRANGEBYSCORE+ offset 可以精确定位游标位置,List 做不到。
加分回答
- 能画出完整的请求链路(从 HTTP 到 Redis/DB/Kafka)说明对架构理解深入
- 主动提到项目中的设计取舍(如推模式 vs 拉模式、Lua vs 事务)展示批判性思维
- 提到点赞子系统的独立设计暗示微服务拆分思路,面试官会认为你有架构意识
- 主动列出项目中发现的 Bug(见 11-已知问题),展示代码审查能力