Skip to content

朋友圈点赞功能如何实现,简单说说? ​

  • 标签:后端、系统设计、场景题

🏛 业务架构师 ​

朋友圈点赞看起来是道小题,但它是社交激励闭环的一环,我会从数据模型、可用性取舍、防刷三面答,重点讲清"点赞该选 AP 还是 CP"。

数据模型

  • 点赞关系:(用户ID, 内容ID, 时间),写 Redis Set(like:{postId} 存用户集合)+ 计数器(like:count:{postId} INCR/DECR)
  • 异步落库(MySQL 唯一索引保证幂等)做持久化和分析
  • 自己是否点过赞:SISMEMBER 毫秒级判断

核心架构判断:点赞业务选 AP

  • 点赞数偶尔少显示几个、短暂不一致,用户无感知也无伤害——它天然适合缓存抗读写、异步持久化、最终一致
  • 对比:支付就不能这么设计。识别"这个业务能不能容忍不一致"是方案分档的第一步
  • 大 V 内容百万赞:精确计数没必要,展示"1.2 万"这种模糊值 + 定期校准即可,省下的存储和计算是真金白银

必须答的工程点

  • 幂等与取消:重复点赞(Set 去重)、取消赞(SREM + DECR)的原子性,用 Lua 保证
  • 防刷:单用户对同一内容点赞频率限制、批量异常点赞(新号一天点 1 万个)进风控——点赞是刷量灰产的重点目标(买赞做营销),异常流量要能识别和清洗
  • 通知链路异步化:点赞通知走 MQ,"XXX 赞了你"的推送延迟几秒无感知

业务视角收尾:点赞数据的产品价值在于内容热度排序和作者激励——它喂给推荐系统的互动信号价值不低于阅读时长,所以落库的数据规范(维度、时间)一开始就要为分析设计,别只当关系数据存。

🏗 系统架构师 ​

点赞是高频写 + 高频读的计数问题,我按数据规模分档设计。

1. 读写模型(百万级日点赞,秒级实时)

  • 计数:Redis INCR like:post:{postId} / 点赞取消 DECR,扛写压力
  • 谁点过赞:Redis Set SADD like:post:{postId}:users {userId},用于判重、取消、展示前 N 个点赞人
  • 用户维度反查(我赞过哪些):ZSet like:user:{userId} 存帖子与时间,朋友圈"我的赞"页要用
  • 判重必须先查后写用原子性保证:Set 的 SADD 返回 0 表示已点过,直接幂等,不依赖"先查再改"

2. 异步落库(持久化与审计) Redis 只是热层:点赞事件发 MQ,消费者批量聚合落 DB(点赞关系表 post_id + user_id 联合唯一索引,计数表按 post 聚合)。批量 upsert,DB 写 QPS 降两个数量级。Redis 与 DB 的计数以 DB 为准做定时校准。

3. 亿级量级的优化

  • 计数膨胀:单条朋友圈赞数过亿不现实,但热点内容会到千万级——INCR 本身 O(1) 无所谓;真正的问题是热点 key 单分片 CPU 瓶颈,用计数分片(like:post:{id}:{0..N} 随机加分片,读时 SUM)打散
  • 关系数据归档:冷数据(30 天前的)从 Redis 淘汰到 DB/对象存储,判重走 DB 唯一索引
  • 朋友圈查看走预渲染的 feed 缓存,点赞数变化时只刷新该条缓存,避免每次读都实时聚合

一致性口径:点赞是"最终一致可接受"的典型,展示数允许短暂滞后,但判重必须强一致(否则重复点赞),这个边界要讲清楚。

💻 全栈开发专家 ​

点赞是典型的高并发写 + 读多写少,我的方案:Redis 为主存,异步落库,计数用独立结构。

数据结构

who liked:  SADD like:post:{postId} {userId}     -- 去重靠 Set
count:      like:post:{postId}:count  INCR/DECR  -- 独立计数,别实时 SCARD

核心逻辑(Lua 保证原子)

lua
-- 点赞:成功加入才计数,天然幂等
if redis.call('SADD', KEYS[1], ARGV[1]) == 1 then
    return redis.call('INCR', KEYS[2])
end
return redis.call('GET', KEYS[2])

-- 取消:SREM 成功才 DECR

异步落库

点赞消息发 MQ(或本地消息表),消费者批量写 post_like(post_id, user_id) 唯一索引兜底,同时更新 post.like_count。DB 只是审计与兜底,在线读写全走 Redis。

页面展示

  • 数量:count key,缺口用 DB 补。
  • "是否点赞过":列表页批量 SISMEMBER 不行(N 次),改用管道 pipeline 或把用户点赞过的 postId 集合另存一份 user:like:{userId} 反向 Set,双向结构换查询效率。
  • 点赞列表(谁赞过我):SMEMBERS 分页用 SSCAN,别一次全拉。

踩过的坑

  • 明星官宣场景瞬时百万点赞:先写 Redis + 限流,落库批量聚合(一条 SQL 更新计数),否则 DB 直接被打穿。
  • count 与 Set 不一致要靠定时校验任务(SCARD vs count)修复。