Redis基础之排序

来源:互联网 发布:如何备份c盘数据 编辑:程序博客网 时间:2024/05/15 11:40

集合类型提供了强大的集合操作命令,但是如果需要排序就要用到有序集合类型。Redis的作者在设计Redis的命令时就考虑到了不同数据结果类型的使用场景,对于不常用到或者在不损失过多性能的前提下可以使用现有命令来实现功能,Redis就不会单独提供命令来实现。这一原则使得Redis在拥有强大功能的同时保持相对精简的命令。

有序集合常见的使用场景是大数据排序,如游戏玩家的排行榜,所以很少会需要获得键中的全部数据。同样Redis认为开发者在做完交集、并集运算后不需要直接获得全部结果,而是会希望将结果存入新的键中一遍后续处理。这解释了为什么排序集合只有ZINTERSTORE和ZUNIONSTORE命令而没有ZINTER和ZUNION命令。

当然实际使用中确实会遇到需要获得集合运算结果的情况,除了等待Redis加入相关命令,我们还可以使用MULTI,ZINTERSTROE,ZRANGE,DEL和EXEC这5个命令自己实现ZINTER:

MULTIZINTERSTORE tempKey ...ZRANGE tempKey ...DEL tempKeyEXEC


1. SORT命令

除了使用有序集合,SORT命令可以对列表类型、集合类型和有序集合类型键进行排序,并且可以完成与关系数据库中的链接查询相似的任务。
例如,某博客中标有"ruby"标签的文章ID分别是:2,6,12,26.由于在集合类型中所有元素是无序的,所以使用SMEMBERS命令不能获取有序的结果。为了能够让博客标签页下的文章也能按照发布时间顺序排列(就是按照文章ID顺序排列),可以借助SORT命令实现。
注:集合类型常被用于存储对象ID,很多情况下是整数,所以Redis对这种情况进行了特殊的优化,元素的排列是有序的。
127.0.0.1:6379> SORT tag:ruby:posts1)"2"2)"6"3)"12"4)"26"
除了集合类型,SORT命令还可以对列表类型和有序集合类型进行排序:
127.0.0.1:6379> LPUSH mylist 4 2 6 1 3 7(integer) 6127.0.0.1:6379> SORT mylist1) "1"2) "2"3) "3"4) "4"5) "6"6) "7"
在对有序集合类型排序时会忽略元素的分值,只针对元素自身值进行排序。例如,对有序集合排序:
127.0.0.1:6379> ZADD myzset 50 2 40 3 20 1 60 5(integer) 4127.0.0.1:6379> SORT myzset1) "1"2) "2"3) "3"4) "5"
除了可以排列数字之外,SORT命令还可以通过ALPHA参数实现按照字典顺序排列非数字元素,例如:
127.0.0.1:6379> LPUSH mylistalpha a c e d B C A(integer) 7127.0.0.1:6379> SORT mylistalpha(error) ERR One or more scores can't be converted into double127.0.0.1:6379> SORT mylistalpha ALPHA1) "A"2) "B"3) "C"4) "a"5) "c"6) "d"7) "e"
如果没有加ALPHA参数的话,SORT命令会尝试将所有元素转换成双精度浮点数来比较,如果无法转换会提示错误。另一个参数是DESC,即从大到小排列。
127.0.0.1:6379> SORT mylistalpha ALPHA DESC1) "e"2) "d"3) "c"4) "a"5) "C"6) "B"7) "A"
SORT命令还支持LIMIT参数来返回指定范围的结果,用法和SQL语句一样,LIMIT offset count,表示跳过前offset个元素并获取之后的count个元素。
SORT mylistalpha ALPHA DESC LIMIT 3 21) "a"2) "C"

2. BY参数

很多情况下,列表(或者集合、有序集合)中存储的元素值代表的是对象的ID(如标签集合中存储的是文章的对象的ID),单纯对这些ID吱声排序有时意义并不大。更多时候是希望根据ID对应地对象的某个属性进行排序。例如,在博客系统中,可以通过使用有序集合键来存储文章ID列表,使得博客能够支持修改文章事件,所以文章ID的顺序和文章的发布时间顺序并不完全一致,因此根据文章ID来排序就变得没有意义了。博客使用散列类型键存储文章对象的,其中time字段存储的就是文章的发布时间。假设ID为“2”,“6”,“12”和“26”的四篇文章的time字段值分别为:“1419019200","1419019600","1419020100","1419020000"(Unix时间),如果需要按发布时间递减排列,结果为”12“,”26“,”6“,”2“。为了获取这样的结果,需要SORT命令的另一个参数BY。
BY参数的语法为”BY 参考键“。其中参考键可以使字符串类型键或者散列类型键的某个字段(表示为键名->字段名)。如果提供了BY参数,SORT命令将不再依据元素自身的值进行排序,而是对每个元素的值替换参考键中的第一个”*“并获取其值(即根据参考键对”*“进行排序),然后依据该值对元素排序。
127.0.0.1:6379>SORT tag:ruby:posts BY post:*->time DESC1)"12"2)"26"3)"6"4)"2"
在上例中SORT命令会读取post:2、post:6、post:12、post:26几个散列键中的time字段的值并以此决定tag:ruby:posts键中各个文章ID的顺序。
除了散列类型之外,参考键还可以使字符串类型,例如:
127.0.0.1:6379> LPUSH sortbylist 2 1 3(integer) 3127.0.0.1:6379> SET itemscore:1 50OK127.0.0.1:6379> SET itemscore:2 100OK127.0.0.1:6379> SET itemscore:3 -10OK127.0.0.1:6379> SORT sortbylist BY itemscore:* DESC1) "2"2) "1"3) "3"
当参考键名不包含”*“时(即常量键名,与元素无关),SORT命令将不会执行排序操作,因为Redis认为这种情况没有意义(因为所有要比较的值都意义),如果几个元素的参考键相同,则SORT命令会再比较元素本身的值来决定元素的顺序。例如:
27.0.0.1:6379> LPUSH sortbylist 4(integer) 4127.0.0.1:6379> SET itemscore:4 50OK127.0.0.1:6379> LRANGE sortbylist 0 -11) "4"2) "3"3) "1"4) "2"127.0.0.1:6379> SORT sortbylist BY anytext1) "4"2) "3"3) "1"4) "2"127.0.0.1:6379> SORT sortbylist BY itemscore:* DESC1) "2"2) "4"3) "1"4) "3"

上例中anytext是常量键名(甚至anytext键可以不存在),此时SORT的结果与LRANGE的结果相同,没有执行排序操作。在不需要排序但需要借助SORT命令获得与元素相关的数据时,常量键名很有用。而元素”4“的参考键itemscore:4的值和元素”1“的参考键itemscore:1的值都是50,所以SORE命令还会比较”4“和”1“,元素本身的大小决定两者的顺序。

当某个元素的参考键不存在,会默认参考键的值为0:

127.0.0.1:6379> LPUSH sortbylist 5(integer) 5127.0.0.1:6379> SORT sortbylist BY itemscore:* DESC1) "2"2) "4"3) "1"4) "5"5) "3"127.0.0.1:6379> 
参考键虽然只支持散列类型,但是”*“只能在”->"符号前面(即键名部分)才有用,在“->"后(即字段名部分)会被当成字段名本身而不会作为占位符被元素的值替换,即常量键名。但实际运行时,会发现有趣的结果:

<pre name="code" class="plain"><pre name="code" class="plain">127.0.0.1:6379> SORT sortbylist BY somekey->somefield:*1) "1"2) "2"3) "3"4) "4"5) "5"
前面提高了当参考键名是常量键名时SORT命令将不会执行排序操作,然而上例中确实进行了排序,而且只对元素本身进行排序。这是因为Redis判断参考键名是不是常量键名的方式是判断参考键名中是否含有”*“,而somekey->somefield:* 中包含”*“,所以不是常量键名。所以在排序的时候Redis对每个元素都会读取somekey中的somefile*字段(”*“不会被替换),无论是否获取其值,每个元素的参考值都相同,所以Redis会按照元素本身的大小排序。

3. GET参数

根据发布时间对文章ID排好序之后,可用HGET命令来获取文章的标题,而一种更简单的操作室借助SORT命令的参数GET。GET不影响排序,它的作用是使SORT命令的返回结果不再是元素自身的值,而是GET参数中指定的键值。GET参数的规则与BY参数一样,GET参数也支持字符串类型和散列类型的键,并使用”*“作为占位符。要实现排名后直接返回文章ID对应的标题,可以这样:

127.0.0.1:6379> SORT tag:ruby:posts BY post:*->time DESC GET post:*->title1)"Windows 8 app designs"2)"RethinkDB"3)"Uses for cURL"4)"The Nature of Ruby"

在一个SORT命令中可以有多个GET参数(而BY参数只能有一个),所以还可以这样:

</pre><pre name="code" class="plain"><pre name="code" class="plain"><span style="font-family: Arial, Helvetica, sans-serif;"> SORT tag:ruby:posts BY post:*->time DESC GET post:*->title GET post:*->time</span>
1)"Windows 8 app designs"2)"1419020100"3)"RethinkDB"4)"141902000"5)"Uses for cURL"6)"1419019600"7)"The Nature of Ruby"8)“1419019200"

如果还要返回文章ID怎么办?用GET # 即可,也就是说GET #返回元素本身的值。

4. STOR参数

默认情况下,SORT会直接返回排序结果,如果希望保存排序结果,可以使用STORE参数。例如,将结果保存到sort.result键中:
127.0.0.1:6379>SORT tag:ruby:posts BY post:*->time GET post:*->title GET post:*->time GET # STORE sort.result(intger)12
保存后的键的类型为列表类型,如果键已经存在则会覆盖它,加上STORE参数后SORT命令会返回结果的个数。
STORE参数常用来结合EXPIRE命令缓存排序结果,如:
$isCacheExists = EXISTS cache.sortif $isCacheExist    return LRANGE cache.sort,0,-1else    sortResult = SORT some.list STORE cache.sort    EXPIRE cache.sort 60    return $sortResult
SORT是Redis中最强大最复杂的命令之一,如果使用不好很容易成为性能的瓶颈。SORT命令的时间复杂度为O(n+mlogm),其中n表示要排序的列表(集合或者有序集合)中元素的个数,m表示要返回的元素的个数。当n较大的时候SORT命令的性能相对较低,并且Redis在排序前会建立一个长度为n的容器来存储待排序的元素(有一个例外是当键的类型为有序集合且参考键为常量键名时容器大小为m而不是n),虽然是一个临时过程,但如果同时进行较多大数据量排序操作则会严重影响性能。
所以开发中使用SORT命令时需要注意一下几点:
  • 尽量减少待排序键中元素的数量(使n尽可能小)
  • 使用LIMIT参数来只取需要的数据
  • 如果要排的数据量较大,尽可能使用STORE参数将结果缓存





0 0
原创粉丝点击