必须用哈希标签确保SINTER的key同槽,否则集群报CROSSSLOT错误;优先用SINTERCARD替代SINTER做交集计数,避免传输冗余数据,响应更快更稳。

直接用 SINTER 是错的,尤其在集群和大数据量下
千万级用户场景下,盲目调用 SINTER 会触发 CROSSSLOT 错误或超长延迟,不是性能问题,而是架构错误。Redis Cluster 要求参与 SINTER 的所有 key 必须落在同一个 slot,而默认按 key 全名哈希,following:1001 和 following:1002 极大概率分属不同节点。
必须强制路由:把 key 改成 following:{1001} 和 following:{1002},花括号包裹的字段会被 Redis Cluster 当作哈希标签(hash tag),确保两个 key 映射到同一 slot。否则命令直接失败,不是慢,是报错。
- 不加哈希标签 → 集群环境下
SINTER99% 概率抛CROSSSLOT Keys in request don't hash to the same slot - 哈希标签写错位置(如写成
{following:1001})→ 标签失效,仍会跨 slot - 未预估集合大小 → 若某用户关注了 200 万账号,
SINTER响应可能达 200ms+,前端不可接受
SINTERCARD 比 SINTER 更适合高并发计数场景
多数前端只显示“你们有 17 位共同关注”,根本不需要返回全部 ID 列表。SINTERCARD(Redis 7.0+)专为此设计:只返回交集数量,不传输数据,网络开销归零,响应稳定在 0.1–0.5ms。
它还能带 LIMIT 参数,比如 SINTERCARD 2 following:{1001} following:{1002} LIMIT 1,只要确认“至少有 1 个共同关注”就立刻返回,用于快速判断是否值得加载详情页。
- 用
SINTER查数量 → 白拉几万 ID 只为取.size(),浪费带宽和 GC - 低版本 Redis(SINTERCARD → 改用
SCARD+SINTERSTORE临时键,但要手动清理,易留僵尸 key -
SINTERCARD不支持分页或排序 → 数量统计之后的列表展示,仍需走SINTER+ 客户端截断
缓存共同关注结果比实时计算更可靠
共同关注不是强实时数据。用户 A 刚取关 C,A 和 B 的共同关注里还短暂出现 C,只要 TTL 控制在 1 小时内,业务上完全可接受;而每次请求都算 SINTER,在 QPS 过万时,Redis CPU 会成为瓶颈。
推荐缓存 key 命名为 mutual_follow:{1001}_{1002}(注意哈希标签已含在子 key 中),TTL 设为 3600 秒。写操作(关注/取关)后,主动删掉所有含该用户 ID 的缓存:
DEL mutual_follow:{1001}_{1002} mutual_follow:{1001}_{1003} mutual_follow:{1002}_{1001}
- 不删缓存 → 旧结果残留,用户看到“已取关的人还在共同列表里”
- 缓存 key 不带哈希标签 →
DEL命令在集群下同样会CROSSSLOT - 用通配符
KEYS mutual_follow:1001_*清理 → 禁止!阻塞主线程,线上严禁
大集合交集前先做轻量过滤,避免无效计算
如果用户 A 关注了 50 万人,用户 B 关注了 3 万人,SINTER 默认以小集合为基准扫描,看似高效。但若其中大量是“已注销用户”或“被封禁账号”,这些脏数据会拖慢交集,且污染结果。
应在写入 following:{uid} 前做过滤:关注时查一次 user:status:{targetId},状态非 active 则不写入 Set。或者定期用 SSCAN 扫描 + SREM 清理无效成员。
- 不做清理 → 共同关注结果混入僵尸账号,影响推荐质量
- 用
SMEMBERS全量拉取再过滤 → 内存爆炸,Java 应用容易 OOM - 清理频率建议每天凌晨一次,用 pipeline 批量
SREM,避免单条命令压垮连接
真正卡住系统的往往不是命令本身,而是哈希标签漏写、缓存未失效、脏数据堆积这三类低级但高频的问题。Redis 的原子性掩盖了业务逻辑的脆弱性——它保证命令执行正确,不保证你用对了地方。











