Redis sort 排序命令详解
本文介绍redis排序命令
redis支持对list,set,sortedset元素的排序
sort排序命令格式:
sortkey[BYpattern][LIMITstartcount][GETpattern][ASC|DESC][ALPHA][STOREdstkey]
1)sortkey(list)
这是最简单的情况,没有任何选项对集合自身元素排序并返回排序结果,默认为value升序。
示例:
127.0.0.1:6379>lpushmimvp12 (integer)1 127.0.0.1:6379>lpushmimvp11 (integer)2 127.0.0.1:6379>lpushmimvp13 (integer)3 127.0.0.1:6379>lpushmimvp10 (integer)4 127.0.0.1:6379>lrangemimvp0-1 1)“10” 2)“13” 3)“11” 4)“12” 127.0.0.1:6379>sortmimvp 1)“10” 2)“11” 3)“12” 4)“13” 127.0.0.1:6379>sortmimvpdesc 1)“13” 2)“12” 3)“11” 4)“10”
2)[ASC|DESC][ALPHA] (list)
sort默认的排序方式(asc)是从小到大排的,当然也可以按照逆序或者按字符顺序排。
逆序可以加上desc选项,想按字母顺序排可以加alpha选项,alpha可以和desc一起用。
sort默认以分数(数值)排序,字母使用默认的sort排序,会报错!
示例:
127.0.0.1:6379>lpushmylistforum (integer)1 127.0.0.1:6379>lpushmylistproxy (integer)2 127.0.0.1:6379>lpushmylistblog (integer)3 127.0.0.1:6379>lpushmylistapptop (integer)4 127.0.0.1:6379>sortmylist (error)ERROneormorescorescan'tbeconvertedintodouble 127.0.0.1:6379>sortmylistalpha 1)“apptop” 2)“blog” 3)“forum” 4)“proxy” 127.0.0.1:6379>sortmylistalphadesc 1)“proxy” 2)“forum” 3)“blog” 4)“apptop” 127.0.0.1:6379>sortmylistdescalpha 1)“proxy” 2)“forum” 3)“blog” 4)“apptop”
3)[BYpattern] (set)
除了可以按集合元素自身值(数字,字母)排序外,还可以将集合元素内容按照给定pattern组合成新的key,并按照新key中对应的内容进行排序。
示例:
127.0.0.1:6379>setmimvp_12mimvp_12 OK 127.0.0.1:6379>setmimvp_11mimvp_11 OK 127.0.0.1:6379>setmimvp_13mimvp_13 OK 127.0.0.1:6379>setmimvp_10mimvp_10 OK 127.0.0.1:6379>sortmimvpbymimvp_* //mimvp_*是字符串,因此需要alpha (error)ERROneormorescorescan'tbeconvertedintodouble 127.0.0.1:6379>sortmimvpbymimvp_*alpha 1)“10” 2)“11” 3)“12” 4)“13” 127.0.0.1:6379>sortmimvpbymimvp_*alphadesc 1)“13” 2)“12” 3)“11” 4)“10”
*代表了上面示例1)mimvp已经给出的元素值,所以这个排序是按照mimvp_12,mimvp_11,mimvp_13,mimvp_10这四个key对应值排序的,但返回的仍然是排序后mimvp集合中的元素,即数值13,11,12,10,而不是mimvp_*的字符串元素。
4)[GETpattern]
上面的例子都是返回的mimvp集合中的数值元素,也可以通过get选项去获取指定pattern作为新key(mimvp_*)对应的字符串值。
示例:
127.0.0.1:6379>sortmimvpbymimvp_*getmimvp_*alpha 1)“mimvp_10″ 2)“mimvp_11″ 3)“mimvp_12″ 4)“mimvp_13″ 127.0.0.1:6379>sortmimvpbymimvp_*getmimvp_*alphadesc 1)“mimvp_13″ 2)“mimvp_12″ 3)“mimvp_11″ 4)“mimvp_10″
这次返回的就不在是mimvp中的元素,而是mimvp_12,mimvp_11,mimvp_13,mimvp_10对应的值,当然排序是按照mimvp_12,mimvp_11,mimvp_13,mimvp_10值并根据字母顺序排的。
另外,get选项可以有多个,#特殊符号引用的是原始集合也就是mimvp(类似与sorted-setwithscores)
示例:
127.0.0.1:6379>sortmimvpbymimvp_*getmimvp_*get#alpha 1)“mimvp_10″ 2)“10” 3)“mimvp_11″ 4)“11” 5)“mimvp_12″ 6)“12” 7)“mimvp_13″ 8)“13” 127.0.0.1:6379>sortmimvpbymimvp_*getmimvp_*get#alphadesc 1)“mimvp_13″ 2)“13” 3)“mimvp_12″ 4)“12” 5)“mimvp_11″ 6)“11” 7)“mimvp_10″ 8)“10”
最后在还有一个引用hash类型字段的特殊字符-> (hash)
示例:
127.0.0.1:6379>hsetuser_12nameyanggang (integer)1 127.0.0.1:6379>hsetuser_11nameyangjie (integer)1 127.0.0.1:6379>hsetuser_13nameyangliang (integer)1 127.0.0.1:6379>hsetuser_10nameyangchuang (integer)1 127.0.0.1:6379>sortmimvpgetuser_*->name 1)“yangchuang” 2)“yangjie” 3)“yanggang” 4)“yangliang” 127.0.0.1:6379>sortmimvpgetuser_*->namedesc 1)“yangliang” 2)“yanggang” 3)“yangjie” 4)“yangchuang”
很容易理解,如果mimvp当对应的数值不存在,则对应返回的是nil
5)[LIMITstartcount] (limit)
上面例子返回结果都是全部元素,limit选项可以限定返回结果的数量。
示例:
127.0.0.1:6379>sortmimvpgetmimvp_*limit12 1)“mimvp_11″ 2)“mimvp_12″ 127.0.0.1:6379>sortmimvpgetmimvp_*limit03 1)“mimvp_10″ 2)“mimvp_11″ 3)“mimvp_12″
start下标是从0开始,这里的limit选项(limit12)意思是从第二个元素开始获取2个。
6)[STOREdstkey] (store)
如果对集合经常按照固定的模式去排序,那么把排序结果缓存起来会减少不少cpu开销,使用store选项可以将排序内容保存到指定key中,保存的类型是list
127.0.0.1:6379>sortmimvpgetmimvp_*limit03storemimvp_store (integer)3 127.0.0.1:6379>typemimvp_store list 127.0.0.1:6379>lrangemimvp_store0-1 1)“mimvp_10″ 2)“mimvp_11″ 3)“mimvp_12″
这个例子将排序结果保存到mimvp_store中,后面使用直接从mimvp_store提取即可。
小结
功能介绍完后,再讨论下关于排序的一些问题。
如果有多个redisserver的话,不同的key可能存在于不同的server上。比如mimvp_12,mimvp_11,mimvp_13,mimvp_10,很有可能分别在四个不同的server上存贮着,这种情况会对排序性能造成很大的影响。
redis作者在他的blog上提到了这个问题的解决办法,就是通过keytag将需要排序的key都放到同一个server上。由于具体决定哪个key存在哪个服务器上一般都是在client端hash的办法来做的,我们可以通过只对key的部分进行hash
举个例子:
假如我们的client如果发现key中包含[],那么只对key中[]包含的内容进行hash,我们将四个name相关的key,都这样命名[name]12[name]13[name]11[name]10,于是client程序就会把他们都放到同一server上(不知道jredis实现了没)。
还有一个问题也比较严重
如果要sort的集合非常大的话排序就会消耗很长时间。由于redis单线程的,所以长时间的排序操作会阻塞其他client的请求。解决办法是通过主从复制机制将数据复制到多个slave上。然后我们只在slave上做排序操作,并尽可能的对排序结果缓存。另外就是一个方案是就是采用sortedset对需要按某个顺序访问的集合建立索引。
示例:
127.0.0.1:6379>saddtom:friend:list123 #tom的好友列表里面是好友的uid (integer)1 127.0.0.1:6379>saddtom:friend:list456 (integer)1 127.0.0.1:6379>saddtom:friend:list789 (integer)1 127.0.0.1:6379>saddtom:friend:list101 (integer)1 127.0.0.1:6379>setuid:sort:1231000 #uid对应的成绩 OK 127.0.0.1:6379>setuid:sort:4566000 OK 127.0.0.1:6379>setuid:sort:789100 OK 127.0.0.1:6379>setuid:sort:1015999 OK 127.0.0.1:6379>setuid:123“{‘uid':123,'name':'lucy'}” #增加uid对应好友信息 OK 127.0.0.1:6379>setuid:456“{‘uid':456,'name':'jack'}” OK 127.0.0.1:6379>setuid:789“{‘uid':789,'name':'marry'}” OK 127.0.0.1:6379>setuid:101“{‘uid':101,'name':'icej'}” OK 127.0.0.1:6379>sorttom:friend:listbyuid:sort:*getuid:* #从好友列表中获得id与uid:sort字段匹配后排序,并根据排序后的顺序,用key在uid表获得信息 1)“{‘uid':789,'name':'marry'}” 2)“{‘uid':123,'name':'lucy'}” 3)“{‘uid':101,'name':'icej'}” 4)“{‘uid':456,'name':'jack'}” 127.0.0.1:6379>sorttom:friend:listbyuid:sort:*getuid:*getuid:sort:* 1)“{‘uid':789,'name':'marry'}” 2)“100” 3)“{‘uid':123,'name':'lucy'}” 4)“1000” 5)“{‘uid':101,'name':'icej'}” 6)“5999” 7)“{‘uid':456,'name':'jack'}” 8)“6000”
问题与分析
1. sortmimvpbymimvp_*getmimvp_*get#alpha为什么会按照10–13的顺序排下来,这个跟单纯的排序name*和name*alpha的结果都不一样
这个问题要从redis的实现逻辑上来分析了
a)list在插入后,默认是按照时间的先后反序排列的(先进后出,FILO),lrangeml0-1,结果是:12131110.这是因为list插入时是将最新的item插入到链表头
b)sortmimvpbymimvp_*确定是会按照mimvp_*的值进行排序的.但当mimvp_*对应的value不是num型并且没有设置alpha的时候,会导致排序分值都是相同的,因为程序将把mimvp_*对应的值尝试转换为nun型
c)这就会导致sortmimvpbymimvp_*会按照ml的自然顺序进行排列了
if(alpha){ if(sortby){ vector[j].u.cmpobj=getDecodedObject(byval); } } else{ if(byval->encoding==REDIS_ENCODING_RAW){ vector[j].u.score=strtod(byval->ptr,NULL); } elseif(byval->encoding==REDIS_ENCODING_INT){ /*Don'tneedtodecodetheobjectifit's *integer-encoded(theonlyencodingsupported)so *far.Wecanjustcastit */ vector[j].u.score=(long)byval->ptr; } else{ redisAssert(1!=1); } }