一捉捅、zset(sorted set:有序集合)
Redis zset和Set一樣也是String類型元素的集合仆葡,且不允許重復(fù)的成員绘闷。不同的是每個(gè)元素都會(huì)關(guān)聯(lián)一個(gè)double類型的分?jǐn)?shù)忆肾。Redis正是通過分?jǐn)?shù)來為集合中的成員進(jìn)行從小到大的排序室梅。zset的成員是唯一的,但分?jǐn)?shù)(score)卻可以重復(fù)。
Redis zset類似Java里的LinkedSet(不重復(fù)有序)疚宇,當(dāng)zset中最后一個(gè)value 被移除后亡鼠,數(shù)據(jù)結(jié)構(gòu)自動(dòng)刪除,內(nèi)存被回收敷待。
二间涵、應(yīng)用場(chǎng)景
2.1 成績(jī)榜單
2.1.1 成績(jī)表單添加數(shù)據(jù)
ZADD命令:添加or更新成員分?jǐn)?shù)
//命令參數(shù)
ZADD key score member [[score member] [score member] ...]
將一個(gè)或多個(gè)member元素及其score值加入到有序集key當(dāng)中。如果某個(gè)member已經(jīng)是有序集的成員榜揖,那么更新這個(gè)member的score值勾哩,并通過重新插入這個(gè)member元素顶捷,來保證該member在正確的位置上牡辽。
score值可以是整數(shù)值或雙精度浮點(diǎn)數(shù)。如果key不存在棒坏,則創(chuàng)建一個(gè)空的有序集并執(zhí)行ZADD操作妨猩。當(dāng)key存在但不是有序集類型時(shí)潜叛,返回一個(gè)錯(cuò)誤。
//假設(shè)用戶A(user1)當(dāng)前游戲的分?jǐn)?shù)為50壶硅,則
ZADD user_rank 50 user1
//添加用戶B(user2)當(dāng)前游戲的分?jǐn)?shù)為60威兜、用戶C(user3)當(dāng)前游戲的分?jǐn)?shù)為70,則可批量操作庐椒,同時(shí)添加user2椒舵、user3 兩個(gè)用戶的分?jǐn)?shù)
ZADD user_rank 60 user2 70 user3
2.1.2 ZREVRANK 獲取成員當(dāng)前的排名
//命令參數(shù)
ZREVRANK key member
返回有序集key中成員member的排名,其中有序集成員按score值遞減(從大到小)排序约谈。排名以0為底笔宿,也就是說犁钟,score 值最大的成員排名為0 。
//獲取用戶A當(dāng)前的排名措伐,user1 當(dāng)前排名為第三特纤,則輸出2
ZREVRANK user_rank user1
2.2.3 ZSCORE 獲取用戶分?jǐn)?shù)(排序值)
//命令參數(shù)
ZSCORE key member
返回有序集key中,成員member的score值侥加。如果member元素不是有序集key的成員或key不存在返回nil捧存。
// 獲取用戶A當(dāng)前的分?jǐn)?shù),user1當(dāng)前分?jǐn)?shù)為50担败,則輸出"50"昔穴,注意返回值是字符串
ZSCORE user_rank user1
2.2 游戲排行榜
一個(gè)典型的游戲排行榜包括以下常見功能:
- 能夠記錄每個(gè)玩家的分?jǐn)?shù);
- 能夠?qū)ν婕业姆謹(jǐn)?shù)進(jìn)行更新提前;
- 能夠查詢每個(gè)玩家的分?jǐn)?shù)和名次吗货;
- 能夠按名次查詢排名前N名的玩家;
- 能夠查詢排在指定玩家前后M名的玩家狈网。
更進(jìn)一步宙搬,上面的操作都需要在短時(shí)間內(nèi)實(shí)時(shí)完成,這樣才能最大程度發(fā)揮排行榜的效用拓哺。由于一個(gè)玩家名次上升x位將會(huì)引起x+1位玩家的名次發(fā)生變化(包括該玩家)勇垛,如果采用傳統(tǒng)數(shù)據(jù)庫(kù)(比如MySQL)來實(shí)現(xiàn)排行榜,當(dāng)玩家人數(shù)較多時(shí)士鸥,將會(huì)導(dǎo)致對(duì)數(shù)據(jù)庫(kù)的頻繁修改闲孤,性能得不到滿足,所以不合適烤礁。
2.2.1 zadd設(shè)置玩家分?jǐn)?shù)
下面設(shè)置了4個(gè)玩家的分?jǐn)?shù)讼积,如果玩家分?jǐn)?shù)已經(jīng)存在,則會(huì)覆蓋之前的分?jǐn)?shù)脚仔。
> redis 127.0.0.1:6379> zadd lb 89 user1
> (integer) 1
> redis 127.0.0.1:6379> zadd lb 95 user2
> (integer) 1
> redis 127.0.0.1:6379> zadd lb 95 user3
> (integer) 1
> redis 127.0.0.1:6379> zadd lb 90 user4
> (integer) 1
2.2.2 zscore查看玩家分?jǐn)?shù)
下面是查看user2這個(gè)玩家在lb排行榜中的分?jǐn)?shù)勤众。
redis 127.0.0.1:6379> zscore lb user2 “95”
2.2.3 zrevrange按名次查看排行榜
由于排行榜一般是按照分?jǐn)?shù)由高到低排序的,所以我們使用zrevrange鲤脏,而命令zrange是按照分?jǐn)?shù)由低到高排序决摧。起始位置和結(jié)束位置都是以0開始的索引,且都包含在內(nèi)凑兰。如果結(jié)束位置為-1則查看范圍為整個(gè)排行榜掌桩。帶上withscores則會(huì)返回玩家分?jǐn)?shù)。下面為查看所有玩家分?jǐn)?shù):
> redis 127.0.0.1:6379> zrevrange lb 0 -1 withscores
> 1) “user3”
> 2) “95”
> 3) “user2”
> 4) “95”
> 5) “user4”
> 6) “90”
> 7) “user1”
> 8) “89”
下面為查詢前三名玩家分?jǐn)?shù):
> redis 127.0.0.1:6379> zrevrange lb 0 2 withscores
> 1) “user3”
> 2) “95”
> 3) “user2”
> 4) “95”
> 5) “user4”
> 6) “90”
2.2.4 zrevrank查看玩家的排名
與zrevrange類似姑食,zrevrank是以分?jǐn)?shù)由高到低的排序返回玩家排名(實(shí)際返回的是以0開始的索引)波岛,對(duì)應(yīng)的zrank則是以分?jǐn)?shù)由低到高的排序返回排名。下面是查詢玩家user3和user4的排名:
> redis 127.0.0.1:6379> zrevrank lb user3
> (integer) 0
> redis 127.0.0.1:6379> zrevrank lb user1
> (integer) 3
2.2.5 zincrby增減玩家分?jǐn)?shù)
有的排行榜是在變更時(shí)重新設(shè)置玩家的分?jǐn)?shù)音半,而還有的排行榜則是以增量方式修改玩家分?jǐn)?shù)则拷,增量可正可負(fù)贡蓖。如果執(zhí)行zincrby時(shí)玩家尚不在排行榜中,則認(rèn)為其原始分?jǐn)?shù)為0煌茬,相當(dāng)于執(zhí)行zdd斥铺。下面將user4的分?jǐn)?shù)增加6,使其名次上升到第一位坛善。
> redis 127.0.0.1:6379> zincrby lb 6 user4
> “96”
> redis 127.0.0.1:6379> zrevrange lb 0 -1 withscores
> 1) “user4”
> 2) “96”
> 3) “user3”
> 4) “95”
> 5) “user2”
> 6) “95”
> 7) “user1”
> 8) “89”
2.2.6 zrem移除某個(gè)玩家
下面移除玩家user4
> redis 127.0.0.1:6379> zrem lb user4
> (integer) 1
> redis 127.0.0.1:6379> zrevrange lb 0 -1 withscores
> 1) “user3”
> 2) “95”
> 3) “user2”
> 4) “95”
> 5) “user1”
> 6) “89”
2.2.7 del刪除排行榜
排行榜對(duì)象在我們首次調(diào)用zadd或zincrby時(shí)被創(chuàng)建晾蜘,當(dāng)我們要?jiǎng)h除它時(shí),調(diào)用redis通用的命令del即可眠屎。
> redis 127.0.0.1:6379> del lb
> (integer) 1
> redis 127.0.0.1:6379> get lb
> (nil)
2.2.8 相同分?jǐn)?shù)問題
從前面的例子我們可以看到剔交,user2和user3具有相同的分?jǐn)?shù),但在按分?jǐn)?shù)逆序排序時(shí)改衩,user3排在了user2前面岖常。而在實(shí)際應(yīng)用場(chǎng)景中,我們更希望看到user2排在user3前面葫督,因?yàn)閡ser2比user3先加入排行榜竭鞍,也就是說user2先到達(dá)該分?jǐn)?shù)。但Redis在遇到分?jǐn)?shù)相同時(shí)是按照集合成員自身的字典順序來排序橄镜,這里即是按照”user2″和”user3″這兩個(gè)字符串進(jìn)行排序偎快,以逆序排序的話user3自然排到了前面。要解決這個(gè)問題蛉鹿,我們可以考慮在分?jǐn)?shù)中加入時(shí)間戳滨砍。
2.3 微信步數(shù)排行榜
本文基于微信步數(shù)排行榜介紹如何使用Redis的ZSET實(shí)現(xiàn)排行榜往湿,重點(diǎn)是Redis的ZSET的使用妖异,因此只是分析了微信步數(shù)排行榜的大致實(shí)現(xiàn)思路,實(shí)際實(shí)現(xiàn)肯定比文中分析的復(fù)雜的多领追。首先他膳,我們來分析下微信步數(shù)排行榜的需求:
排行榜是以日期為單位的,歷史日期的排行榜是可以查看的
排行榜可能并不會(huì)顯示所有好友的步數(shù)绒窑,比如我的微信有349位好友棕孙,但排行榜從來沒有顯示過這么多,假設(shè)最多只顯示步數(shù)前200的好友
步數(shù)是異步更新的些膨,所以每隔一段時(shí)間步數(shù)同步后蟀俊,排行榜都會(huì)變化
排行榜中,好友頭像和微信昵稱可以理解為不變的(變動(dòng)的幾率小订雾,就像熱搜榜中的標(biāo)題和Url)肢预,但步數(shù)和點(diǎn)贊數(shù)是可變的
基于以上分析的需求,大致實(shí)現(xiàn)思路如下:
1洼哎、使用Redis的ZSET數(shù)據(jù)結(jié)構(gòu)
2烫映、設(shè)置key時(shí)沼本,基于微信號(hào)和日期,比如我的微信是zwwhnly锭沟,今天的日期是2020-06-01抽兆,那么key就可以設(shè)計(jì)為:StepNumberRanking:zwwhnly:20200601
3、設(shè)置value時(shí)族淮,將好友的昵稱作為成員member辫红,將好友的步數(shù)作為分值score,如下所示:
4瞧筛、使用Redis的HASH數(shù)據(jù)結(jié)構(gòu)厉熟,其中key為第2步的key+第3步的成員member,value分別存儲(chǔ)好友頭像较幌、昵稱揍瑟、步數(shù)、點(diǎn)贊數(shù)乍炉,如下所示:
5绢片、獲取微信步數(shù)排行榜時(shí),分為以下2步:
A岛琼、先查詢出微信步數(shù)排行榜中的好友昵稱底循,也就是查詢
StepNumberRanking:zwwhnly:20200601的值
B、根據(jù)獲取到的好友昵稱槐瑞,查詢好友步數(shù)信息熙涤,也就是查詢
StepNumberRanking:zwwhnly:20200601:yst的值
2.3.1 ZADD初始化微信步數(shù)排行榜
執(zhí)行如下命令初始化微信步數(shù)排行榜,以上面圖片中的9個(gè)好友為例困檩,分2次初始化:
ZADD StepNumberRanking:zwwhnly:20200602 25452 yst 23683 zq 23599 ljx 20391 yyq 19628 XxZz
ZADD StepNumberRanking:zwwhnly:20200602 18261 lxx 16636 zcc 16555 clc 16098 fl
執(zhí)行完的效果如下圖所示:
可以看到祠挫,默認(rèn)是以score正序排列的,也就是步數(shù)從少到多排列悼沿。
2.3.2 HMSET存用戶詳細(xì)信息
因?yàn)檎故静綌?shù)排行榜時(shí)等舔,需要展示昵稱、頭像糟趾、步數(shù)慌植、點(diǎn)贊數(shù),所以可以借助于Redis的HASH 數(shù)據(jù)結(jié)構(gòu)來存儲(chǔ)义郑,這時(shí)就要用到HMSET命令
2.3.3 ZINCRBY更新好友步數(shù)
每隔一段時(shí)間蝶柿,好友的步數(shù)是會(huì)更新的,此時(shí)可以使用ZINCRBY命令來更新好友步數(shù)非驮,假設(shè)我們只更新步數(shù)位于前2位好友的步數(shù)交汤,給他們的步數(shù)增加10,就可以執(zhí)行以下命令:
ZINCRBY StepNumberRanking:zwwhnly:20200602 10 yst ZINCRBY StepNumberRanking:zwwhnly:20200602 10 zq
執(zhí)行完的效果如下圖所示:
更新完排行榜里的步數(shù)后院尔,不要忘記執(zhí)行HMSET命令更新好友的步數(shù):
2.3.4 ZRANGE正序
在所有的數(shù)據(jù)就緒后蜻展,剩下的就是查詢了喉誊,我們可以使用ZRANGE命令獲取排行榜里的好友信息:
ZRANGE StepNumberRanking:zwwhnly:20200602 0 -1
可以看出,查詢出的好友信息是按步數(shù)從少到多排序的纵顾,而排行榜應(yīng)該按步數(shù)從多到少排序伍茄,這就用到了下面的ZREVRANGE命令。
2.3.5 ZREVRANGE倒序
ZREVRANGE命令和ZRANGE命令類似施逾,不過是按score倒序的敷矫,剛好符合排行榜的場(chǎng)景。
ZREVRANGE StepNumberRanking:zwwhnly:20200602 0 -1 WITHSCORES
可以看出汉额,查詢出的好友信息按步數(shù)從大到小排序曹仗,剛好就是在排行榜要展示的順序。不過蠕搜,排行榜一般都不展示所有的數(shù)據(jù)怎茫,這里我們的數(shù)據(jù)比較少,如果只獲取步數(shù)top5的好友妓灌,就可以執(zhí)行如下命令:
ZREVRANGE StepNumberRanking:zwwhnly:20200602 0 4 WITHSCORES
如果你要獲取top200轨蛤,就將上面的4修改為199。
2.3.6 HGETALL獲取好友詳情
獲取到了排行榜里的好友信息虫埂,最后一步就是獲取這些好友的步數(shù)祥山、點(diǎn)贊數(shù)、頭像掉伏、昵稱這些信息缝呕,也就是我們之前使用HASH數(shù)據(jù)結(jié)構(gòu)存儲(chǔ)的信息,此時(shí)我們可以使用HGETALL命令斧散,如下所示:
HGETALL StepNumberRanking:zwwhnly:20200602:yst
資料來源:
使用Redis的有序集合實(shí)現(xiàn)排行榜功能
Redis場(chǎng)景應(yīng)用之排行榜
使用redis進(jìn)行排行榜的小秘訣