单个zset key超10万成员后zrange等命令延迟明显上升,因跳表遍历、序列化及网络传输三重开销叠加;当zcard>20万且zrange 0 99耗时>5ms、zincrby偶发超时、内存占用>100mb或debug object显示为skiplist编码时,应放弃单key方案。

单个 ZSet key 超过 10 万元素后,ZRANGE、ZREVRANGE 等命令延迟会明显上升,不是 Redis 不行,而是跳表(skiplist)的遍历开销和内存带宽瓶颈开始显现。必须从数据结构选型、访问模式、分层架构三方面动手,不能只调参或换命令。
什么时候该放弃单 key ZSet?
当出现以下任一现象时,说明已超出单 key 承载边界:
-
ZCARD key返回值持续 > 20 万,且ZRANGE key 0 99 WITHSCORES平均耗时 > 5ms(实测值,非理论) - 高频写入场景下,
ZINCRBY出现偶发超时(> 10ms),尤其在ZADD后紧跟ZREMRANGEBYRANK操作 - 内存监控显示该 key 占用 > 100MB,且
DEBUG OBJECT key返回编码为skiplist(而非ziplist)
ZRANGE 查询卡顿的根因与应对
卡顿不是因为“慢”,而是因为跳表遍历 + 序列化 + 网络传输三重叠加。例如查 Top 1000,即使 O(log N + m) 理论很快,但实际要遍历约 30–50 个跳表层级节点,再序列化成字符串数组,最后塞进响应 buffer。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 避免
ZRANGE key 0 -1 WITHSCORES这类全量拉取,哪怕 N=50 万也极易打满 Redis 带宽 - 分页必须用索引范围(
ZRANGE key start stop),不用分数范围(ZRANGEBYSCORE),后者在跳表中需二分定位起点,更耗 CPU - 客户端缓存分页结果(如 Caffeine 缓存
page_1→["a", "b", ...]),失效策略设为 TTL + 主动刷新,而非每次穿透 - 若业务允许,用
ZPOPMAX key 100替代ZREVRANGE key 0 99:前者原子弹出,避免重复遍历;但注意它会删数据,仅适用于“消费型榜单”
分片不是加个哈希就完事
哈希分片(如 user_id % 16)能缓解写入瓶颈,但读取全局 Top N 时,应用层合并排序容易翻车:
- 各分片返回的 Top 100 不代表全局 Top 100,比如 shard_0 的第 100 名 score=99.5,shard_1 的第 1 名 score=99.8,直接取各分片 Top 10 再 merge 会漏掉后者
- 正确做法是:每个分片返回 Top K(K ≥ N × 分片数),再在应用层归并排序取 Top N;K 取值需压测验证,通常设为
N * 2到N * 4 - 范围分片(按 score 拆,如
score:0-99、score:100-199)适合固定分数段业务(如等级榜),但无法处理 score 动态漂移(如积分榜),易导致热点分片 - 生产环境建议混合策略:热榜(前 500 名)走哈希分片 + 本地缓存兜底;冷榜(500 名之后)走数据库,Redis 只存摘要
zset-max-ziplist-entries 参数别乱调
这个参数只对“轻量级 ZSet”有效——即元素 ≤ 128 且每个 member 和 score 字符串长度 ≤ 64 字节。强行设为 1000 不但不省内存,反而让插入变慢(ziplist 插入是 O(n))。
- 先执行
ZRANGE key 0 -1 WITHSCORES抽样,用 Python 脚本算出典型member和score的字节数(注意:score存为字符串,"1672531200"是 10 字节) - 若抽样发现 90% 的
member是 UUID(36 字符)+ 前缀,总长 > 64 字节,直接设zset-max-ziplist-value 0,禁用 ziplist,避免无效尝试 - 真正适合调高的场景只有两种:纯数字 ID + 整数 score(如
"12345"+"87"),或短 token + 时间戳低精度截断(如"u_abc"+"1718200000")
最常被忽略的点是:ZSet 的性能瓶颈往往不在 Redis 本身,而在应用层对“全局有序”的执念。接受局部 Top N + 最终一致性,比硬扛单 key 百万级数据更可靠。滚动榜单的 key 命名、同分防乱序的时间戳嵌入、清零用 ZREMRANGEBYRANK key 0 -1 而非 DEL——这些细节比调参更能决定线上稳定性。










