使用redis的有序集合實現(xiàn)排行榜功能

游戲中存在各種各樣的排行榜反症,比如玩家的等級排名强胰、分數(shù)排名等稳摄。玩家在排行榜中的名次是其實力的象征粮坞,位于榜單前列的玩家在虛擬世界中擁有無尚榮耀肪凛,所以名次也就成了核心玩家的追求目標。

一個典型的游戲排行榜包括以下常見功能:

  1. 能夠記錄每個玩家的分數(shù);
  2. 能夠對玩家的分數(shù)進行更新;
  3. 能夠查詢每個玩家的分數(shù)和名次濒翻;
  4. 能夠按名次查詢排名前N名的玩家;
  5. 能夠查詢排在指定玩家前后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)就非常適合于排行榜的構建。下面簡要總結一下药薯。

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)(http://redis.io)下載最新的代碼自行編譯,步驟略穷娱。

2. ZSet的常用命令

有序集合首先是集合绑蔫,其成員(member)具有唯一性,其次泵额,每個成員關聯(lián)了一個分數(shù)(score)配深,使得成員可以按照分數(shù)排序。關于有序集合的介紹見http://redis.io/topics/data-types#sorted-sets嫁盲,其命令見http://redis.io/commands#sorted_set篓叶。

下面介紹幾個能用于排行榜的命令。

假設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 排行榜名稱 起始位置 結束位置 [withscores] 時間復雜度:O(log(N)+M)

由于排行榜一般是按照分數(shù)由高到低排序的京革,所以我們使用zrevrange奇唤,而命令zrange是按照分數(shù)由低到高排序。

起始位置和結束位置都是以0開始的索引匹摇,且都包含在內(nèi)咬扇。如果結束位置為-1則查看范圍為整個排行榜。

帶上withscores則會返回玩家分數(shù)廊勃。

下面為查看所有玩家分數(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”

下面為查詢前三名玩家分數(shù)。

redis 127.0.0.1:6379> zrevrange lb 0 2 withscores

  1. “user3”
  2. “95”
  3. “user2”
  4. “95”
  5. “user4”
  6. “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

  1. “user4”
  2. “96”
  3. “user3”
  4. “95”
  5. “user2”
  6. “95”
  7. “user1”
  8. “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

  1. “user3”
  2. “95”
  3. “user2”
  4. “95”
  5. “user1”
  6. “89”
7) del——刪除排行榜

命令格式:del 排行榜名稱

排行榜對象在我們首次調用zadd或zincrby時被創(chuàng)建爆阶,當我們要刪除它時,調用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倍积担,然后把兩部分相加的結果作為zset的分數(shù)♀剩考慮到要按時間倒序排列帝璧,所以時間戳這部分需要顛倒一下,這便是用9999999999減去時間戳的原因湿刽。當我們要讀取玩家實際分數(shù)時的烁,只需去掉后10位即可。

初步看起來這個方案還不錯诈闺,但這里面有兩個問題渴庆。

第一個問題是小問題,采用秒為時間戳可能區(qū)分度還不夠买雾,如果同一秒出現(xiàn)兩個分數(shù)相同的仍然會出現(xiàn)前面的問題把曼,當然我們可以選擇精度更高的時間戳,但在實際場景中漓穿,同一秒誰排前面已經(jīng)無關緊要。

第二個問題是大問題注盈,因為Redis的分數(shù)類型采用的是double晃危,64位雙精度浮點數(shù)只有52位有效數(shù)字,它能精確表達的整數(shù)范圍為-253到253老客,最高只能表示16位十進制整數(shù)(最大值為9007199254740992僚饭,其實連16位也不能完整表示)。這就是說胧砰,如果前面時間戳占了10位的話鳍鸵,分數(shù)就只剩下6位了,這對于某些排行榜分數(shù)來說是不夠用的尉间。我們可以考慮縮減時間戳位數(shù)偿乖,比如從2015年1月1日開始計時击罪,但這仍然增加不了幾位√靶剑或者減少區(qū)分度媳禁,以分鐘、小時來作為時間戳單位画切。

如果Redis的分數(shù)類型為int64竣稽,我們就沒有上面的煩惱。說到這里霍弹,其實Redis真應該再額外提供一個int64類型的ZSet毫别,但目前只能是幻想,除非自己改其源碼典格。

既然Redis也不能完美解決排行榜問題拧烦,那最終是不是有必要自己實現(xiàn)一個專門的排行榜數(shù)據(jù)結構呢?畢竟實際應用中的排行榜有很多可以優(yōu)化的地方钝计,比玩家呈金字塔分布恋博,越是低分段玩家數(shù)量越多,同一分數(shù)擁有大量玩家私恬,玩家增加一分都可能超越很多玩家债沮,這就為優(yōu)化提供了可能。

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末本鸣,一起剝皮案震驚了整個濱河市疫衩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌荣德,老刑警劉巖闷煤,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異涮瞻,居然都是意外死亡鲤拿,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門署咽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來近顷,“玉大人,你說我怎么就攤上這事宁否≈仙” “怎么了?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵慕匠,是天一觀的道長饱须。 經(jīng)常有香客問我,道長台谊,這世上最難降的妖魔是什么蓉媳? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任譬挚,我火速辦了婚禮,結果婚禮上督怜,老公的妹妹穿的比我還像新娘殴瘦。我一直安慰自己,他們只是感情好号杠,可當我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布蚪腋。 她就那樣靜靜地躺著,像睡著了一般姨蟋。 火紅的嫁衣襯著肌膚如雪屉凯。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天眼溶,我揣著相機與錄音悠砚,去河邊找鬼。 笑死堂飞,一個胖子當著我的面吹牛灌旧,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播绰筛,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼枢泰,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了铝噩?” 一聲冷哼從身側響起衡蚂,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎骏庸,沒想到半個月后毛甲,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡具被,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年玻募,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片硬猫。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡补箍,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出啸蜜,到底是詐尸還是另有隱情,我是刑警寧澤辈挂,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布衬横,位于F島的核電站,受9級特大地震影響终蒂,放射性物質發(fā)生泄漏蜂林。R本人自食惡果不足惜遥诉,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望噪叙。 院中可真熱鬧矮锈,春花似錦、人聲如沸睁蕾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽子眶。三九已至瀑凝,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間臭杰,已是汗流浹背粤咪。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留渴杆,地道東北人寥枝。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像磁奖,于是被迫代替她去往敵國和親囊拜。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,925評論 2 344

推薦閱讀更多精彩內(nèi)容