点赞状态判断不能只查Redis Set大小,因为SCARD仅返回总数而无法判断当前用户是否已点,正确做法是用SISMEMBER直接检查指定用户ID,O(1)时间复杂度;需同时获取总数和状态时应并发执行SCARD与SISMEMBER。

点赞状态判断为什么不能只查 Redis Set 大小
因为 SET 本身不记录「谁点过」,只存用户 ID;但业务上常需判断「当前用户是否已点」,光靠 SCARD 返回总数无法回答这个问题。常见错误是用 SCARD + 全量拉取再本地遍历,这在用户数多时直接拖垮 Redis 和应用内存。
- 正确做法是用
SISMEMBER直接查指定用户 ID 是否在集合中,O(1) 时间复杂度 - 如果同时需要「总点赞数」和「当前用户状态」,应并发执行
SCARD和SISMEMBER,避免串行等待 - 注意:Redis 的
SET不支持过期时间,若需自动清理(比如 30 天后失效),得换Sorted Set或配合定时任务
Java 里用 Jedis 操作点赞 Set 的关键写法
Jedis 是最轻量的 Redis 客户端之一,适合高频读写场景,但默认不带连接池,容易在并发下抛 JedisConnectionException。
- 必须用
JedisPool管理连接,配置maxTotal=50、minIdle=5等参数,否则压测时连接耗尽 - 点赞操作建议封装成原子命令:
SADD+SISMEMBER组合不行,要用 Lua 脚本保证「先判重再加」不出现重复计数 - 示例 Lua 脚本:
if redis.call("SISMEMBER", KEYS[1], ARGV[1]) == 1 then return 0 else redis.call("SADD", KEYS[1], ARGV[1]); return 1 end - Java 调用:
jedis.eval(script, Collections.singletonList("post:123:likes"), Collections.singletonList("user:456"))
为什么不用 String + JSON 存用户列表
有人图省事把点赞用户 ID 拼成字符串存在 String 类型里,比如 "[\"u1\",\"u2\"]",看似简单,实际埋雷。
- 每次新增都要 GET + 解析 + 判重 + 序列化 + SET,网络往返 + CPU 开销双高,QPS 上不去
- 并发写入时无原子性,两个请求同时读到旧值、各自加一个 ID、再写回去,必然丢数据
- 内存占用反而更大:JSON 字符串比 Redis
SET内部的 intset/hashtable 结构更冗余,尤其用户 ID 是数字时 - 查询「某用户是否点过」要全量反序列化,无法利用 Redis 原生 O(1) 查找能力
冷热分离:点赞数缓存与 Set 数据如何保持一致
前端常要展示「点赞数」,而 SCARD 在大集合上虽快,但频繁调用仍占带宽;于是有人加一层本地缓存(如 Caffeine),但容易脏。
立即学习“Java免费学习笔记(深入)”;
- 不要让应用层维护「点赞数」变量,它永远滞后;所有读取都应以 Redis
SCARD为准,缓存只是降级手段 - 写操作必须同步更新:Lua 脚本成功返回 1 时,才触发本地缓存的
invalidate,而不是等异步消息 - 如果 Redis 集群分片,确保同一帖子的所有点赞数据落在同一个 slot(用
{post:123}这种哈希标签),否则SCARD无法跨节点聚合
SADD,而是想明白「什么时候不该查总数」「哪一步必须原子」「缓存到底该信谁」。










