ZSet 是 Redis 最容易被神化的数据类型之一。排行榜、延迟队列、滑动窗口、优先级任务都能往它身上套,但它并不是一个没有代价的排序容器。
ZSet 的关键在于同时满足两类访问:按 member 找 score,以及按 score 找排名和范围。小 ZSet 可以用 listpack,规模上来后则要同时维护 dict 和 skiplist。
先把机制边界说清楚
这一篇只讲 ZSet 的排序结构和工程边界,不把所有延迟任务问题都归结为 ZSet。延迟任务还需要 ack、重试、幂等和补偿,这些不是 ZSet 本身提供的。
整体路径
上面这张图先把主线铺开:dict 查成员,skiplist 查排名,一次写入维护两份结构。读 Redis 这类系统,最重要的是别只停在命令接口,要继续追问它在内存里是什么形状、在主线程上走多远、失败时会留下什么状态。
底层机制
- 小 ZSet 用 listpack 顺序存储 member/score,节省指针和节点开销。
- 大 ZSet 使用 dict + skiplist:dict 负责按 member 查 score,skiplist 负责排序、范围和排名。
- 跳表用随机层高换取实现简单和范围遍历便利,平均复杂度接近平衡树。
- 一次 ZADD、ZREM 或分数更新要同时维护两套结构,写入成本高于普通 Set。
这些机制放在一起看,就能把「这个命令能不能用」改成「这个命令在当前数据规模下还便不便宜」。Redis 的很多坑,不是命令本身错了,而是数据规模和访问方式已经越过了它的舒适区。
取舍与边界
ZSet 的优势是范围查询和排名,弱点是深分页、超大范围返回和频繁重排。它适合热榜窗口,不适合无限历史排名。
典型问题:用机制化例子排查
- 排行榜要有时间窗口或分桶,避免一个 ZSet 无限增长。
- 分页尽量用分数游标,不要长期依赖深 offset。
- 延迟队列要额外设计消费确认和失败重试,不要只靠
ZRANGEBYSCORE+ZREM。 - 慢查询里看到大范围
ZRANGE、ZREMRANGEBYSCORE时,先看返回量和 key 大小。
收束:一句判断
ZSet 能让排序变简单,但不会让排序成本消失。
关于十三Tech
我是十三,All in AI Agent 方向的架构师,专注 AI 工程实践。
我相信 AI 是程序员的最佳搭档,也希望帮助每一位开发者更好地驾驭 AI。
如果你想继续跟完这套「图解 Redis」,欢迎关注公众号 「十三Tech」。后续会继续按数据结构、底层机制、持久化、高可用和实战排查这条线更新。

