外观
点赞模块面经文档
项目:黑马点评(类 Yelp 本地生活服务平台) 技术栈:Spring Boot · Redis · MySQL · MyBatis-Plus
1. 模块概览
点赞模块的核心设计:用 Redis Set 做点赞去重 + SortedSet 做点赞排行,通过内存操作保证高并发下的性能,再异步同步到数据库做持久化。
面试常问点:「为什么不直接用数据库做点赞」「如何防止重复点赞」「点赞排行榜怎么实现」。
2. 核心设计
2.1 点赞去重(Redis Set)
Key: blog:liked:{blogId}
Value: Set<userId>
点赞:SADD blog:liked:1001 userId
取消:SREM blog:liked:1001 userId
判重:SISMEMBER blog:liked:1001 userId
计数:SCARD blog:liked:1001为什么用 Set 而不用数据库唯一索引?
- 点赞是高频操作,每次点赞/取消都走数据库会造成大量写压力
- Set 的 SADD 天然去重,SISMEMBER O(1) 判重,性能远高于 SELECT+INSERT
- 先在 Redis 操作,再异步落库,兼顾性能和持久化
2.2 点赞排行(Redis SortedSet)
Key: blog:liked:{blogId}
Score: 点赞时间戳
Member: userId
点赞:ZADD blog:liked:1001 timestamp userId
取消:ZREM blog:liked:1001 userId
Top5: ZRANGE blog:liked:1001 0 4使用 SortedSet 替代 Set 的好处:不仅能去重,还能按点赞时间排序,实现"最早点赞的人排在前面"。
2.3 数据库表设计
sql
CREATE TABLE tb_blog_likes (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
blog_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_blog_user (blog_id, user_id) -- 数据库层兜底去重
);数据库唯一索引是最后一道防线,防止 Redis 故障时出现重复数据。
3. 点赞业务流程
用户点击点赞
│
▼
SISMEMBER 判断是否已点赞?
├─ 未点赞 → ZADD 加入 + 数据库 INSERT + 博客点赞数 +1
└─ 已点赞 → ZREM 移除 + 数据库 DELETE + 博客点赞数 -1关键实现:
- 点赞数存在 Blog 表的
liked字段,通过UPDATE tb_blog SET liked = liked + 1原子更新 - 查询博客列表时,对每篇博客调用 SISMEMBER 判断当前用户是否已点赞,设置
isLike标志
4. 面试高频追问
Q1 高并发下点赞数会不会不准?
Redis 的 SADD/SREM 是原子操作,不会出现并发问题。数据库的 liked + 1 也是原子更新。两层配合保证一致性。
Q2 Redis 宕机了点赞数据会丢吗?
会有短暂丢失风险。解决方案:
- Redis 开启 AOF 持久化,降低丢失窗口
- 定时任务从 Redis 全量同步到数据库(兜底)
- 数据库唯一索引保证不会出现重复数据
Q3 为什么不用 Bitmap 做点赞?
Bitmap 适合用户量固定且密集的场景(如签到)。点赞场景下用户分布稀疏,Bitmap 会浪费大量空间。Set 按需存储,空间更高效。
Q4 点赞排行 Top N 怎么实现?
使用 SortedSet 的 ZRANGE 命令取前 N 个成员,再批量查询用户信息。时间复杂度 O(log(N) + M),性能优于数据库 ORDER BY + LIMIT。