久久精品人人爽,华人av在线,亚洲性视频网站,欧美专区一二三

Redis如何實現(xiàn)排行榜及相同積分按時間排序功能

230次閱讀
沒有評論

共計 3669 個字符,預(yù)計需要花費 10 分鐘才能閱讀完成。

這篇“Redis 如何實現(xiàn)排行榜及相同積分按時間排序功能”文章的知識點大部分人都不太理解,所以丸趣 TV 小編給大家總結(jié)了以下內(nèi)容,內(nèi)容詳細(xì),步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“Redis 如何實現(xiàn)排行榜及相同積分按時間排序功能”文章吧。

在日常的開發(fā)中,經(jīng)常會碰到需要對用戶的分值等進(jìn)行排序,比如在游戲里面需要對戰(zhàn)斗力進(jìn)行排行,在組隊活動中需要對各個隊伍的貢獻(xiàn)值進(jìn)行排行,在微信中需要對各個好友的步數(shù)進(jìn)行排行,此時一般會選擇 redis 的有序集合對用戶的分?jǐn)?shù)進(jìn)行存儲,從而實現(xiàn)排行榜的需求,但是不同的場景排行榜的方式也略有不同,以下根據(jù)自己日常的開發(fā)進(jìn)行了一下歸納總結(jié)。

需求:對組隊活動中各個隊伍的貢獻(xiàn)值進(jìn)行排行。

不考慮積分相同

Redis 的 Sorted Set 是 String 類型的有序集合。集合成員是唯一的,這就意味著集合中不能出現(xiàn)重復(fù)的數(shù)據(jù)。

每個元素都會關(guān)聯(lián)一個 double 類型的分?jǐn)?shù)。redis 正是通過分?jǐn)?shù)來為集合中的成員進(jìn)行從小到大的排序。

有序集合的成員是唯一的,但分?jǐn)?shù) (score) 卻可以重復(fù)。

下面先不考慮積分相同的情況,實現(xiàn)排行榜:

//  準(zhǔn)備數(shù)據(jù),其中 value 為每個隊伍的 ID,score 為隊伍的貢獻(xiàn)值
  zadd z1 5 a 6 b 1 c 2 d 10 e
(integer) 5
//  分頁查詢排行榜所有的隊伍和貢獻(xiàn)值,要使用 zrevrange,而不是 zrange,貢獻(xiàn)值越大越排在前面
  zrevrange z1 0 2 withscores
1)  e 
2)  10 
3)  b 
4)  6 
5)  a 
6)  5 
//  增加某個隊伍的貢獻(xiàn)值
  zincrby z1 3 d
  zincrby z1 4 c
//  查詢排行榜所有的隊伍
  zrevrange z1 0 -1 withscores
 1)  e 
 2)  10 
 3)  b 
 4)  6 
 5)  d 
 6)  5 
 7)  c 
 8)  5 
 9)  a 
10)  5 
//  查詢某個隊伍的排名
  zrevrank z1 d
(integer) 2

Redis 默認(rèn)實現(xiàn)是相同分?jǐn)?shù)的成員按字典順序排序(09,AZ,a~z),上面使用的是 zrevrange,所以是倒序,所以相同分?jǐn)?shù)排序就不能根據(jù)時間優(yōu)先來排序。

積分相同按時間排序,排名唯一

在上面的實現(xiàn)中,如果兩個隊伍的貢獻(xiàn)值相同,也就是積分值相同,無法根據(jù)時間的先后進(jìn)行排行。

所以需要設(shè)計一個分?jǐn)?shù) = 貢獻(xiàn)值 + 時間戳,誰分?jǐn)?shù)大誰排前面,最后還要能根據(jù)分?jǐn)?shù)能解析出來貢獻(xiàn)值。

設(shè)計 1

使用整型存儲分?jǐn)?shù)值,redis 中 score 本身是一個 double 類型,能精確存儲的最大整型數(shù)字為 2^53=9007199254740992(16 位)。而精確到毫秒的時間戳需要 13 位,此時留給存儲貢獻(xiàn)值只有 3 位數(shù)了,當(dāng)前如果時間只要精確到秒,只需要 10 位,這樣留給貢獻(xiàn)值就有 6 位。

整體設(shè)計:高 3 位表示貢獻(xiàn)值,低 13 位表示時間戳。

如果我們簡單地把 score 結(jié)構(gòu)由:貢獻(xiàn)值 * 10^13 + 時間戳 拼湊,因為分?jǐn)?shù)越大越靠前,而時間戳越小則越靠前,這樣兩部分的判斷規(guī)則是相反的,無法簡單把兩者合成一起成為 score。

但是我們可以逆向思維,可以用同一個足夠大的數(shù) Integer.MAX 減去時間戳,時間戳越小,則得到的差值越大,這樣我們就可以把 score 的結(jié)構(gòu)改為:貢獻(xiàn)值 * 10^13 + (Integer.MAX- 時間戳),這樣就能滿足我們的需求了。

設(shè)計 2

由于 redis 的 score 值是 double 類型,可以使用整數(shù)部分存儲貢獻(xiàn)值,小數(shù)部分存儲時間戳,同樣時間戳的部分使用一個最大值減去它。

這樣,整體設(shè)計變?yōu)椋悍謹(jǐn)?shù) = 貢獻(xiàn)值 + (Integer.MAX- 時間戳) * 10^-13

弊端:由于分?jǐn)?shù)值是由兩個變量來計算得出,所以在給隊伍增加貢獻(xiàn)值時,無法簡單的使用之前的 zincrby 來改變 score 的值了,這樣在并發(fā)情況下為隊伍增加貢獻(xiàn)值就會導(dǎo)致 score 值不準(zhǔn)確。

錯誤情況模擬:

假設(shè)現(xiàn)在隊伍 A 的貢獻(xiàn)值為 10 隊伍 A 中的隊員 X 為隊伍增加貢獻(xiàn)值 1,在程序中算出 score 為 11.xxx 隊伍 A 中的隊員 Y 為隊伍增加貢獻(xiàn)值 1,在程序中算出 score 為 11.yyy 隊伍 A 中的隊員 X 調(diào)用 redis 的 zadd 命令設(shè)置隊伍的貢獻(xiàn)值為 11.xxx 隊伍 A 中的隊員 Y 調(diào)用 redis 的 zadd 命令設(shè)置隊伍的貢獻(xiàn)值為 11.yyy 最后算出隊伍 A 的貢獻(xiàn)值為 11,無法保證增加貢獻(xiàn)值這一個操作的原子性。

此時需要借助 lua 腳本來保證計算和設(shè)置貢獻(xiàn)值這兩個操作的原子性:

//  其中 KEYS[1]為排行榜 key,KEYS[2]為隊伍 ID
//  其中 ARGV[1]為增加的貢獻(xiàn)值,ARGV[2]為 Integer.MAX- 時間戳
local score = redis.call(zscore , KEYS[1], KEYS[2]) 
if not(score) then
 score=0 
end 
score=math.floor(score) + tonumber(ARGV[1]) + tonumber(ARGV[2]) 
redis.call(zadd , KEYS[1], score, KEYS[2]) return 1

由于 redis 中無法使用時間函數(shù),所以(Integer.MAX- 時間戳) * 10^-13 部分由腳本外程序計算好傳入。

分頁查詢排行榜,查詢隊伍的排名等功能都可以繼續(xù)使用上面的命令。

積分相同按時間排序,并列排名

所謂并列排行榜,就是存在相同排名情況的排行榜。

我們期望的結(jié)果如下表:

隊伍 ID 貢獻(xiàn)值排名 a1001b992c992d884e875

當(dāng)然現(xiàn)實中也有排名不跳過的情況,我這里考慮的是排名跳過的情況。

redis 中 score 的設(shè)計還是采用上面的分?jǐn)?shù) = 貢獻(xiàn)值 + (Integer.MAX- 時間戳) * 10^-13,只是在查詢排名時需要進(jìn)行計算。

比如要查上表中隊伍 b 的排名,思路如下:

首先查到隊伍 b 的 score

再查到跟隊伍 b 的 score 的整數(shù)部分相同(也就是貢獻(xiàn)值一樣),排在第一個的隊伍的 value(隊伍 ID)

根據(jù)上一步得到的隊伍 ID 查詢此隊伍的排名就是隊伍 b 的排名

使用命令實現(xiàn)上面的步驟如下:

 zscore  排行榜 key teamId
  zrevrangebyscore(排行榜 key,  上一步得到的 score+1,  上一步得到的 score, limit, 0 , 1)
  zrevrank(排行榜 key,  上一步得到的 teamId)

為了性能考慮,可以使用下面的腳本一次查出來:

// KEYS[1]表示排行榜 key
// KEYS[2]表示要查詢的隊伍的 ID
local rank = 0 
local score = redis.call(zscore , KEYS[1], KEYS[2]) 
if not(score) then
 score=0 
else 
 score=math.floor(score) 
 local firstScore = redis.call(zrevrangebyscore , KEYS[1], score+1, score,  limit , 0, 1) 
 rank=redis.call(zrevrank , KEYS[1], firstScore[1]) 
end 
return {score,rank}

下面附上分頁查詢排行榜的腳本,假如一頁 10 條,不用下面的腳本需要查詢 10 次上面的腳本,如果連上面的腳本都沒有使用的話就要查詢 30 次 redis。

//  排行榜 key
// ARGV[1]分頁起始偏移
// ARGV[2]分頁結(jié)束偏移
local list = redis.call(zrevrange , KEYS[1], ARGV[1], ARGV[2],  withscores ) 
local result={} 
local i = 1 
for k,v in pairs(list) do 
 if k%2 == 0 then 
 local teamId = list[k-1] 
 local score = math.floor(v) 
 local firstScore = redis.call(zrevrangebyscore , KEYS[1], score+1, score,  limit , 0, 1) 
 local rank=redis.call(zrevrank , KEYS[1], firstScore[1]) 
 local l = {teamId=teamId, contributionValue=score, teamRank=rank+1} 
 result[i] = l i = i + 1 
 end 
end 
return cjson.encode(result)

此腳本使用了 cjson 庫,返回的是一個 json。

以上就是關(guān)于“Redis 如何實現(xiàn)排行榜及相同積分按時間排序功能”這篇文章的內(nèi)容,相信大家都有了一定的了解,希望丸趣 TV 小編分享的內(nèi)容對大家有幫助,若想了解更多相關(guān)的知識內(nèi)容,請關(guān)注丸趣 TV 行業(yè)資訊頻道。

正文完
 
丸趣
版權(quán)聲明:本站原創(chuàng)文章,由 丸趣 2023-07-13發(fā)表,共計3669字。
轉(zhuǎn)載說明:除特殊說明外本站除技術(shù)相關(guān)以外文章皆由網(wǎng)絡(luò)搜集發(fā)布,轉(zhuǎn)載請注明出處。
評論(沒有評論)
主站蜘蛛池模板: 佳木斯市| 安溪县| 孝义市| 咸宁市| 饶阳县| 富锦市| 卫辉市| 山阳县| 桂平市| 东安县| 文登市| 青海省| 桂阳县| 抚宁县| 荣昌县| 大名县| 阿城市| 陇川县| 武安市| 龙南县| 延津县| 阳新县| 武山县| 四平市| 宁陵县| 浏阳市| 自治县| 松原市| 邵武市| 上饶市| 通城县| 休宁县| 黄冈市| 岳西县| 额济纳旗| 柯坪县| 梁河县| 炉霍县| 永安市| 务川| 中卫市|