使用MySQL、Redis解決排行榜問題

問題描述

排行榜主要分為兩種黑竞,一種是并列排行榜(存在相同排名的情況)捕发,一種是嚴格排行榜(分先后順序,不存在并列名次)很魂。

一般根據(jù)不同的業(yè)務(wù)場景扎酷,選用不同的排行榜。例如遏匆,對于存在實物獎勵且前一名與后一名的獎品差距很大時法挨,往往采用嚴格排行榜,而對于只是激勵用戶的場景幅聘,則選用并列排行榜凡纳。

下面,通過舉兩個例子帝蒿,介紹一下這兩種排行榜荐糜。
假設(shè)存在一張表 tbl_score,原始數(shù)據(jù)如圖:

tbl_score 表

用到的表和數(shù)據(jù):

CREATE TABLE `tbl_score` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '用戶ID',
  `score` int(11) DEFAULT NULL COMMENT '得分',
  `created_time` int(11) DEFAULT NULL COMMENT '得分時間',
  PRIMARY KEY (`id`)
) COMMENT='得分表';

INSERT INTO `tbl_score` (`id`, `score`, `created_time`)
VALUES
    (1, 90, 1534429870),
    (2, 80, 1534429871),
    (3, 82, 1534412273),
    (4, 96, 1534429243),
    (5, 100, 1534429133),
    (6, 5, 1534429273),
    (7, 8, 1534429823),
    (8, 80, 1534429801);

方式一:并列排行榜

經(jīng)過排行后的排行結(jié)果如圖:


并列排行榜

可以看到葛超,相同得分情況下暴氏,名次相同。下一位巩掺,名次為當前用戶順序位偏序。例如,兩名第五名胖替,則下一位為第七名研儒,

方式二:嚴格排行榜

嚴格排行榜,通過多級排行独令,排列出有先后次數(shù)的排名端朵。在本例中,當相同得分的情況下燃箭,根據(jù)時間進行二次排序冲呢,最先得分的名次越高。
排行結(jié)果如圖:


嚴格排行榜招狸,得分+時間

并列排行榜的解決方法

MySQL 實現(xiàn)

SELECT `id`, `score`,
    IFNULL((SELECT COUNT(*) FROM `tbl_score` WHERE `score` > `t`.`score`), 0) + 1 AS `rank`  # 計算并列排名
FROM `tbl_score` t
ORDER BY rank ASC

通過 (SELECT COUNT(*) FROM `tbl_score` WHERE `score` > `t`.`score`)敬拓,獲取排序。計算原理是比較出比當前分數(shù)要大的條數(shù)裙戏,然后加1乘凸,獲取當前排序。例如累榜,排名第一的得分营勤,篩選出比當前大的得分行數(shù)為0,然后加1,得排名為 1葛作。

邏輯層實現(xiàn)排行

// data 為已根據(jù) score DESC 排列的數(shù)據(jù)

const ranked = data.map(function(item, i) {
    if (i > 0) {
        // 獲取上一個元素
        var prevItem = data[i - 1];
        if (prevItem.score == item.score) {
            // 得分相同寿羞,則排序相同
            item.rank = prevItem.rank;
        } else {
            // 得分不相同,則排序 + 1
            item.rank = i + 1;
        }
    } else {
        // 第一個數(shù)據(jù)赂蠢,排序為 1
        item.rank = 1;
    }

    return item;
});

精確排行榜的解決方法

MySQL 實現(xiàn)

根據(jù)多級條件進行排序绪穆,本例中以得分、時間為條件進行排序客年。優(yōu)先對得分按照降序進行排序霞幅,其次是以時間升序進行排序。得分高且獲得分數(shù)越早的玩家量瓜,排序越靠前司恳。

最后,計算行號得到排名绍傲。

SELECT `id`,`score`, `created_time`, (@rowNum := @rowNum + 1) as rank
FROM tbl_score, (SELECT @rowNum := 0) b
ORDER BY `score` DESC, `created_time` ASC

Redis 實現(xiàn)

主要利用Redis的有序數(shù)列集合(zset)實現(xiàn)扔傅。

實現(xiàn)原理

有序集合由三部分組成,KEY(鍵)烫饼、score(成員的得分)猎塞、member(成員)。有序集合的每一項杠纵,都是以鍵值對的形式存儲荠耽,每一項都有一個分數(shù)。有序集合會根據(jù)score自動排序比藻。利用這個特性铝量,我們就可以實現(xiàn)排行榜了。

scoro 是數(shù)字類型银亲,可以是整形也可以是浮點型慢叨。按照排行榜多級排序的要求,相同分值下按照先來后到的順序排序(創(chuàng)建時間越早务蝠,排序越高)拍谐,但是Redis相同分值,是按照 memberASCII碼進行排序馏段。
如果直接將得分作為有序集合的 score轩拨,得不到我們想要的效果。

zset score 按照ASCII碼排序

所以院喜,需要將 score進行改造亡蓉,同時記錄得分與時間信息。實現(xiàn)方式有兩種:

  1. score 為整數(shù)够坐。得分 + 時間差寸宵。
  2. score 為浮點數(shù)。整數(shù)部分使用得分元咙,小數(shù)部分使用時間差梯影。

假設(shè)得分為 10, 得分時間為 1534649521(Unix時間戳庶香。2018/8/19 11:32:01), 截止時間為 1534694400(Unix時間戳甲棍。2018/8/20 0:0:0。 若無截止時間赶掖,取值為 9999999999)感猛。
方式一:

10 + (1534694400 - 1534649521) = 44889

方式二:

10.44879

選第二種方式的好處是,當需求不僅需要排序奢赂,還需展示得分時陪白,可以將 score 強制轉(zhuǎn)化成整形,即可獲取到得分膳灶。需要注意的是咱士,得分最好不要太大, score得分盡量控制在16位以下(浮點數(shù)時轧钓,小數(shù)點前后位數(shù)和不要超過16位序厉,最好15位)。超過16位后毕箍,score值存入redis弛房,會發(fā)生精度丟失。

超過16位而柑,精度發(fā)生丟失

獲取排行榜

ZREVRANGE test 0 -1 withscores

實現(xiàn)步驟

  1. 排行榜
  2. 相同得分文捶,按照先來后到的順序
  3. redis有序集合
  4. 計算score值
  5. 得到排序排行榜

總結(jié)

本文主要對并列排行榜和精確排行榜的問題進行了分析,采用Redis 或MySQL的方式解決排行榜問題牺堰。在使用Redis解決精確排行榜問題時拄轻,遇到了 score 精度問題。開發(fā)者需要特別注意此點伟葫,對于特別大的 score時恨搓,需要特別處理。例如筏养,將大數(shù)值轉(zhuǎn)換成小數(shù)值斧抱。

關(guān)于并列排行榜處理問題,在實踐中仍然存在一些問題渐溶。例如辉浦,對于排序計算較復(fù)雜的場景。我們會使用定時任務(wù)提前計算好排行榜茎辐,然后將數(shù)據(jù)寫入緩存中宪郊。這樣掂恕,用戶在拉取排行榜時能得到較快響應(yīng)。

但是當既需要展示排名弛槐,又需要展示得分信息懊亡,且需要保證獲取的數(shù)組是按序時,為了方便查詢時乎串,較快獲取排名店枣、得分。我采用的方式是叹誉,將排名鸯两、得分分別寫入兩個 zset中。理想狀態(tài)下长豁,期望寫一個 zset钧唐,并同時儲存排名、得分信息匠襟,且方便取出逾柿,又不會丟失精度。目前宅此,沒有找到這種解決辦法机错。

參考文章

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末父腕,一起剝皮案震驚了整個濱河市弱匪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌璧亮,老刑警劉巖萧诫,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異枝嘶,居然都是意外死亡帘饶,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進店門群扶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來及刻,“玉大人,你說我怎么就攤上這事竞阐〗煞梗” “怎么了?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵骆莹,是天一觀的道長颗搂。 經(jīng)常有香客問我,道長幕垦,這世上最難降的妖魔是什么丢氢? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任傅联,我火速辦了婚禮,結(jié)果婚禮上疚察,老公的妹妹穿的比我還像新娘纺且。我一直安慰自己,他們只是感情好稍浆,可當我...
    茶點故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著猜嘱,像睡著了一般衅枫。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上朗伶,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天弦撩,我揣著相機與錄音,去河邊找鬼论皆。 笑死益楼,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的点晴。 我是一名探鬼主播感凤,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼粒督!你這毒婦竟也來了陪竿?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤屠橄,失蹤者是張志新(化名)和其女友劉穎族跛,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體锐墙,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡礁哄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了溪北。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片桐绒。...
    茶點故事閱讀 38,716評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖之拨,靈堂內(nèi)的尸體忽然破棺而出掏膏,到底是詐尸還是另有隱情,我是刑警寧澤敦锌,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布馒疹,位于F島的核電站,受9級特大地震影響乙墙,放射性物質(zhì)發(fā)生泄漏颖变。R本人自食惡果不足惜生均,卻給世界環(huán)境...
    茶點故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望腥刹。 院中可真熱鬧马胧,春花似錦、人聲如沸衔峰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽垫卤。三九已至威彰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間穴肘,已是汗流浹背歇盼。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留评抚,地道東北人豹缀。 一個月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓,卻偏偏與公主長得像慨代,于是被迫代替她去往敵國和親邢笙。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,612評論 2 350

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