|
| 1 | ++++ |
| 2 | +date = '2025-08-30T8:00:00+08:00' |
| 3 | +draft = false |
| 4 | +title = 'Redis Ordered Set' |
| 5 | +tags = ['Redis', 'NoSQL'] |
| 6 | ++++ |
| 7 | + |
| 8 | +Redis 的有序集和(ordered set)同时具有"有序"和"集和"两种性质, 这种结构中每个元素都由一个成员和一个与成员相关联的分值组成, 其中成员与字符串方式存储, 而分值以64位双精度浮点数格式存储. |
| 9 | + |
| 10 | +例如下面一个记录薪水的集和: |
| 11 | +| 成员 | 分值 | |
| 12 | +| :--- | :--- | |
| 13 | +| "perter" | 3500 | |
| 14 | +| "bob" | 3800 | |
| 15 | +| "jack" | 4500 | |
| 16 | +| "tom" | 5000 | |
| 17 | +| "mary" | 5500 | |
| 18 | + |
| 19 | +与集和一样, 有序集和中的元素都是唯一的, 同时, 成员将按照分值大小进行排序. |
| 20 | +有序集和分值除了可以是数字外, 还可以是字符串 "+inf" 或者 "-inf", 这两个特殊值分别表示无穷大和无穷小. |
| 21 | +虽然有序集和的成员不可相同, 但是分值可以是相同的, 当两个或多个成员拥有相同的分值时,Redis 将按照这些成员在字典序中的大小对其进行排列. |
| 22 | + |
| 23 | +有序集合是Redis提供的所有数据结构中最为灵活的一种, 它可以以多种不同的方式获取数据, 比如根据成员获取分值、根据分值获取成员、根据成员的排名获取成员、根据指定的分值范围获取多个成员等. |
| 24 | + |
| 25 | +- ZADD: 添加或更新成员 |
| 26 | + ``` |
| 27 | + ZADD sorted_set socre number [score number ...] |
| 28 | + ``` |
| 29 | + 默认情况下, ZADD 命令将返回成功添加的新成员数量作为返回值, 对于更新操作会返回0(未添加新成员). |
| 30 | + 使用 `XX | NX` 选项来显示地指示命令 只更新 或 只添加操作 |
| 31 | + ``` |
| 32 | + ZADD sorted [XX|NX] socre member [socre member ...] |
| 33 | + ``` |
| 34 | + 若要返回所有被修改的成员数量(新添加 + 更新数量), 可使用 CH 选项 |
| 35 | + ``` |
| 36 | + ZADD sorted_set [CH] socre number [score number ...] |
| 37 | + ``` |
| 38 | + 复杂度: O(M * log(N)) 其中 M 为给定成员数量, N 为有序集和的成员数量 |
| 39 | +
|
| 40 | +- ZREM: 移除指定的成员 |
| 41 | + ``` |
| 42 | + ZREM sorted_set member [member ...] |
| 43 | + ``` |
| 44 | + ZREM 会返回被移除成员的数量作为返回值, 如果给定的某个成员不存在, 则会自动忽略该成员. |
| 45 | + 复杂度: O(M * log(N)), 其中 M 为给定成员的数量, N 为有序集和中包含的成员数量. |
| 46 | +
|
| 47 | +- ZSCORE: 获取成员的分值 |
| 48 | + ``` |
| 49 | + ZSCORE sorted_set member |
| 50 | + ``` |
| 51 | + 如果给定的 member 不存在, 将返回空值 |
| 52 | + 复杂度: O(1) |
| 53 | +
|
| 54 | +- ZINCRBY: 对成员的分值进行自增或自减操作 |
| 55 | + ``` |
| 56 | + ZINCRBY sorted_set increment member |
| 57 | + ``` |
| 58 | + 该命令完成操作后, 将返回成员当前分值, increment 为正数时就是自增操作, 为负数时就是自减操作. |
| 59 | + 如果命令给定的有序集和或者成员不存在, 则相当于 ZADD 命令, 会自动创建对应的值. |
| 60 | + 复杂度: O(log(N)) |
| 61 | +
|
| 62 | +- ZCARD: 获取有序集和的大小 |
| 63 | + ``` |
| 64 | + ZCARD sorted_set |
| 65 | + ``` |
| 66 | + 返回集和中成员数量, 若不存在则返回0. |
| 67 | + 复杂度: O(1) |
| 68 | +
|
| 69 | +- ZRANK、ZREVRANK: 取得给定成员在有序集和中的排名 |
| 70 | + ``` |
| 71 | + ZRANK sorted_set member # 升序 |
| 72 | + ZREVRANK sorted_set member # 降序 |
| 73 | + ``` |
| 74 | + 若给定的集和或者成员不存在, 则返回空值. |
| 75 | + 复杂度: O(log(N)) |
| 76 | +
|
| 77 | +- ZRANGE、ZREVRANGE: 获取索引范围内的成员 |
| 78 | + ``` |
| 79 | + ZRANGE sorted_set start end # 升序 |
| 80 | + ZREVRANGE sorted_set start end # 降序 |
| 81 | + ``` |
| 82 | + 索引范围为 [start, end], 即这两个索引上的成员也会包含在命令返回的结果当中, 索引从0开始. 此外, 还可以使用负数索引, 索引最后一个从 -1 开始. |
| 83 | + 默认情况下, ZRANGE 和 ZREVRANGE 命令只会返回指定索引范围内的成员, 如果用户想要获取这些成员以及其分值, 可以使用 WITHSCORES 选项 |
| 84 | + ``` |
| 85 | + ZRANGE sorted_set start end [WITHSCORES] |
| 86 | + ZREVRANGE sorted_set start end [WITHSCORES] |
| 87 | + ``` |
| 88 | + 如果给定的有序集和不存在, 则会返回一个空列表. |
| 89 | + 复杂度: O(M + log(N)), 其中 M 为命令返回成员数量, N 为有序集和成员数量. |
| 90 | +
|
| 91 | +
|
| 92 | +### 示例: 排行榜 |
| 93 | +在网站上经常会有各种各样的排行榜. 比如, 音乐网站上可能有试听排行榜, 下载排行榜等. 而视屏网站上可能看到观看排行榜, 收藏排行榜等. 甚至 GitHub 项目托管网站也有 star 排行榜. |
| 94 | +
|
| 95 | +下面代码实现了一个排行榜程序: |
| 96 | +- 这个程序使用 ZADD 命令向排行榜中添加被排序的元素及分数, 并使用 ZREVRANK 命令获取元素在排行榜中的排名, 以及使用 ZSCORE 命令去获取元素的分数 |
| 97 | +- 当不再需要对某个元素进行排序的时候, 可以调用 ZREM 命令实现 `remove()` 方法, 从排行榜中移除该元素 |
| 98 | +- 如果用户想要修改某个被排序的元素的分数, 则调用 ZINCRBY 命令实现的 `increase_score()` 方法或者 `decrease_score()` 方法即可 |
| 99 | +- 当用户想要获取排行榜前N位的元素及其分数时, 只需要调用由 ZREVRANGE 命令实现的 `top()` 方法即可 |
| 100 | +
|
| 101 | +```Python |
| 102 | +class RankingList: |
| 103 | + def __init__(self, client, key): |
| 104 | + self.client = client |
| 105 | + self.key = key |
| 106 | +
|
| 107 | + def set_score(self, item, score): |
| 108 | + """为排行榜中指定元素设置分数, 不存在的元素会被添加到排行榜中""" |
| 109 | + self.client.zadd(self.key, {item: score}) |
| 110 | +
|
| 111 | + def get_score(self, item): |
| 112 | + """获取排行榜中指定元素的分数""" |
| 113 | + return self.client.zscore(self.key, item) |
| 114 | +
|
| 115 | + def remove(self, item): |
| 116 | + """从排行榜中删除指定的元素""" |
| 117 | + self.client.zrem(self.key, item) |
| 118 | +
|
| 119 | + def increase_score(self, item, increment): |
| 120 | + """将给定的元素分数增加 increment 分""" |
| 121 | + self.client.zincrby(self.key, increment, item) |
| 122 | +
|
| 123 | + def decrease_score(self, item, decrement): |
| 124 | + """将给定的元素减少 increment 分""" |
| 125 | + self.client.zincrby(self.key, 0-decrement, item) |
| 126 | +
|
| 127 | + def get_rank(self, item): |
| 128 | + """获取给定元素排行榜中的排名""" |
| 129 | + rank = self.client.zrevrank(self.key, item) |
| 130 | + if rank is not None: |
| 131 | + return rank + 1 # Redis 排名从0开始 |
| 132 | +
|
| 133 | + def top(self, n, with_score=False): |
| 134 | + """获取排行榜中得分最高的元素""" |
| 135 | + return self.client.zrevrange(self.key, 0, n-1, withsores=with_score) |
| 136 | +``` |
| 137 | + |
| 138 | +- ZRANGEBYSCORE、ZREVRANGEBYSCORE: 获取分值范围内的成员 |
| 139 | + ``` |
| 140 | + ZRANGEBYSCORE sorted_set min max |
| 141 | + ZREVRANGEBYSCORE sorted_set max min |
| 142 | + ``` |
| 143 | + 命令的 min 参数和 max 参数分别用于指定用户想要获取的最小分值和最大分值, 要注意这两条命令接受最大最小值的顺序正好相反. |
| 144 | + 该命令也可以使用 WITHSCORES 参数来同时获取成员及其分值 |
| 145 | + ``` |
| 146 | + ZRANGEBYSCORE sorted_set min max [WITHSCORES] |
| 147 | + ZREVRANGEBYSCORE sorted_set max min [WITHSCORES] |
| 148 | + ``` |
| 149 | + 默认情况下, 该命令会返回范围内的所有成员, 有时成员数量很多, 就可以使用 LIMIT 选项来限制命令返回的成员数量. |
| 150 | + ``` |
| 151 | + ZRANGEBYSCORE sorted_set min max [LIMIT offset count] |
| 152 | + ZREVRANGEBYSCORE sorted_set min max [LIMIT offset count] |
| 153 | + ``` |
| 154 | + 其中, offset 是指从满足 min 和 max 的元素中, 跳过前面的 offset 个元素, count 是从 offset 之后开始, 返回接下来的 count 个元素. |
| 155 | +
|
| 156 | +
|
0 commit comments