如何设计一个点赞系统?
- 标签:系统设计、场景题
🏛 业务架构师
点赞是典型的高并发写 + 弱一致容忍场景,我的分层设计:
1. 写路径:先扛住,再落库
- 去重:
SET like:{内容ID}:{用户ID}或 Bitmap(SETBIT like:{内容ID} {用户ID偏移} 1)——一人一赞的业务规则在存储结构上直接表达; - 计数:ZSet/hash 的 incr 计数,计数与去重双写要原子(Lua 打包);
- 点赞事件进 MQ,异步落库——用户操作只等 Redis 返回(毫秒级),持久化排队做。
2. 同步落库:批量合并
消费者攒批(每秒/每千条)update 点赞表与计数表——1 万次点赞合并为几十条 SQL,DB 压力降两个数量级;落库失败重试 + 对账补偿。
3. 读路径
- 计数读 Redis(显示赞数);
- "我是否赞过"用 Bitmap/Redis 判断;
- 榜单类(获赞最多)ZSet 承载。
4. 业务治理(比架构更重要)
- 防刷:频次限制(单用户每分钟 N 赞)、设备指纹、异常增长告警——刷赞是黑产刚需,安全预算要预留;
- 冷热分离:热门内容活跃计数走上述链路;长尾内容定期归档,计数快照化;
- 降级预案:Redis 故障时"点赞暂不可用"比"点赞打挂全站"好——非核心功能的牺牲优先级要提前声明。
一致性立场:计数短暂不精确(展示 1.2 万 vs 实际 1.2001 万)业务完全可接受——用最终一致(对账定时校准)换高并发,这是典型的"一致性换吞吐"定价。唯一不能错的是去重(一人一赞是规则)——它有一致性保障(唯一键兜底)。
一句话:点赞系统 = 去重强一致 + 计数最终一致 + 读写分层削峰。
🏗 系统架构师
点赞系统是典型的高并发写 + 实时统计场景,我按"写链路、读链路、一致性、防刷"四块设计:
1. 数据模型:点赞关系表 like(user_id, target_id, target_type, create_time),唯一索引 (user_id, target_id) 天然防重复点赞;计数表 like_count(target_id, count) 或 Redis 承载。
2. 写链路(削峰聚合):用户点赞 → 先写 Redis Set(like:{targetId} 存 user)或 Bitmap(user_id 连续时 Bitmap 省 10 倍内存)做实时判重 + ZSet 记录近期点赞 → 异步写 MQ → 消费端批量聚合落库(合并同一 target 的 N 次点赞为一次 update count = count + N)。DB 只承受聚合后的低频写入,扛住大促洪峰。
3. 读链路:计数读 Redis(like:count:{targetId} 缓存 + 定时与 DB 校准),列表读(谁赞过我)走 DB 分页或 ES。热点内容的计数用本地缓存兜一层。
4. 一致性与容灾:Redis 与 DB 最终一致——点赞结果先落 Redis 立即返回,MQ 投递失败落本地消息表补发;Redis 故障降级为"先查 DB 判重";对账任务按天校验 Redis/DB 计数差异并修复。
5. 防刷与体验:单用户单目标频控、单用户每日点赞上限、设备/黑产特征拦截;取消点赞 = 删 Set 成员 + 计数减一(同样幂等);"先点赞后取消"的抖动在聚合层做窗口合并。
核心架构思想:关系(判重)用 Set/DB 唯一约束,计数用 Redis 热路径 + 异步聚合落库,读写分离、峰值化渐。
💻 全栈开发专家
点赞看着简单,难在高并发写 + 防重 + 数据量。我的设计:
1. 存储模型(防重是核心)
sql
CREATE TABLE user_like (
id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL,
target_id BIGINT NOT NULL, -- 被赞对象(帖子/评论)
target_type TINYINT NOT NULL, -- 类型:1帖子 2评论
create_time DATETIME,
UNIQUE KEY uk_user_target (user_id, target_type, target_id) -- 唯一索引防重复点赞
);2. 高并发写:Redis 承接 + 异步落库
java
// 点赞:Set 判重 + 计数器,DB 不直接扛写
Boolean first = redis.opsForSet().add("liked:" + targetType + ":" + targetId, userId);
if (Boolean.TRUE.equals(first)) {
redis.opsForValue().increment("like:cnt:" + targetType + ":" + targetId);
mq.send(new LikeMessage(userId, targetId, type)); // 异步落库
} else {
redis.opsForSet().remove(...); // 再点一次=取消,同时计数-1、发取消消息
}3. 消费端幂等落库:唯一索引兜底(重复消息撞索引忽略),计数表 like_count(target_id, cnt) upsert。
4. 一致性与对账:Redis 计数是准实时,DB 是最终一致;定时对账(DB count vs Redis 计数)纠正漂移;Redis 挂了从 DB 重建(Set 太大可只重建计数,明细靠 DB)。
5. 大 V 场景:单条微博百万赞,Set 太大就不存明细只存计数(是否点过赞容忍"重赞一次"或落 DB 查询);计数展示可缓存 + 10 秒刷新,业务上不要求绝对实数。
关键取舍:点赞是"可丢失可重算"的数据,用最终一致换写入性能——这类判断比技术细节更值得讲。