redis用set存点赞状态可避免数据库行锁和多次查询,key设为"post:{post_id}:liked_users",配合lua脚本保证判断与添加原子性,再异步落库+定时校准实现高性能与一致性。

点赞状态为什么不能只存数据库
每次点一下就查一次、写一次,用户量上来后 UPDATE user_post SET likes = likes + 1 会锁行,高并发下直接拖慢整个帖子服务。更麻烦的是“已点赞”这种布尔状态,查一遍才能知道要不要加,多一次 round-trip。
用 Redis SET 存用户 ID 是最轻量的判断方式:存在即点过,不存在就能点。但得注意 SET 不是唯一选择——HSET 或 BITFIELD 在某些场景更省空间,不过对点赞这种“用户-帖子”二元关系,SET 直观、原子、调试方便。
常见错误现象:
- 把用户 ID 当字符串拼进 key 里(如
"post:123:likes:u456"),导致 key 过多、无法批量操作 - 用
INCR统计数但没同步更新 Set,造成“已点却没进榜”或“重复点”
Redis Set 的 key 设计和原子操作怎么写
key 必须能快速定位到某篇帖子的所有点赞者,推荐格式:"post:{post_id}:liked_users"。别用 "user:{uid}:liked_posts" 反向建,除非你要做“谁点过我”通知——那是另一个读路径。
立即学习“Python免费学习笔记(深入)”;
核心动作只有两个:判断是否点过、添加并计数。必须用 Lua 脚本保证原子性,否则并发时可能重复加 1 或漏判:
local liked_key = "post:" .. ARGV[1] .. ":liked_users"
local is_liked = redis.call("SISMEMBER", liked_key, ARGV[2])
if is_liked == 1 then
return {0, "already liked"}
end
redis.call("SADD", liked_key, ARGV[2])
redis.call("INCR", "post:" .. ARGV[1] .. ":like_count")
return {1, "ok"}
说明:
-
ARGV[1]是post_id,ARGV[2]是user_id(建议转成字符串,避免整型比较陷阱) - 别用
SETNX+ 单独INCR,网络延迟可能导致两次请求都通过判断 - 如果业务允许“点两次取消”,脚本要改:先
SREM再SISMEMBER判断当前状态
定时持久化不是“定期刷库”,而是“补漏+兜底”
Redis 挂了不可怕,可怕的是重启后所有 like_count 和 liked_users 全丢。但你不需要每秒都写 DB——那样反而压垮 MySQL。真正该做的,是把“新增点赞”异步落库,并用定时任务校准差异。
做法分两层:
- 每次成功点赞后,发一条消息到 Celery/RQ/Kafka,由 worker 异步执行
INSERT INTO post_likes (post_id, user_id) VALUES (?, ?),失败重试 3 次 - 每天凌晨跑一个校准脚本:对比
post:{id}:like_count和 DB 中COUNT(*) FROM post_likes WHERE post_id = ?,差值超过阈值(比如 5%)就触发全量同步
性能影响:
- 异步写库让主流程 RT 压在 5ms 内;Redis 本身不持久化 RDB/AOF 也完全 OK
- 校准脚本用
SCAN遍历 key,别用KEYS,避免阻塞
Python 客户端怎么防雪崩和连接泄漏
用 redis-py 时,默认连接池大小是 10,小流量够用,但上线后瞬间几百 QPS 就会卡住。必须显式配置:
redis_client = redis.Redis(
connection_pool=redis.ConnectionPool(
host="localhost",
port=6379,
db=0,
max_connections=50,
retry_on_timeout=True,
health_check_interval=30
)
)
容易踩的坑:
- 在 Flask/FastAPI 的 request scope 里反复 new
Redis()实例,连接数指数增长 - 没设
socket_timeout,Redis 假死时整个 Web 请求 hang 住(默认无超时) - 用
pipeline批量操作时忘了execute(),脚本一直不发,连接卡住
还有一个隐蔽问题:点赞接口被刷。别只靠前端按钮置灰,后端必须加 INCR + EXPIRE 做用户粒度频控,比如 "rate_limit:u{uid}:post",10 秒最多点 3 次。
Redis Set 看似简单,但 key 命名一错、原子性一漏、连接一松,线上就变成慢接口和数据不一致的温床。尤其要注意 Lua 脚本里别调 redis.call("KEYS") 或循环大集合——那不是缓存,是定时炸弹。










