☰
Redis ZSET排行榜:从跳表原理到亿级架构
2026/10/13 8:22:46 网站建设 项目流程

做排行榜这个需求,几乎每个业务都绕不过去。无论是游戏里的战力榜、社区里的积分榜、电商大促的销售榜,还是直播间的礼物榜,本质上都在问同一个问题:谁排第几。我维护过的几个高并发项目里,Redis Sorted Set(有序集合,以下简称 ZSET)一直是我处理榜单类需求的第一选择。这篇文章我想把 ZSET 从底层原理到亿级数据架构的完整链路讲透,特别是跳表到底是怎么工作的、为什么 Redis 选它而不是红黑树、同分排名怎么处理、单 key 到达瓶颈后怎么分片聚合。看完之后,你应该能独立设计一套能扛住亿级排行的架构方案,也能避开我在实际项目中踩过的大坑。

1. 排行榜系统的经典困境:为什么我坚持用 ZSET

1.1 先看业务需求:排行榜不只是"排个名"

很多刚接触 Redis 的人会把排行榜理解成"把数据按分数排个序,取前 N 名",但真实业务里排行榜的需求远比这复杂。我拆解过不下十个榜单类需求,它们基本都包含下面几个核心操作:

  • 单个成员的分数要能高频更新,比如直播间里用户送礼物的总额,每秒可能跳动好几次;
  • 查询某个成员的当前排名,比如"我在好友榜排第几";
  • 区间查询,比如"取第 1 到第 50 名"或者"取我前后各 10 名的用户";
  • 按分数范围过滤,比如"找出分数在 1000 到 5000 之间的所有成员";
  • 历史榜单的归档和清理,比如保留最近 30 天的日榜。

如果用传统关系型数据库做这件事,最直观的做法是给分数列建索引,然后ORDER BY score DESC LIMIT N。数据量在十万级别时,这个方案勉强能用;一旦到了千万甚至亿级,每次排序都是全量排序或者深度分页,数据库的 CPU 和 IO 压力会直接拉满。更重要的是,数据库的写事务成本很高,用户每涨一分就 update 一次,在高频写入场景下会引发锁竞争和主从延迟。

1.2 常见技术选型的对比:MySQL 查排名、内存排序和 Redis ZSET

我最早做的排行榜系统用的是 MySQL 加内存缓存。当时的思路是:数据库只负责落库,排名计算全部放到应用内存里。业务初期数据量小,每天凌晨用定时任务把全量数据加载到内存,排序后直接对外服务。但用户量涨起来之后,这种方案暴露了三个致命问题:

第一,全量排序的时间复杂度是 O(N log N),当成员数达到百万级时,每次榜单刷新都需要几百毫秒,而用户端是实时请求,根本等不起。第二,内存里的榜单是快照,存在"统计口径延迟",用户看到的排名可能落后真实数据几分钟甚至更久,运营对账的时候经常发现前后台数据不一致。第三,成员数持续增长,内存占用不断膨胀,单机内存很快就撑不住了。

对比之下,Redis ZSET 的核心优势在于,它是一个天然支持按分数排序的字典结构。ZADD 的时间复杂度是 O(log N),ZRANK 查排名也是 O(log N),取 Top N 只需要 O(log N + M)(M 是返回的数量)。这个复杂度意味着,无论你的集合里有一千个成员还是一亿个成员,单次操作的耗时差距只有几倍,而不是几万倍。而且 ZSET 的所有操作都是纯内存级别的,写和读都在微秒到亚毫秒级别,能扛住极高的 QPS 冲击。

1.3 Sorted Set 的核心能力清单

在我最终决定全面使用 ZSET 之后,我把它的能力梳理成了下面这张清单,每次和业务方对齐需求时,我都是按这张清单逐项确认的:

  • score 支持整数和浮点数,ZINCRBY 可以原子地对分数做增减,这是做实时积分系统的关键;
  • member 是唯一索引,同一个 key 下,同一个 member 只能存在一份,重复 ZADD 只会更新分数,不会新增记录;
  • 排名从 0 开始,ZREVRANK 返回的是从高到低的排名,ZRANK 返回的是从低到高的排名,注意业务口径里"第 1 名"对应的是 ZREVRANK 的 0;
  • 区间操作丰富,ZRANGE、ZREVRANGE、ZRANGEBYSCORE、ZRANGEBYLEX 可以覆盖从"按排名分页"到"按分数条件筛选"的各种场景;
  • 天然支持过期策略,对 key 设置 TTL 就能实现日榜、周榜自动过期,不需要额外开发清理任务。

这套能力组合起来,基本覆盖了绝大多数排行榜业务的底层需求,剩下的问题,就是怎么把 ZSET 用到极致。

2. 跳表原理拆解:为什么它能扛住亿级数据

标题里写了"从跳表原理到亿级排行榜架构",很多人对跳表的理解停留在"一个有序链表加了几层索引",但真正面试或者排查性能问题时,你会发现细节远比这丰富。下面这部分我会从基础开始逐步拆解,就算你现在还不熟悉跳表,也能看明白。

2.1 从单链表说起:查询为什么是 O(N)

如果一个有序集合只用普通链表存储,里面的数据按分数从小到大排列。你要找到分数为 8.5 的节点,必须从头节点开始一个个往后遍历,直到找到目标或者确认不存在。链表不像数组,它没有下标,也没法二分查找,因为内存不连续。所以单链表查询的时间复杂度是 O(N),这在数据量小的时候无所谓,数据量一上来就完全不可用了。

有的同学会想:那为什么不用数组加二分查找?因为 ZSET 要支持高频插入和删除。在数组中插入一个元素,需要把后面的元素全部后移,平均时间复杂度是 O(N);而在链表中插入元素只需要 O(1) 的指针操作。问题在于,链表虽然解决了插入删除的效率,却牺牲了查询。跳表要解决的,正是"既想快速插入删除,又想快速查找"这个矛盾。

2.2 多层索引的构建逻辑

跳表的思路非常直观:用空间换时间,在原始链表之上,再建几层"稀疏索引"。

我举个具体的例子。假设你有一个排好序的链表,里面有 16 个节点。如果在原始链表之上,抽取每隔一个节点的节点作为第一层索引,那么第一层索引只有 8 个节点。再每隔一个索引节点抽取一层,第二层只有 4 个节点。一直抽下去,最后一层只剩下 2 个节点。查询时,先从最高层开始找,逐层向下逼近,每一层都能跳过一半的节点。整体查找路径就像下楼梯一样,从顶层快速跨越大段数据,到底层时只需要做常数级比较。

在 Redis 的实现里,这个"间隔"并不是严格每隔一个节点,而是按概率决定每个节点是否晋升到上一层。每个新插入的节点,会通过一个随机数决定它的层高:有 25% 的概率升到第 2 层,有 25% 的概率从第 2 层再升到第 3 层,以此类推。这背后的概率参数是 1/4,Redis 的代码里写得很明确。

每个节点实际层高服从几何分布,期望层高大约是 1/(1-p) = 1.33 层左右。所以虽然跳表理论上会多一些指针开销,但实际平均内存开销并不离谱。对于包含 N 个元素的跳表,查询、插入、删除的期望时间复杂度都是 O(log N)。

2.3 Redis 为什么用跳表而不是红黑树

这是面试里出现频率很高、实际工程里也值得思考的问题。红黑树在内存里也是有序结构,理论上查找、插入、删除同样是 O(log N),那 Redis 为什么选了跳表?

我的理解有四个层面。

第一,实现难度和维护成本。红黑树要维护颜色、旋转、重新着色,代码复杂度高,出 bug 的概率大;跳表的实现逻辑清晰,每一层都是独立的链表,插入删除只需要处理相邻节点指针,代码审查和问题排查都简单得多。

第二,区间查询的友好度。排行榜场景非常依赖范围遍历,比如 ZRANGEBYSCORE 要返回一段分数区间内的所有元素。在跳表里,找到区间的起始节点之后,沿着最底层的链表往后遍历就行,甚至不需要回溯。红黑树虽然也能做中序遍历找区间,但过程明显更繁琐。

第三,并发场景下的扩展性思维。虽然 Redis 是单线程模型,但在抽象层面,跳表的层间结构天然更容易做细粒度锁优化(每个节点独立),而红黑树的旋转操作往往涉及多个节点的联动。Redis 本身的单线程使得这个优势没有完全发挥,但作为数据结构选型,它依然是合理考量。

第四,空间占用可接受。前面提到过,跳表平均每个节点只有大约 1.33 层指针,考虑到 Redis 本身的指针是 8 字节,额外开销有限。相比平衡树节点里存储的颜色标记和复杂的父子关系,跳表的空间使用反而更可控。

2.4 哈希表与跳表并存:ZSET 的双结构设计

很多人以为 ZSET 底层只有跳表,其实它内部同时维护了一个哈希表和一个跳表。

哈希表负责按 member 精确查找分数。比如你要查 ZSCORE key member,这一步如果走跳表,需要从顶层开始逐层查找,时间复杂度仍然接近 O(log N);但有了哈希表,直接按 member 哈希定位,O(1) 就拿到分数了。ZADD 更新已有成员时,也是先用哈希表快速定位老分数,再决定是更新跳表还是直接更新节点,避免了跳表内的重复查找。

跳表则负责按分数排序的所有操作:ZRANGE、ZRANK、ZRANGEBYSCORE、ZCOUNT 等等。两个结构共享同一份 member 和 score 数据,通过指针引用关联。我在排查问题时就发现,凡是涉及 ZSET 的内存估算,不能只算跳表,还要把哈希表本身的开销算进去,这个细节在容量评估时很关键。

3. 基础命令的正确打开方式:分数、排名与边界条件

理论讲完了,回到实际使用。ZSET 的命令不多,但每个命令里都藏着一些容易踩坑的边界条件。这一节我把高频命令按"场景"重新组织,并标注出我踩过的坑。

3.1 ZADD/ZINCRBY:写入中的类型与精度坑

ZADD 的基本用法是ZADD key score member [score member ...]。这里第一个隐藏坑是score 的类型是 double。Redis 官方文档说 score 是 double 类型,但在实际传输和存储中,它会被当成字符串解析。如果你用 64 位整数做分数,注意 double 只能精确表示绝对值不超过 2^53 的整数,超过这个范围就会产生精度丢失。

我之前处理过一个游戏战力榜,战力值本身是 bigint,出于习惯我直接把 JSON 里的数字传给了 ZADD。结果上线第二天,运营发现两个玩家战力不同,排名却一样,原因就是超过 2^53 后 double 无法区分这两个整数,Redis 保存的时候把两个不同的值当成了同一个。解决方式很简单:把分数改成"主分数 + 辅助排序字段"的组合编码,或者所有操作都限定在 2^53 以内,不要直接用海量整数当 score。

另一个写入类的常用命令是 ZINCRBY,它的作用是"给指定 member 的分数增加一个增量"。这个命令特别适合直播礼物实时榜:用户送礼时直接ZINCRBY gift_rank:20250101 10 user_123,Redis 内部会先找到这个 member 的老分数,再加上增量,写回。整个过程是原子的,不需要分布式锁,也不会有并发覆盖的问题。

3.2 ZRANK/ZREVRANK 与 ZRANGE/ZREVRANGE:排名系统的关键差异

ZRANK 返回的是"从低到高"的排名,也就是分数最低的是 0,分数最高的是倒数第一;ZREVRANK 返回的是"从高到低"的排名,分数最高的是 0。业务里常说的"第 1 名"在代码里其实是ZREVRANK的返回结果加一。

很多人会把 ZRANK 和 ZRANGE 搞混。ZRANK 返回的是排名序号(一个整数),ZRANGE 返回的是成员列表(可以带分数)。两者的组合关系是:要拿"排名区间内的成员",正确的做法是 ZRANGE/ZREVRANGE;要拿"某个成员的排名",才用 ZRANK/ZREVRANK。如果业务需求是"从第 100 名到第 200 名",ZREVRANGE 的 start 和 stop 可以直接传 100 和 200,非常高效。

还有一个细节值得注意:ZRANGE 在 Redis 2.8 到 6.0 的版本里,返回的成员顺序是按分数升序的;6.2 版本引入了REV参数后,可以直接用ZRANGE key start stop REV替代 ZREVRANGE。如果你维护的 Redis 版本较老,代码里应该统一用 ZREVRANGE,不要混用,否则语义容易出错。

3.3 ZRANGEBYSCORE/ZREM/TTL:运营场景里的高频组合

运营后台经常需要做"按分数阈值筛选"的操作,比如找出所有分数大于 1000 的用户。这个场景对应的是ZRANGEBYSCORE key min max,它支持开闭区间,语法是(min表示不包含 min。比如ZRANGEBYSCORE rank (1000 +inf表示取出分数大于 1000 的所有成员。

这里有个容易踩的坑:+inf和-inf不是字符串,是特殊的无穷大符号。如果你在自己的代码里拼 SQL 习惯了,很容易把+inf当成一个普通变量去拼接,然后在 Redis 客户端里收到语法错误。正确写法是原样传给命令,比如在 Redis 命令行里直接写ZRANGEBYSCORE rank 1000 +inf。

ZREM 用于删除指定 member,比如用户注销或者榜单刷票被清退。如果需要在删除的同时清掉它的排名记录,ZREM 就够了,不用额外维护一份"黑名单"。

最后是 TTL。日榜的 key 我一般设置 TTL 为"当天结束时间加一个缓冲"。比如现在是早上 10 点,当天榜单设 TTL 为 24 小时,第二天同一时间自动过期。周榜、月榜同理。这里注意,ZINCRBY 和 ZADD 不会重置 TTL,所以如果你在榜单一周后仍然往里面写数据,而这个 key 的 TTL 设置的是 7 天,那么第 8 天这个 key 就没了。最常见的做法是定期用EXPIRE重新刷新过期时间,或者干脆把"周"做成业务 key 的一部分,比如rank:2025W03,这样天然不会混淆。

4. 亿级排行榜架构设计:从单 key 到分片扩容

当集合内成员数从百万涨到亿级时,单 key 的 ZSET 会开始暴露问题。这一节我会从一个实践者的角度,完整讲述我是怎么把 ZSET 的架构从"一把梭"演进到"分片聚合"的。

4.1 先算账:数据量、内存与 QPS 的工程预算

在谈任何架构设计之前,先要会做工程预算。一个亿级成员的 ZSET,大概占多少内存?我用的估算公式是:

  • 每个 member 平均按 20 字节算;
  • 每个 score 是 double,8 字节;
  • 跳表节点的平均指针开销大约是 16 到 24 字节(按层数和 Redis 的指针大小综合估算);
  • 哈希表表项的开销再加 10 到 16 字节。

综合下来,每个成员大约需要 60 到 100 字节。一亿个成员,内存大约在 6GB 到 10GB 之间。这个数字本身并不可怕,但如果你的 Redis 实例同时承载其他业务,或者你的集群还有主从复制放大,单 key 内存达到 10GB 的时候,迁移、持久化、主从同步都会变得非常难控制。

QPS 预算同样关键。ZADD、ZINCRBY 这类写操作在单线程 Redis 上单机大约能跑到 10 万 QPS 以上,但 ZRANGEBYSCORE 这种扫描区间较大的命令会消耗更高的 CPU。如果业务峰值 QPS 5 万,其中 20% 是范围查询,单节点可能还能扛;但如果全部流量都打在一个 key 上,就会不可避免地把单核 CPU 打满。Redis 的单线程模型决定了:一个 key 越热,它对整个实例的影响就越大。

4.2 拆分策略:业务纬度分桶、时间分桶、冷热分离

单 key 到达瓶颈后,我一般会按三种维度做拆分。

业务维度分桶只适用于业务本身有天然区分度的情况。比如游戏里既有全服榜又有好友榜,全服榜是全局 ZSET,好友榜不需要建全局,只要在拉取好友数据时临时把好友的分数聚合起来排序即可。不要把好友榜做成全量 ZSET,否则一亿成员里 99.9% 的数据跟当前用户无关,纯属浪费。

时间维度分桶是处理日榜、周榜最自然的方式。key 的名字直接带上日期或者周标识,比如rank:daily:20250107、rank:weekly:2025W01。每天的 key 各管各的,历史 key 到了 TTL 自动过期。这种设计的额外好处是,运营如果要"昨日榜"和"今日榜"对比分析,只需要分别读两个 key,不需要从同一个 key 里做时间筛选。

冷热分离解决的是历史数据堆积问题。榜单的热度往往集中在最近一周或者一个月,三个月前的数据几乎没人访问,但它们仍然占用内存。我的做法是:热榜保留在 Redis,历史榜单定期用后台任务把 ZSET 里的数据导出到归档存储(关系型数据库或者对象存储)。需要查历史排名时,走归档查询通道,而不是直接打 Redis。这样 Redis 的内存始终控制在可预测的范围内。

4.3 聚合层设计:叶子榜单与总榜的关系

当业务没办法只在时间或业务维度上拆分时,就需要做水平分片 + 聚合。我把这种架构称为"叶子榜单 + 总榜"两层结构。

叶子榜单是分片后的多个 ZSET,比如按成员 ID 哈希取模,把一亿个成员分到 100 片,每片只有一百万成员。每个成员写入时,确定性路由到对应的叶子分片,分数的更新只发生在叶子分片上。总榜则是一个单独的聚合 ZSET,它并不实时包含所有成员的分数,而是由一层聚合器周期性从叶子分片汇总生成。

这里要特别注意"总榜的时效性"和"叶子榜的实时性"之间的取舍。如果业务允许总榜有 1 到 5 分钟的延迟,聚合器可以每 5 分钟跑一次,把所有叶子分片 Top 100 拉出来做归并,得到全局 Top 100。因为全局 Top 100 一定只可能出现在某个叶子分片的 Top 100 中,所以归并时只需要每片取 Top 100,总归并量是分片数乘以 100,非常小。这种"分片 Top K 归并"的方案,可以在一亿成员的规模下,让总榜查询的耗时保持在毫秒级。

如果业务要求总榜也必须实时一致,那就不能靠归并了,只能维持一个全局 ZSET,由所有写入事件同时写全局 ZSET。这个形态实际上回到了单 key 模式,只不过你可以通过主从复制和读写分离来分散读压力,而写压力仍然集中在主节点上。所以我的建议是:在业务层面明确一致性与实时性的目标,不要一刀切追求"全实时"。

4.4 同分排名与先到先得:复合分数的设计

排行榜业务里最容易被忽略的需求是同分怎么排。Redis 默认的规则是,当两个成员分数相同时,按 member 的字典序升序排列。但是业务方往往想要的是"同分情况下,先达到这个分数的人排前面",也就是先到先得,这跟字典序完全是两回事。

解决思路是把单值分数改成复合分数:假设主业务分数是主分,想要先到先得,就再拼接一个时间维度。我会把 score 设计成一个大整数,其结构为:

score = 主业务分 * K + (K - 时间戳偏移量)

其中 K 是时间戳可能取到的最大值的上界。举个例子,如果业务主分最大是 10000,时间戳用秒级,一天的秒数是 86400,我可以把 K 设为 100000,那么:

  • 分数 9500、时间戳较早的人,score = 9500 * 100000 + (100000 - 时间戳)
  • 分数 9500、时间戳较晚的人,score = 9500 * 100000 + (100000 - 更晚的时间戳)

后面这个加项会相对更小,于是先到者的复合分更高,在 ZREVRANGE 里自然排前面。这个方案的优点是完全不用改 Redis 数据结构,所有排序逻辑都塞进 score 里;缺点是要注意复合分数的总长度不能超过前述 2^53 的精度上限,否则白搭。所以我一般会预估好主分范围和最大时间跨度,选择一个保证总位数在 15 位以内的 K。

5. 性能优化与两个真实事故复盘

理论架构说完,下面分享两个我实际遇到的事故。这两个问题都让我花了很长时间排查,写出来是希望大家不要重蹈覆辙。

5.1 事故一:double 精度泄漏导致排行榜分数漂移

那是某次游戏活动榜,运营反馈说玩家 A 和 B 明明战力相同,但 Rank 排名会随机变化。我第一反应是 Redis 排序不稳定,查了一圈资料发现稳定排序没问题。后来用ZSCORE逐个读两名玩家的分数,发现他们存的分数看起来一样,但用严格的字节比较后数值不同。

原因是前端把战力值作为浮点数传给了接口,后面经过 JSON 解析,double 在存储时产生了细微的精度误差。比如玩家 A 的战力是 123456789012345678,玩家 B 是 123456789012345679,两个数在 double 里都被表示成同一个近似值。Redis 保存后看起来分数一致,但跳表内部做比较时按照二进制浮点数解释,表现反而不是稳定的。

这次事故之后,我定了一条硬性规范:所有进入 Redis ZSET 的 score 必须经过整数化处理。业务分如果是浮点数,先乘以一个缩放因子(比如乘以 1000)转成整数,再写入;如果超过 2^53 精度范围,就把业务分拆成高低位,或者改用复合分数编码。这类问题在测试环境很难复现,因为数据量少、分数差异小,但在线上亿级数据里一碰一个准。

5.2 事故二:深分页查询拖垮 Redis 节点

某段时间线上出现了一次 Redis 节点 CPU 飙升的告警。排查后发现,运营后台的一个榜单页面,没人看前 100 名,反而点了第 10001 到 10100 名。这个请求对应的是ZREVRANGE rank 10000 10099。在 Redis 2.8 到 6.0 的版本里,ZREVRANGE的复杂度是 O(log N + M),M 是返回元素数量。我们要返回 100 个元素,但它的内部实现需要从分最高的元素开始遍历到第 10000 个元素,才能拿到后面的 100 个。虽然复杂度看起来不吓人,但这意味着每次请求都要遍历 10000 个节点,CPU 消耗是取前 100 名的 100 倍。

这个问题的本质不是 Redis 慢,而是深分页打到了 Redis 的线性扫描能力上。我的解决方案有三层:

  • 运营后台页面限制最大分页深度,超过 500 名就不允许翻页,强制用"上一名/下一名"的游标模式;
  • 对于需要看"腰部门"排名的场景,改用ZRANGEBYSCORE的 min/max 参数配合 LIMIT,尽量从分数区间内取数据,而不是从队首开始数位置;
  • 对超高频访问的 Top 100,在 Redis 前面加一层本地缓存,过期时间 5 秒,极大降低对 Redis 的读压力。

那次事故之后,团队内部还专门写了个规范:凡是排名类接口,禁止出现超过固定阈值(比如 5000)的分页参数。运营要看深排名,走游标滚动或者异步导出,而不是实时打透 Redis。

5.3 热 key 治理与容量评估

ZSET 的排行榜天然是热 key,尤其是全服总榜。所有用户端入口都读同一个 key,瞬间高并发时会让单分片 CPU 打满。我的热 key 治理路径一般是这样:

先通过客户端采集 key 的访问频次,确认哪些 ZSET key 是热 key。然后分级处理:一级是把热点 key 的读请求复制到一个只读副本上,用读写分离稀释主节点压力;二级是在业务侧加进程内缓存;三级是对整个榜单做分片,把热 key 拆成多个子 key。分片后再合并排名时,对前端暴露一个统一接口,后端用上一节说的聚合方案来获取最终榜单。

容量评估方面,我建议每次上榜单类需求前都做一次"最坏情况内存推演"。比如一亿 user 进 ZSET,每个 user ID 12 字节,总量内存 7GB,主从复制双份就是 14GB。如果集群内存不够,宁可提前做好分片,也不要等出故障再扩容。Redis 的 ZSET 不是无限制膨胀的容器,内存上限就是数据规模的上限。

6. 哪些场景不适合用 Sorted Set

不要误会,我不是让你在所有排序场景里都用 ZSET。下面这几类场景,我反而会建议你用其他方案。

6.1 频繁更新的海量会员列表

如果数据更新频率极高,而且每次更新不只改分数,还要改大量属性字段,比如用户头像、昵称、等级图标,那 ZSET 就不是好的选择。ZSET 的 member 只能存一个字符串,你不可能把整个用户对象塞进去,通常只存 user_id,然后用另一个 hash 或者数据库去关联用户信息。这会引入两次读取,比一次读取要慢。这种情况下可以考虑用数据库做排序,或者把属性字段缓存到独立的哈希表里,通过批量管道读取来降低延迟。

6.2 需要多维查询排序的榜单

如果业务要求"既要按总积分排,又要按本周积分排,还要按完成任务数排",还把三种分数放在同一个 ZSET 里,用三个不同的 key 去维护,你会发现数据一致性很难保证。因为同一个 member 在三个 key 里的更新时机不同,会出现某个榜已更新、另一个榜还没更新的情况。

这种场景我建议直接用关系型数据库处理,通过多条索引列完成多维排序;或者使用专门的列式存储做分析查询。ZSET 适合"单维分数"榜单,不适合"多维度动态切换"的复杂查询场景。

6.3 我最后的几条个人建议

如果你现在正准备设计一个排行榜系统,我会把最重要的经验浓缩成这几句话:

第一,先定义清楚业务分数和排名的口径,是把"分数高的人排在前面"还是"达到同样分数先来者优先",这个决定会直接影响 score 编码。第二,不要把超过 2^53 的数直接塞进 score,一定要做精度预算或者复合编码。第三,每次上线前做一次容量和 QPS 推演,单 key 模式在百万级可以横着走,到了千万级就要开始想分片和聚合方案。第四,线上问题优先查 key 的热度和慢命令耗时,ZSET 的坑多数是深分页、大 key 和精度问题,这三类占了九成以上。

我个人在实际项目中的体会是,Redis Sorted Set 是一个上限非常高的数据结构,只要前期把分数模型设计好,后期你会省下大量的维护成本。希望这篇文章能让你对它有一个完整的理解,后面实际动手的时候能少踩几个坑。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询