Skip to content

点赞模块面经文档

项目:黑马点评(类 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。