這篇文章給大家分享的是有關如何使用Redis的有序集合實現(xiàn)排行榜功能的內(nèi)容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。
創(chuàng)新互聯(lián)公司專注于企業(yè)網(wǎng)絡營銷推廣、網(wǎng)站重做改版、吳堡網(wǎng)站定制設計、自適應品牌網(wǎng)站建設、H5建站、成都商城網(wǎng)站開發(fā)、集團公司官網(wǎng)建設、外貿(mào)營銷網(wǎng)站建設、高端網(wǎng)站制作、響應式網(wǎng)頁設計等建站業(yè)務,價格優(yōu)惠性價比高,為吳堡等各大城市提供網(wǎng)站開發(fā)制作服務。一個典型的游戲排行榜包括以下常見功能:
能夠記錄每個玩家的分數(shù);
能夠?qū)ν婕业姆謹?shù)進行更新;
能夠查詢每個玩家的分數(shù)和名次;
能夠按名次查詢排名前N名的玩家;
能夠查詢排在指定玩家前后M名的玩家。
更進一步,上面的操作都需要在短時間內(nèi)實時完成,這樣才能大程度發(fā)揮排行榜的效用。
由于一個玩家名次上升x位將會引起x+1位玩家的名次發(fā)生變化(包括該玩家),如果采用傳統(tǒng)數(shù)據(jù)庫(比如MySQL)來實現(xiàn)排行榜,當玩家人數(shù)較多時,將會導致對數(shù)據(jù)庫的頻繁修改,性能得不到滿足,所以我們只能另想它法。
Redis作為NoSQL中的一員,近年來得到廣泛應用。與Memcached相比,Redis擁有更多的數(shù)據(jù)類型和操作接口,具有更大的適用范圍,其中的有序集合(sorted set,也稱為zset)就非常適合于排行榜的構(gòu)建。下面簡要總結(jié)一下。
1. Redis的安裝
Ubuntu下安裝Redis非常簡單,執(zhí)行如下命令即可:
$ sudo apt-get install redis-server
安裝完畢,運行命令行客戶端redis-cli就可以訪問本地redis服務器。
$ redis-cli redis 127.0.0.1:6379>
如果要使用最新版本,需要到Redis官網(wǎng)(redis.io)下載最新的代碼自行編譯,步驟略。
2. ZSet的常用命令
有序集合首先是集合,其成員(member)具有唯一性,其次,每個成員關聯(lián)了一個分數(shù)(score),使得成員可以按照分數(shù)排序。關于有序集合的介紹見redis.io/topics/data…,其命令見redis.io/commands#so…。
下面介紹幾個能用于排行榜的命令。
假設lb為排行榜名稱,user1、user2等為玩家唯一標識。
1) zadd——設置玩家分數(shù)
命令格式:zadd 排行榜名稱 分數(shù) 玩家標識 時間復雜度:O(log(N))
下面設置了4個玩家的分數(shù),如果玩家分數(shù)已經(jīng)存在,則會覆蓋之前的分數(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) zscore——查看玩家分數(shù)
命令格式:zscore 排行榜名稱 玩家標識 時間復雜度:O(1)
下面是查看user2這個玩家在lb排行榜中的分數(shù)。
redis 127.0.0.1:6379> zscore lb user2 “95”
3) zrevrange——按名次查看排行榜
命令格式:zrevrange 排行榜名稱 起始位置 結(jié)束位置 [withscores] 時間復雜度:O(log(N)+M)
由于排行榜一般是按照分數(shù)由高到低排序的,所以我們使用zrevrange,而命令zrange是按照分數(shù)由低到高排序。
起始位置和結(jié)束位置都是以0開始的索引,且都包含在內(nèi)。如果結(jié)束位置為-1則查看范圍為整個排行榜。
帶上withscores則會返回玩家分數(shù)。
下面為查看所有玩家分數(shù)。
redis 127.0.0.1:6379> zrevrange lb 0 -1 withscores “user3” “95” “user2” “95” “user4” “90” “user1” “89”
下面為查詢前三名玩家分數(shù)。
redis 127.0.0.1:6379> zrevrange lb 0 2 withscores “user3” “95” “user2” “95” “user4” “90”
4) zrevrank——查看玩家的排名
命令格式:zrevrank 排行榜名稱 玩家標識 時間復雜度:O(log(N))
與zrevrange類似,zrevrank是以分數(shù)由高到低的排序返回玩家排名(實際返回的是以0開始的索引),對應的zrank則是以分數(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
5) zincrby——增減玩家分數(shù)
命令格式:zincrby 排行榜名稱 分數(shù)增量 玩家標識 時間復雜度:O(log(N))
有的排行榜是在變更時重新設置玩家的分數(shù),而還有的排行榜則是以增量方式修改玩家分數(shù),增量可正可負。如果執(zhí)行zincrby時玩家尚不在排行榜中,則認為其原始分數(shù)為0,相當于執(zhí)行zdd。
下面將user4的分數(shù)增加6,使其名次上升到第一位。
redis 127.0.0.1:6379> zincrby lb 6 user4 “96” redis 127.0.0.1:6379> zrevrange lb 0 -1 withscores “user4” “96” “user3” “95” “user2” “95” “user1” “89”
6) zrem——移除某個玩家
命令格式:zrem 排行榜名稱 玩家標識 時間復雜度:O(log(N))
下面移除玩家user4。
redis 127.0.0.1:6379> zrem lb user4 (integer) 1 redis 127.0.0.1:6379> zrevrange lb 0 -1 withscores “user3” “95” “user2” “95” “user1” “89”
7) del——刪除排行榜
命令格式:del 排行榜名稱
排行榜對象在我們首次調(diào)用zadd或zincrby時被創(chuàng)建,當我們要刪除它時,調(diào)用redis通用的命令del即可。
redis 127.0.0.1:6379> del lb (integer) 1 redis 127.0.0.1:6379> get lb (nil)
3. 相同分數(shù)問題
免費的方案總有那么一些不完美。從前面的例子我們可以看到,user2和user3具有相同的分數(shù),但在按分數(shù)逆序排序時,user3排在了user2前面。而在實際應用場景中,我們更希望看到user2排在user3前面,因為user2比user3先加入排行榜,也就是說user2先到達該分數(shù)。
但Redis在遇到分數(shù)相同時是按照集合成員自身的字典順序來排序,這里即是按照”user2″和”user3″這兩個字符串進行排序,以逆序排序的話user3自然排到了前面。
要解決這個問題,我們可以考慮在分數(shù)中加入時間戳,計算公式為:
帶時間戳的分數(shù) = 實際分數(shù)*10000000000 + (9999999999 – timestamp)
timestamp我們采用系統(tǒng)提供的time()函數(shù),也就是1970年1月1日以來的秒數(shù),我們采用32位的時間戳(這能堅持到2038年),由于32位時間戳是10位十進制整數(shù)(大值4294967295),所以我們讓時間戳占據(jù)低10位(十進制整數(shù)),實際分數(shù)則擴大10^10倍,然后把兩部分相加的結(jié)果作為zset的分數(shù)。考慮到要按時間倒序排列,所以時間戳這部分需要顛倒一下,這便是用9999999999減去時間戳的原因。當我們要讀取玩家實際分數(shù)時,只需去掉后10位即可。
初步看起來這個方案還不錯,但這里面有兩個問題。
第一個問題是小問題,采用秒為時間戳可能區(qū)分度還不夠,如果同一秒出現(xiàn)兩個分數(shù)相同的仍然會出現(xiàn)前面的問題,當然我們可以選擇精度更高的時間戳,但在實際場景中,同一秒誰排前面已經(jīng)無關緊要。
第二個問題是大問題,因為Redis的分數(shù)類型采用的是double,64位雙精度浮點數(shù)只有52位有效數(shù)字,它能精確表達的整數(shù)范圍為-2^53到2^53,最高只能表示16位十進制整數(shù)(大值為9007199254740992,其實連16位也不能完整表示)。這就是說,如果前面時間戳占了10位的話,分數(shù)就只剩下6位了,這對于某些排行榜分數(shù)來說是不夠用的。我們可以考慮縮減時間戳位數(shù),比如從2015年1月1日開始計時,但這仍然增加不了幾位。或者減少區(qū)分度,以分鐘、小時來作為時間戳單位。
如果Redis的分數(shù)類型為int64,我們就沒有上面的煩惱。說到這里,其實Redis真應該再額外提供一個int64類型的ZSet,但目前只能是幻想,除非自己改其源碼。
既然Redis也不能完美解決排行榜問題,那最終是不是有必要自己實現(xiàn)一個專門的排行榜數(shù)據(jù)結(jié)構(gòu)呢?畢竟實際應用中的排行榜有很多可以優(yōu)化的地方,比玩家呈金字塔分布,越是低分段玩家數(shù)量越多,同一分數(shù)擁有大量玩家,玩家增加一分都可能超越很多玩家,這就為優(yōu)化提供了可能。
感謝各位的閱讀!關于“如何使用Redis的有序集合實現(xiàn)排行榜功能”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,讓大家可以學到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!