# 如何设计一个点赞系统?

9 min read
Table of Contents

如何设计一个点赞系统?

如何设计一个点赞系统?

【核心定义】

点赞系统是一个高并发、高可用、最终一致性的计数与状态管理服务,其核心本质是处理海量“写-读”请求下的数据一致性、性能与扩展性平衡。

【关键要点】

  1. 数据结构设计:采用“计数+状态”分离存储,计数用 Redis String/哈希存储聚合值,用户点赞状态用 Redis Set/Hash 记录防重复。
  2. 核心算法:基于异步合并+批量写入的最终一致性模型,通过消息队列缓冲写请求,定时同步到持久化数据库。
  3. 扩容/保护机制:读写分离、缓存分层(本地缓存+分布式缓存)、服务限流与降级、数据库分库分表。
  4. 线程安全:通过 Redis 原子操作(INCR/DECR、SADD/SREM)保证单点操作的原子性,分布式锁仅用于极端并发控制。
  5. 版本差异:V1.0 同步写数据库 → V2.0 引入 Redis 缓存 → V3.0 引入异步消息队列与最终一致性。

【深度推导/细节】

1. 数据结构与存储设计

  • 计数存储:

    • Redis Key:like:count:{target_type}:{target_id}(如 like:count:post:123)
    • 值类型:String(直接存储整数)或 Hash(存储多维度计数,如点赞、收藏)。
    • 设计理由:Redis 单线程内存操作,INCR/DECR 原子性保证计数准确,O(1) 时间复杂度。
  • 用户状态存储:

    • Redis Key:like:status:{target_type}:{target_id}
    • 值类型:Set(存储用户 ID,自动去重)或 Bitmap(海量用户时节省内存)。
    • 设计理由:快速判断用户是否已点赞(SISMEMBER O(1)),避免重复点赞。

2. 核心流程与算法

Step 1:用户点赞请求

  1. 前端发送 POST /like,参数:target_type, target_id, user_id。
  2. 网关层进行身份验证与限流(如令牌桶算法,限制每秒请求数)。

Step 2:业务逻辑处理

def like(target_type, target_id, user_id):
# 1. 检查是否已点赞(防重复)
if redis.sismember(f"like:status:{target_type}:{target_id}", user_id):
return "already_liked"
# 2. 原子性执行:计数+状态更新
with redis.pipeline() as pipe:
pipe.incr(f"like:count:{target_type}:{target_id}") # 计数+1
pipe.sadd(f"like:status:{target_type}:{target_id}", user_id) # 记录状态
pipe.execute()
# 3. 发送异步消息到队列(用于持久化与业务解耦)
mq.send("like_events", {"target_type": target_type, "target_id": target_id, "user_id": user_id, "action": "like"})
return "success"

Step 3:异步持久化

  • 消费者从消息队列拉取点赞事件,批量合并后写入 MySQL。
  • 批量策略:每 1000 条或每 5 秒聚合一次,减少数据库写入压力。
  • 数据库表设计:
    -- 计数表(按 target 分片)
    CREATE TABLE like_count (
    target_type VARCHAR(32),
    target_id BIGINT,
    count INT,
    PRIMARY KEY(target_type, target_id)
    ) ENGINE=InnoDB;
    -- 状态表(按 user_id 分片)
    CREATE TABLE like_status (
    user_id BIGINT,
    target_type VARCHAR(32),
    target_id BIGINT,
    created_at TIMESTAMP,
    PRIMARY KEY(user_id, target_type, target_id)
    ) ENGINE=InnoDB;

3. 扩容与保护机制

  • 读写分离:读请求直接走 Redis(99% 场景),写请求走异步队列。
  • 缓存分层:
    • L1:本地缓存(Guava Cache,缓存热点内容点赞数,TTL=10s)。
    • L2:Redis 集群(分片存储,采用一致性哈希避免扩容抖动)。
  • 限流降级:
    • 入口层:Nginx 限流,防止恶意刷赞。
    • 服务层:Sentinel 或 Hystrix 实现熔断,当 Redis 不可用时降级为直接写队列。
  • 数据库分库分表:
    • 按 target_id 哈希分片(如 1024 个分片),解决单表数据量过大问题。

4. 线程安全与一致性

  • 原子操作:Redis INCR 和 SADD 是单线程原子操作,无需额外锁。
  • 极端场景:当需要同时更新多个关联资源时,使用分布式锁(RedLock)保证强一致性,但会牺牲性能(一般避免)。
  • 最终一致性模型:
    • 用户看到点赞成功 → 计数已更新(Redis)。
    • 后台异步同步到 MySQL(延迟 1-5 秒)。
    • 通过定时对账任务修复数据不一致(每小时扫描差异并修复)。

【关联/对比】

  • vs. 传统同步写数据库:传统方式无法承受高并发(如明星发微博瞬间百万点赞),本设计通过缓存+异步解耦,QPS 可从 1000 提升到 10 万+。
  • vs. 纯内存方案:纯内存(如 HashMap)无法持久化且无法分布式扩展,本设计结合了内存速度与磁盘持久化。
  • vs. 其他计数系统:点赞系统更注重“状态防重复”与“实时性”,而分享计数可能更注重去重算法(Bloom Filter)。

『面试官追问』

  1. Q:如果消息队列积压导致数据同步延迟,用户看到计数不一致怎么办?

    • A:采用“缓存为主,数据库为辅”的读取策略。读时优先读 Redis,如果 Redis 故障则读数据库+重建缓存。延迟在秒级可接受,同时前端可设计“软计数”(点赞后本地直接+1,避免等待)。
  2. Q:Redis 内存爆了怎么办?

    • A:分层存储策略。热点数据放 Redis,全量数据放 MySQL。对冷数据(如 3 个月前的帖子点赞状态)从 Redis 迁移到 MySQL,Redis 只保留最近活跃数据。
  3. Q:如何防止刷赞?

    • A:多维度风控。① IP 限流:每个 IP 每分钟最多点赞 100 次。② 用户行为分析:检测异常模式(如瞬间点赞不同帖子 1000 次)。③ 验证码挑战:对可疑请求弹出验证码。
  4. Q:Bitmap 和 Set 如何选择?

    • A:Bitmap 内存占用固定(每个目标 1.2MB 可支持 1000 万用户),适合海量用户场景(如微博大 V)。Set 内存随用户数增长,适合中小规模。临界点:当用户数 > 100 万时,Bitmap 优势明显。
  5. Q:分库分片键为什么用 target_id 而不是 user_id?

    • A:因为查询模式多为“查看某个内容的点赞数”,按 target_id 分片可保证同一目标的数据落在同一分片,避免跨分片查询。用户维度的查询(如“我赞过的帖子”)可通过冗余表或 ES 解决。

【直击痛点:关键数字与设计合理性】

  • Redis 批量写入间隔:5 秒/1000 条:平衡数据延迟与数据库压力,太短则数据库负载高,太长则故障时丢失数据多。
  • 本地缓存 TTL:10 秒:保证数据相对新鲜,同时避免缓存穿透击穿数据库。
  • 分片数量:1024:保证足够分散,且后续扩容可翻倍(一致性哈希)。
  • 限流阈值:单 IP 100 次/分钟:基于正常人类操作频率(每秒 < 2 次),防止脚本刷赞。

总结:一个工业级点赞系统设计需围绕“高性能、高可用、可扩展”三大原则,通过缓存原子操作+异步持久化+分层限流的核心架构,平衡并发性能与数据一致性。关键是要根据业务规模(用户量、QPS)灵活选择数据结构(Set/Bitmap)与同步策略(实时/延迟)。

My avatar

文章看完了,不打算去别的篇章转转吗?👀 感谢你的耐心阅读!如果你有问题想探讨,或者发现了文中的 Bug,请务必通过页脚的链接联系我


More Posts

# 如何设计一个秒杀功能?

7 min read

秒杀功能是一种在极短时间内应对海量并发请求,通过多层次的技术手段确保系统高可用、数据一致性,并最终实现有限库存商品公平、高效售出的分布式系统解决方案。

Read

Comments