From a7206d120e7e4f70374a8f67a8ff58939422590c Mon Sep 17 00:00:00 2001 From: asahi Date: Mon, 8 Sep 2025 14:51:17 +0800 Subject: [PATCH] =?UTF-8?q?doc:=20=E9=98=85=E8=AF=BBsorted=20set=E6=96=87?= =?UTF-8?q?=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 中间件/redis/redis.md | 188 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) diff --git a/中间件/redis/redis.md b/中间件/redis/redis.md index 924cfa5..a31a0cb 100644 --- a/中间件/redis/redis.md +++ b/中间件/redis/redis.md @@ -71,6 +71,17 @@ - [Field Expiration](#field-expiration) - [Common field expiration use cases](#common-field-expiration-use-cases) - [Field Expiration examples](#field-expiration-examples) + - [Sorted sets](#sorted-sets) + - [ZRANGEBYSCORE](#zrangebyscore) + - [ZREMRANGEBYSCORE / ZREM](#zremrangebyscore--zrem) + - [Inclusive and exclusive](#inclusive-and-exclusive) + - [ZRANK / ZREVRANK](#zrank--zrevrank) + - [`Lexicographical scores`](#lexicographical-scores) + - [ZRANGEBYLEX](#zrangebylex) + - [Updating the score: leaderboards](#updating-the-score-leaderboards) + - [Leaderboard Example](#leaderboard-example) + - [ZADD](#zadd) + - [ZINCRBY](#zincrby) # redis @@ -1073,3 +1084,180 @@ print(expire_time) # prints [1717668041] # your actual value may vary ``` +### Sorted sets +redis sorted set是一个包含unique strings的集合,其中unique strings会关联一个score,并且strings会按照score进行排序。`如果多个string存在相同的score,那么拥有相同score的strings将会按照字典序进行排序`。 + +sorted sets的用例场景包括如下: +- `Leaderboards`: 可以通过sorted sets维护一个ordered list,其可被用于实现排行榜 +- `RateLimiter`: 通过sorted list,可以构建一个`sliding-window rate limiter`,从而避免过多的api调用 + - 在实现`sliding-window rate limiter`时,可以将时间戳作为score,因为可以快速获取一个时间范围内的调用数量 + +可以将sorted sets看作是set和hash的混合, +- 和set一样,sorted sets由unique strings组成 +- 和hash一样,sorted sets中元素由一个关联的floating point value,被称为score + +sorted set的使用示例如下: +```redis-cli +> ZADD racer_scores 10 "Norem" +(integer) 1 +> ZADD racer_scores 12 "Castilla" +(integer) 1 +> ZADD racer_scores 8 "Sam-Bodden" 10 "Royce" 6 "Ford" 14 "Prickett" +(integer) 4 +``` + +`ZADD`和`SADD`类似,但是其接收一个用于表示`score`的额外参数。和`SADD`类似,可以使用`ZADD`来添加多个`score-value pairs`。 + +> #### Implementation +> sorted sets实现的数据结构中,同时使用了`skip list`和`hash table`两种数据结构,故而每次向zset中添加元素时,其操作的复杂度为o(log(n))`。 +> +> 并且,在获取元素时,由于元素已经被排序,获取操作无需其他的额外开销。 + +`ZRANGE`的顺序为从小到大,`ZREVRANGE`的顺序则是从大到小 +```redis-cli +> ZRANGE racer_scores 0 -1 +1) "Ford" +2) "Sam-Bodden" +3) "Norem" +4) "Royce" +5) "Castilla" +6) "Prickett" +> ZREVRANGE racer_scores 0 -1 +1) "Prickett" +2) "Castilla" +3) "Royce" +4) "Norem" +5) "Sam-Bodden" +6) "Ford" +``` +在上述示例中,0和-1代表index位于`[0, len-1]`范围内的元素。(负数代表的含义和`LRANGE`命令中相同) + +在`ZRANGE`命令中指定`withscores`,也能够在返回zset中value时同时返回score: +```redis-cli +> ZRANGE racer_scores 0 -1 withscores + 1) "Ford" + 2) "6" + 3) "Sam-Bodden" + 4) "8" + 5) "Norem" + 6) "10" + 7) "Royce" + 8) "10" + 9) "Castilla" +10) "12" +11) "Prickett" +12) "14" +``` + +#### ZRANGEBYSCORE +除了上述操作外,sorted sets还支持对`operate on ranges`。可以通过`ZRANGEBYSCORE`来实现`get all racers with 10 or fewer points`的操作: +```redis-cli +> ZRANGEBYSCORE racer_scores -inf 10 +1) "Ford" +2) "Sam-Bodden" +3) "Norem" +4) "Royce" +``` +上述示例中,`ZRANGEBYSCORE racer_score -inf 10`向redis请求`返回score位于negative infinity和10之间(both included)的元素`。 + +#### ZREMRANGEBYSCORE / ZREM +如果想要丛zset中移除元素,可以调用`ZREM`命令。同样的,sorted sets也支持`remove ranges of elements`的操作,可通过`ZREMRANGEBYSCORE`命令来实现。 +```redis-cli +> ZREM racer_scores "Castilla" +(integer) 1 +> ZREMRANGEBYSCORE racer_scores -inf 9 +(integer) 2 +> ZRANGE racer_scores 0 -1 +1) "Norem" +2) "Royce" +3) "Prickett" +``` +上述示例中,通过`ZREMRANGEBYSCORE racer_scores -inf 9`命令实现了`remove all the racers with strictly fewer than 10 points`的操作。 + +`ZREMRANGEBYSCORE`会返回其移除的元素个数。 + +##### Inclusive and exclusive +在使用`ZRANGEBYSCORE`指定`min`和`max`时,可以分别将其指定为`-inf`和`+inf`。故而,在获取zset中比`xx`大或比`xx`小的所有元素时,可以使用`-inf`和`+inf`,此时无需知道当前zset中的最大/最小元素。 + +默认情况下,`interval specified by min and max is closed`(inclusive)。但是,可以将其指定为`open interval`(exclusive),只需要在score前添加`(`符号即可,示例如下: +```redis-cli +ZRANGEBYSCORE zset (1 5 +``` +上述示例中,会返回位于`(1, 5]`区间内的元素。 + +而`ZRANGEBYSCORE zset (5 (10`命令则是会返回`(5, 10)`区间内的元素。 + +#### ZRANK / ZREVRANK +sorted sets还支持`get-rank operation`,通过`ZRANK`可以返回`position of an element in the set of ordered elements`。 + +`ZREVRANK`命令的作用和`ZRANK`类似,但是`ZREVRANK`返回的值为`从大到小的降序ranking`。 + +使用示例如下所示: +```redis-cli +> ZRANK racer_scores "Norem" +(integer) 0 +> ZREVRANK racer_scores "Norem" +(integer) 2 +``` + +#### `Lexicographical scores` +自redis 2.8其,引入了`getting ranges lexicograhpically`的新特性,其假设sorted set中的所有元素都拥有相同的score。 + +与`lexicographical ranges`进行交互的主要命令如下: +- ZRANGEBYLEX +- ZREVRANGEBYLEX +- ZREMRANGEBYLEX +- ZLEXCOUNT + +使用示例如下所示: +```redis-cli +> ZADD racer_scores 0 "Norem" 0 "Sam-Bodden" 0 "Royce" 0 "Castilla" 0 "Prickett" 0 "Ford" +(integer) 3 +> ZRANGE racer_scores 0 -1 +1) "Castilla" +2) "Ford" +3) "Norem" +4) "Prickett" +5) "Royce" +6) "Sam-Bodden" +> ZRANGEBYLEX racer_scores [A [L +1) "Castilla" +2) "Ford" +``` +在上述示例中,可以通过`ZRANGEBYLEX`按照字典序对range进行请求。 + +##### ZRANGEBYLEX +ZRANGEBYLEX的语法如下: +``` +ZRANGEBYLEX key min max [limit offset count] +``` +和`ZRANGEBYSCORE`命令不同的是,`ZRANGEBYSCORE`在指定范围时,默认是`included`的;而`ZRANGEBYLEX`必须通过`[`和`(`来显式指定inclusive或exclusive。 + +而在指定min和max时,`-`和`+`分别则代表negatively infinite和positive infinite。故而,`ZRANGEBYLEX myzset - +`命令代表返回zset中所有的元素。 + +#### Updating the score: leaderboards +支持对sorted set中元素的score进行更新。在更新sorted set中元素的score时,只需要`再次调用ZADD命令即可`,sorted set会更新score,更新操作的时间复杂度为`O(log(N))`。 + +#### Leaderboard Example +在通过zset实现leaderboard时,由如下两种方式对user score进行更新: +- 在得知user当前score的情况下,可以直接通过`ZADD`命令来进行覆盖 +- 如果想要针对当前的score进行`增加`操作时,可以使用`ZINCRBY`命令 + +```redis-cli +> ZADD racer_scores 100 "Wood" +(integer) 1 +> ZADD racer_scores 100 "Henshaw" +(integer) 1 +> ZADD racer_scores 150 "Henshaw" +(integer) 0 +> ZINCRBY racer_scores 50 "Wood" +"150" +> ZINCRBY racer_scores 50 "Henshaw" +"200" +``` +##### ZADD +当当前添加的元素已经在sorted set中存在时,`ZADD`命令会返回`0`,否则`ZADD`命令会返回`1`。 + +##### ZINCRBY +而`ZINCRBY`命令则是会返回更新后的new score。 +