如何用PHP结合Redis计数和数据库实现文章点赞功能?

2026-05-08 04:074阅读0评论SEO资讯
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计833个文字,预计阅读时间需要4分钟。

如何用PHP结合Redis计数和数据库实现文章点赞功能?

直接说结论:

Redis 用什么结构存点赞数和用户状态

两个 key 分开管,别塞进一个 Hash 里硬扛所有逻辑:

  • article:like_count:{article_id}:用 INCR/DECR 做原子计数,类型是 String。这是你前端显示的“XX 人点赞”来源,快且线程安全
  • article:liked_by:{article_id}:用 Redis Set 存已点赞的 user_id,比如 SADD article:liked_by:123 456。查“我点没点”就 SISMEMBER article:liked_by:123 456,O(1) 响应

不用 Hash 存状态,是因为 Hash 的 field 不能做成员存在性判断(HGET 返回 null 不代表没点过,可能是点过又取消),而 Set 天然支持 SISMEMBER + SREM + SCARD,语义清晰、操作原子。

PHP 怎么连 Redis 并做点赞/取消的原子操作

Predis 客户端(比原生 Redis 扩展更易处理 pipeline 和事务):

立即学习“PHP免费学习笔记(深入)”;

$redis = new Predis\Client([ 'scheme' => 'tcp', 'host' => '127.0.0.1', 'port' => 6379, 'password' => 'your_pass', 'database' => 0, ]); <p>// 点赞(带防重) $pipe = $redis->pipeline(); $pipe->sAdd('article:liked_by:'.$article_id, $user_id); $pipe->incr('article:like_count:'.$article_id); $result = $pipe->execute();</p><p>// 取消点赞(同样原子) $pipe = $redis->pipeline(); $pipe->sRem('article:liked_by:'.$article_id, $user_id); $pipe->decr('article:like_count:'.$article_id); $result = $pipe->execute();

注意:pipeline 不等于事务,但对这两个操作足够了;真要强一致性(比如防止超赞),得用 WATCH + MULTI,但实际中 SADD/SREM 本身已幂等,够用。

定时把 Redis 数据写回 MySQL 的坑在哪

不是简单地 SELECT * FROM article WHERE id IN (...)UPDATE 就完事:

  • 别用 SCARD 当最终点赞数 —— Redis 可能有脏数据(如用户删号后没清理 Set),要以数据库为准做校验
  • 写回时用 INSERT ... ON DUPLICATE KEY UPDATE,主键设为 (article_id, user_id),避免重复插入或丢失取消动作
  • 定时任务别卡死:用 SCAN 游标分批读 article:liked_by:*,每次最多取 1000 个 key,防止 Redis 阻塞
  • 记录最后同步时间戳到 MySQL 表里,下次只扫新增的点赞 key,别全量扫

示例 SQL:

INSERT INTO article_like (article_id, user_id, created_at) VALUES (?, ?, NOW()) ON DUPLICATE KEY UPDATE updated_at = NOW();

为什么不能只靠 Redis 持久化

Redis 是缓存,不是数据库。哪怕开了 AOF + RDB,以下情况仍会丢数据:

  • 服务器断电且 AOF 未刷盘(默认每秒刷一次,可能丢 1 秒数据)
  • 内存淘汰策略触发 maxmemory-policy,误删了点赞 key(尤其用 allkeys-lru 时)
  • 运维误操作 FLUSHALL 或配置错误导致 DB 编号错乱

所以点赞关系表(article_like)必须存在,且字段要有 created_atupdated_at,用于对账和补漏 —— 这不是备胎,是底线。

标签:PHPRedisred

本文共计833个文字,预计阅读时间需要4分钟。

如何用PHP结合Redis计数和数据库实现文章点赞功能?

直接说结论:

Redis 用什么结构存点赞数和用户状态

两个 key 分开管,别塞进一个 Hash 里硬扛所有逻辑:

  • article:like_count:{article_id}:用 INCR/DECR 做原子计数,类型是 String。这是你前端显示的“XX 人点赞”来源,快且线程安全
  • article:liked_by:{article_id}:用 Redis Set 存已点赞的 user_id,比如 SADD article:liked_by:123 456。查“我点没点”就 SISMEMBER article:liked_by:123 456,O(1) 响应

不用 Hash 存状态,是因为 Hash 的 field 不能做成员存在性判断(HGET 返回 null 不代表没点过,可能是点过又取消),而 Set 天然支持 SISMEMBER + SREM + SCARD,语义清晰、操作原子。

PHP 怎么连 Redis 并做点赞/取消的原子操作

Predis 客户端(比原生 Redis 扩展更易处理 pipeline 和事务):

立即学习“PHP免费学习笔记(深入)”;

$redis = new Predis\Client([ 'scheme' => 'tcp', 'host' => '127.0.0.1', 'port' => 6379, 'password' => 'your_pass', 'database' => 0, ]); <p>// 点赞(带防重) $pipe = $redis->pipeline(); $pipe->sAdd('article:liked_by:'.$article_id, $user_id); $pipe->incr('article:like_count:'.$article_id); $result = $pipe->execute();</p><p>// 取消点赞(同样原子) $pipe = $redis->pipeline(); $pipe->sRem('article:liked_by:'.$article_id, $user_id); $pipe->decr('article:like_count:'.$article_id); $result = $pipe->execute();

注意:pipeline 不等于事务,但对这两个操作足够了;真要强一致性(比如防止超赞),得用 WATCH + MULTI,但实际中 SADD/SREM 本身已幂等,够用。

定时把 Redis 数据写回 MySQL 的坑在哪

不是简单地 SELECT * FROM article WHERE id IN (...)UPDATE 就完事:

  • 别用 SCARD 当最终点赞数 —— Redis 可能有脏数据(如用户删号后没清理 Set),要以数据库为准做校验
  • 写回时用 INSERT ... ON DUPLICATE KEY UPDATE,主键设为 (article_id, user_id),避免重复插入或丢失取消动作
  • 定时任务别卡死:用 SCAN 游标分批读 article:liked_by:*,每次最多取 1000 个 key,防止 Redis 阻塞
  • 记录最后同步时间戳到 MySQL 表里,下次只扫新增的点赞 key,别全量扫

示例 SQL:

INSERT INTO article_like (article_id, user_id, created_at) VALUES (?, ?, NOW()) ON DUPLICATE KEY UPDATE updated_at = NOW();

为什么不能只靠 Redis 持久化

Redis 是缓存,不是数据库。哪怕开了 AOF + RDB,以下情况仍会丢数据:

  • 服务器断电且 AOF 未刷盘(默认每秒刷一次,可能丢 1 秒数据)
  • 内存淘汰策略触发 maxmemory-policy,误删了点赞 key(尤其用 allkeys-lru 时)
  • 运维误操作 FLUSHALL 或配置错误导致 DB 编号错乱

所以点赞关系表(article_like)必须存在,且字段要有 created_atupdated_at,用于对账和补漏 —— 这不是备胎,是底线。

标签:PHPRedisred