zset不适合直接存全量好友列表,因大数据量下范围查询延迟陡增且高频写入放大内存碎片和cpu开销;应分片+时间戳拼接score确保单调递减、避免同分乱序;只存活跃好友,冷数据走关系库兜底;用pipeline、zincrby和lua脚本优化批量更新与原子操作;精简member和整型score降低内存占用。

为什么ZSet不适合直接存全量好友列表
直接把几万甚至几十万好友塞进一个 ZSET 做排序,不是功能问题,而是结构误用。ZSet 的 ZRANGE、ZREVRANGE 等范围操作在数据量大时会触发 skiplist 全量遍历前 N 项,延迟陡增;更关键的是,每次好友关系变更(如新增/删除/互动分更新)都要 ZADD 或 ZREM,高频写入会放大内存碎片和 CPU 开销。
用分片 + 时间戳拼接 Score 规避重复与抖动
Score 相同会导致按 member 字典序排序,好友名或 ID 字典序毫无业务意义,极易造成“同分用户排名乱跳”。正确做法是让 Score 成为复合值:
- 把原始权重(如最近互动分)放大后取整,避免浮点精度误差
- 拼接毫秒级时间戳(如
(score * 10000) + (1672531200000 - timestamp_ms)),确保严格单调递减——时间越新,Score 越大,且绝不重复 - 这样即使两人互动分相同,也能按最新行为时间稳定排序,无需额外查
ZSCORE再比时间
只存活跃好友,冷数据走关系库兜底
真实场景中,用户真正关心的只是最近互动过的前 50~200 位好友。不要试图用一个 ZSet 扛住全部好友关系:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 用
ZADD只维护「最近 90 天有互动」的好友,过期自动剔除(配合ZREMRANGEBYSCORE定期清理) - 查询完整好友列表时,先查 ZSet 拿活跃部分,再 fallback 到 MySQL 或图数据库查剩余关系,用服务端合并
- 避免
ZCARD返回总数误导前端——ZSet 里只有活跃子集,总人数必须从关系表读
批量更新和 Lua 脚本减少往返开销
好友排序分常随点赞、评论、私信等行为实时变动,若每个行为都单独 ZADD,网络+Redis 解析成本叠加严重:
- 客户端聚合多个行为:一次请求内汇总对多个好友的互动分增量,用 pipeline 批量
ZADD - 对单个好友的多维度加分(如点赞+1、评论+3、分享+5),改用
ZINCRBY而非先ZSCORE再ZADD - 需要同时更新排名并通知前后好友时,必须用 Lua 脚本原子执行
ZREVRANK+ZREVRANGE,否则 N+1 查询在高并发下必然出现排名错位
最易被忽略的一点:ZSet 的内存占用和元素长度强相关。用 user:1001 比用完整 JSON 字符串作 member 节省 80%+ 内存;Score 尽量用整型,避免 123.456789 这类长浮点数——Redis 内部存储为 double,但序列化/比较开销翻倍。










