Redis高级特性——排序

排序

对于排序的场景,在业务中会大量使用到,对于Redis,如果使用了有序集合,那么排序问题很容易解决,并且得分可以根据实际的业务,由时间、点赞、费用、排行等等进行转化,支持的业务范围也比较广泛。

除了有序集合的排序外,Redis另外提供了排序的指令SORT。SORT指令可以针对列表类型、集合类型、有序集合类型的键进行排序,同时借助BY指令,Redis的SORT...BY指令可以完成一部分类似MYSQL中的关系查询排序效果。

SORT key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern...]] [ASC|DESC] [ALPHA] [STORE destinion]

SORT排序比较简单,排序之后立即将结果返回,以列表的形式返回。假定有一个资源ID列表,资源ID使用MYSQL的自增主键,从1开始累增。如果将这些ID放置到了集合中,那么在使用按入库时间降序排列时,使用集合是无法做到的,此时可以使用SORT。使用列表效果也一致。参考下面的例子。

# 结合
SADD resourceids 2 3 1 5 4
# (integer) 5
SORT resourceids    # 默认为ASC
# 1) "1"
# 2) "2"
# 3) "3"
# 4) "4"
# 5) "5"
SORT resourceids DESC
# 1) "5"
# 2) "4"
# 3) "3"
# 4) "2"
# 5) "1"

# 列表
LPUSH lst  2 3 1 5 4
# (integer) 5
SORT lst DESC
# 1) "5"
# 2) "4"
# 3) "3"
# 4) "2"
# 5) "1"

从上列中看出,使用SORT指令时,列表、集合或者是有序集合,指令功能基本是一致的。而对有序集合使用SORT进行排序时,将会忽略SCORE,只对元素字面值进行排序,此时效果和集合或列表使用方式及表现是一致的。

ZADD zlst 123 2 125 3 120 1 125 5 124 4
# (integer) 5
SORT zlst DESC
# 1) "5"
# 2) "4"
# 3) "3"
# 4) "2"
# 5) "1"

一般情况下资源类的ID都是数字,使用SORT进行排序能完成快速排序的目的,但有时候也会有字符串类的ID集合,Redis命令也可以通过指定ALPHA属性实现字符串排序。这个时候只是按非数字字符串本身进行排序,会丢掉数据库自增的顺序属性。如果SORT没有携带ALPHA参数,那么SORT仍然会使用数字类型排序,如果是非数字字符串集合,那么将会返回错误信息。

LPUSH alphaLst hello world lua redis c# java JAVA
# (integer) 7
SORT alphaLst ALPHA
# 1) "c#"
# 2) "hello"
# 3) "java"
# 4) "JAVA"
# 5) "lua"
# 6) "redis"
# 7) "world"

当结果较多时,排序时,可以使用LIMIT参数进行限制返回的数据数量,可以实现分页效果,该参数的使用方式同MySQL的LIMIT基本一致。offset从0开始

SORT lst DESC
# 1) "5"
# 2) "4"
# 3) "3"
# 4) "2"
# 5) "1"
SORT lst LIMIT 0 2 DESC
# 1) "5"
# 2) "4"

SORT lst LIMIT 2 2 DESC
# 3) "3"
# 4) "2"

如果资源ID已经存储到了列表中,如上例中的[1,2,3,4,5]五个资源,同时使用散列数据结构存储了这些资源的详情,同时详情中包含了一个点击得分的字段score,该字段为数字类型。如果有需求要实现按用户的点击得分进行排序,此时需要借助BY参数实现。

# 资源详情散列数据结构及数据如下,散列的key为`resource:id`,散列的field为字段名,这里只有title和score
[
    "resource:5" : {
        title: "efg",
        score: 125
    } ,
    "resource:4" : {
        title: "bcd",
        score: 230
    } ,
    "resource:3" : {
        title: "ddd"
        score: 125
    } ,
    "resource:2" : {
        title: "ggg",
        score: 100
    } ,
    "resource:1" : {
        title: "fff",
        score: 156
    } 
]

要实现资源点击得分倒序排列,采用如下的指令。当排序中参考字段某几个元素相同时,如本例中5、3得分都是125,那么在排序时,遇到相同的,会再次参考元素本身进行排序

SORT lst BY resource:*->score DESC
# 1) "4"
# 2) "1"
# 3) "5"
# 4) "3"
# 5) "2"

BY的使用方法为:BY pattern,pattern为要进行排序的参考的键。该字段的标识方法为:键名->字段名,可以是散列类型,也可以是字符串类型。当出现BY参数时,Redis会使用列表中元素,去替换BY后面字段中的*,并根据实际的字段获取值,参与到排序中,如上述示例中,将分别获取resource:1resource:2resource:3resource:4resource:5等散列中的score字段的值,并参与到排序中。如果参考字段时散列类型那么对于通配符*只能存在于->之前。如果使用字符串作为参考字段时,ID列表不需要变更,只要将score存储到字符串中,其数据如下:

[
    "strscore:5": 125,
    "strscore:4": 230,
    "strscore:3": 125,
    "strscore:2": 100,
    "strscore:1": 156,
]

排序指令如下,得到和散列结构详情一样的结果。

SORT lst BY strscore:* DESC
# 1) "4"
# 2) "1"
# 3) "5"
# 4) "3"
# 5) "2"

当参考键中不包含*时,常量键值,此时SORT将不会进行排序。

SORT lst BY a DESC
# 1) "2"
# 2) "3"
# 3) "1"
# 4) "5"
# 5) "4"

如果参考字段中某个值不存在时,会以默认值0进行替代,上例中抹去resource:1字段,在进行排序

DEL strscore:1
# (integer) 1
SORT lst BY strscore:* DESC
# 1) "4"
# 2) "5"
# 3) "3"
# 4) "2"
# 5) "1"

GET参数可以应用于SORT BY结构中,用于指定返回的信息。这个功能在做列表时非常有用,如果有需求要显示点击排行列表,那么一方面需要按点击排序,另外一方面需要返回资源名称以及资源ID;如果可以在SORT指令中一并返回,就不需要业务先做排序,拿到排序后的ID集合,在根据ID获取资源信息,取出title信息。

SORT指令的GET实现了这个功能,根据字符串或散列对象分别获取,其规则为

# 散列
GET key:*->field
get resource:*->title

# 字符串
GET key:*

# 获取列表中的ID
GET #

根据之前的数据结构,从散列表中resource:*和集合resourceids中按评分进行排序,并获取id、title和评分的列表。

SORT resourceids BY resource:*->score DESC GET resource:*->title GET resource:*->score GET # LIMIT 0 2
# 1) "bcd"
# 2) "230"
# 3) "4"
# 4) "efg"
# 5) "125"
# 6) "5"

如果有几个GET,如上面例子中有三个GET,那么返回结果列表中,每一个结果就有几条显示,(3条,title、score、id)。

一般情况下,SORT主要用于对列表进行排序,获取部分结果,如排行榜之类的。这时显示的一般是列表,不需要返回跟多的详情信息,并且有时间要求(排行榜每一小时更新一次)。这种情况不需要业务系统每次都排序,获取字段组装数据,SORT指令提供给了最后一个参数STORE,该参数可以将结果存储到指定的键中,同时设置有效期后,将可以满足上述的业务需求。其参数使用方式为:... STORE key,加了该参数后,其返回结果将不在是列表,而是存储的结果数量,以列表数据存储

SORT resourceids BY resource:*->score DESC GET resource:*->title GET resource:*->score GET # LIMIT 0 2 STORE my_top
# (integer) 6
LRANGE my_top 0 -1
# 1) "bcd"
# 2) "230"
# 3) "4"
# 4) "efg"
# 5) "125"
# 6) "5"

SORT指令是目前为止使用到的最复杂的指令,且提供了类似MySQL一样的关联查询操作。在某些操作的时候,可能会导致查询缓慢,由于Redis时单线程,也可能一个指令导致Redis的阻塞。

SORT指令的时间负责度:O(n+mlogm),n为要排序的列表数量,m为要SORT返回的元素数量(LIMIT),随着n的增大,该指令的效率降低。同时排序时,Redis也会建立一个长度为n的临时列表存储这些元素,从这方面看数据越大,性能就越慢。

为了提高性能,只能从这两方面入手:降低n的数量,或者降低m的数量(LIMIT可解决);数量较大时,使用STORE参数将结果缓存,在一定程度上可以减少STORE的调用次数。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容