Redis 的 zset 可以很方便的实现一个排行榜,但是只通过单纯的 score 和 key 排序并不满足一些需要多条件排序的需求。本文将会介绍一些通过计算让 zset 的 score 支持多条件排序的方法。
一些需求中经常要我们实现一个排行榜,数据量少的话可以使用 RDB 数据库排序,数据量大可以自己实现算法或者使用 NoSQL 数据库排序,NoSQL 数据库中最方便的可能就是利用 Redis 的 zset 来实现了。 例如要实现一个玩家成就点数的排行榜:
>zadd r 100 A"1">zadd r 200 B"1">zadd r 200 C"1">zadd r 300 D"1">zrange r 0 -1 withscores1) "A"2) "100"3) "B"4) "200"5) "C"6) "200"7) "D"8) "300"
其中 B 和 C 的分数是一样的,这时我们可能想让先达到对应分数的人排在前面,相当于增加了一个排序维度。这时直接用 score 就不能实现了,需要做一些转换,这里分享几个我会用到的方法。
实现方法
假设我们需要一个三个维度的排序,第一维度是具体的数值,第二个维度是一个是否标志位,第三个维度是时间戳。其中氪金的(0否1是)和时间早的排序靠前。
以下是原始数据:
玩家 成就 是否氪金 时间戳A 100 1 1571819021259B 200 0 1571819021259C 200 1 1571819021259D 400 0 1571819021259E 200 1 1571810001259
1. 利用 key 排序
如果后两个维度初始化后就不再变化的话,可以利用 Redis 的排序特性,将不变的维度写到 key 里,这样 score 相同时会用 key 来进行排序。
# 这里反转时间戳(9999999999999 - 1571810001259),让时间早的排在前面>zadd r 100 A-1-8428180978740"1">zadd r 200 B-0-8428180978740"1">zadd r 200 C-1-8428180978740"1">zadd r 400 D-0-8428180978740"1">zadd r 200 E-1-8428189998740"1">zrange r 0 -1 withscores 1) "A-1-8428180978740" 2) "100" 3) "B-0-8428180978740" 4) "200" 5) "C-1-8428180978740" 6) "200" 7) "E-1-8428189998740" 8) "200" 9) "D-0-8428180978740" 10) "400"
以上就是第一种方法了,实现起来非常简单。
score 存储格式
在介绍下两种方法前先来看看 zset 的 score 是怎么存储的,能保留多少精度,直接看跳表 node 的结构。
/* * 跳跃表节点 */typedef struct zskiplistNode { // 成员对象 robj *obj; // 分值 double score; // 后退指针 struct