您好,登錄后才能下訂單哦!
這篇文章主要介紹“Redis怎么實(shí)現(xiàn)排行榜及相同積分按時(shí)間排序功能”,在日常操作中,相信很多人在Redis怎么實(shí)現(xiàn)排行榜及相同積分按時(shí)間排序功能問(wèn)題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”Redis怎么實(shí)現(xiàn)排行榜及相同積分按時(shí)間排序功能”的疑惑有所幫助!接下來(lái),請(qǐng)跟著小編一起來(lái)學(xué)習(xí)吧!
Redis的Sorted Set是String類型的有序集合。集合成員是唯一的,這就意味著集合中不能出現(xiàn)重復(fù)的數(shù)據(jù)。
每個(gè)元素都會(huì)關(guān)聯(lián)一個(gè)double類型的分?jǐn)?shù)。redis正是通過(guò)分?jǐn)?shù)來(lái)為集合中的成員進(jìn)行從小到大的排序。
有序集合的成員是唯一的,但分?jǐn)?shù)(score)卻可以重復(fù)。
下面先不考慮積分相同的情況,實(shí)現(xiàn)排行榜:
// 準(zhǔn)備數(shù)據(jù),其中value為每個(gè)隊(duì)伍的ID,score為隊(duì)伍的貢獻(xiàn)值 > zadd z1 5 a 6 b 1 c 2 d 10 e (integer) 5 // 分頁(yè)查詢排行榜所有的隊(duì)伍和貢獻(xiàn)值,要使用zrevrange,而不是zrange,貢獻(xiàn)值越大越排在前面 > zrevrange z1 0 2 withscores 1) "e" 2) "10" 3) "b" 4) "6" 5) "a" 6) "5" // 增加某個(gè)隊(duì)伍的貢獻(xiàn)值 > zincrby z1 3 d "5" > zincrby z1 4 c "5" // 查詢排行榜所有的隊(duì)伍 > 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" // 查詢某個(gè)隊(duì)伍的排名 > zrevrank z1 d (integer) 2
Redis默認(rèn)實(shí)現(xiàn)是相同分?jǐn)?shù)的成員按字典順序排序(09,AZ,a~z),上面使用的是zrevrange,所以是倒序,所以相同分?jǐn)?shù)排序就不能根據(jù)時(shí)間優(yōu)先來(lái)排序。
在上面的實(shí)現(xiàn)中,如果兩個(gè)隊(duì)伍的貢獻(xiàn)值相同,也就是積分值相同,無(wú)法根據(jù)時(shí)間的先后進(jìn)行排行。
所以需要設(shè)計(jì)一個(gè)分?jǐn)?shù) = 貢獻(xiàn)值 + 時(shí)間戳 ,誰(shuí)分?jǐn)?shù)大誰(shuí)排前面,最后還要能根據(jù)分?jǐn)?shù)能解析出來(lái)貢獻(xiàn)值。
使用整型存儲(chǔ)分?jǐn)?shù)值,redis中score本身是一個(gè)double類型,能精確存儲(chǔ)的最大整型數(shù)字為2^53=9007199254740992(16位)。而精確到毫秒的時(shí)間戳需要13位,此時(shí)留給存儲(chǔ)貢獻(xiàn)值只有3位數(shù)了,當(dāng)前如果時(shí)間只要精確到秒,只需要10位,這樣留給貢獻(xiàn)值就有6位。
整體設(shè)計(jì):高3位表示貢獻(xiàn)值,低13位表示時(shí)間戳。
如果我們簡(jiǎn)單地把score結(jié)構(gòu)由:貢獻(xiàn)值 * 10^13 + 時(shí)間戳
拼湊,因?yàn)榉謹(jǐn)?shù)越大越靠前,而時(shí)間戳越小則越靠前,這樣兩部分的判斷規(guī)則是相反的,無(wú)法簡(jiǎn)單把兩者合成一起成為score。
但是我們可以逆向思維,可以用同一個(gè)足夠大的數(shù)Integer.MAX減去時(shí)間戳,時(shí)間戳越小,則得到的差值越大,這樣我們就可以把score的結(jié)構(gòu)改為:貢獻(xiàn)值 * 10^13 + (Integer.MAX-時(shí)間戳)
,這樣就能滿足我們的需求了。
由于redis的score值是double類型,可以使用整數(shù)部分存儲(chǔ)貢獻(xiàn)值,小數(shù)部分存儲(chǔ)時(shí)間戳,同樣時(shí)間戳的部分使用一個(gè)最大值減去它。
這樣,整體設(shè)計(jì)變?yōu)椋?code>分?jǐn)?shù)=貢獻(xiàn)值 + (Integer.MAX-時(shí)間戳) * 10^-13
弊端:由于分?jǐn)?shù)值是由兩個(gè)變量來(lái)計(jì)算得出,所以在給隊(duì)伍增加貢獻(xiàn)值時(shí),無(wú)法簡(jiǎn)單的使用之前的zincrby來(lái)改變score的值了,這樣在并發(fā)情況下為隊(duì)伍增加貢獻(xiàn)值就會(huì)導(dǎo)致score值不準(zhǔn)確。
錯(cuò)誤情況模擬:
假設(shè)現(xiàn)在隊(duì)伍A的貢獻(xiàn)值為10隊(duì)伍A中的隊(duì)員X為隊(duì)伍增加貢獻(xiàn)值1,在程序中算出score為11.xxx隊(duì)伍A中的隊(duì)員Y為隊(duì)伍增加貢獻(xiàn)值1,在程序中算出score為11.yyy隊(duì)伍A中的隊(duì)員X調(diào)用redis的zadd命令設(shè)置隊(duì)伍的貢獻(xiàn)值為11.xxx隊(duì)伍A中的隊(duì)員Y調(diào)用redis的zadd命令設(shè)置隊(duì)伍的貢獻(xiàn)值為11.yyy最后算出隊(duì)伍A的貢獻(xiàn)值為11,無(wú)法保證增加貢獻(xiàn)值這一個(gè)操作的原子性。
此時(shí)需要借助lua腳本來(lái)保證計(jì)算和設(shè)置貢獻(xiàn)值這兩個(gè)操作的原子性:
// 其中KEYS[1]為排行榜key,KEYS[2]為隊(duì)伍ID // 其中ARGV[1]為增加的貢獻(xiàn)值,ARGV[2]為Integer.MAX-時(shí)間戳 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中無(wú)法使用時(shí)間函數(shù),所以(Integer.MAX-時(shí)間戳) * 10^-13
部分由腳本外程序計(jì)算好傳入。
分頁(yè)查詢排行榜,查詢隊(duì)伍的排名等功能都可以繼續(xù)使用上面的命令。
所謂并列排行榜,就是存在相同排名情況的排行榜。
我們期望的結(jié)果如下表:
隊(duì)伍ID | 貢獻(xiàn)值 | 排名 |
---|---|---|
a | 100 | 1 |
b | 99 | 2 |
c | 99 | 2 |
d | 88 | 4 |
e | 87 | 5 |
當(dāng)然現(xiàn)實(shí)中也有排名不跳過(guò)的情況,我這里考慮的是排名跳過(guò)的情況。
redis中score的設(shè)計(jì)還是采用上面的分?jǐn)?shù)=貢獻(xiàn)值 + (Integer.MAX-時(shí)間戳) * 10^-13
,只是在查詢排名時(shí)需要進(jìn)行計(jì)算。
比如要查上表中隊(duì)伍b的排名,思路如下:
首先查到隊(duì)伍b的score
再查到跟隊(duì)伍b的score的整數(shù)部分相同(也就是貢獻(xiàn)值一樣),排在第一個(gè)的隊(duì)伍的value(隊(duì)伍ID)
根據(jù)上一步得到的隊(duì)伍ID查詢此隊(duì)伍的排名就是隊(duì)伍b的排名
使用命令實(shí)現(xiàn)上面的步驟如下:
> zscore 排行榜key teamId > zrevrangebyscore(排行榜key, 上一步得到的score+1, 上一步得到的score, limit, 0 , 1) > zrevrank(排行榜key, 上一步得到的teamId)
為了性能考慮,可以使用下面的腳本一次查出來(lái):
// KEYS[1]表示排行榜key // KEYS[2]表示要查詢的隊(duì)伍的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}
下面附上分頁(yè)查詢排行榜的腳本,假如一頁(yè)10條,不用下面的腳本需要查詢10次上面的腳本,如果連上面的腳本都沒(méi)有使用的話就要查詢30次redis。
// 排行榜key // ARGV[1]分頁(yè)起始偏移 // ARGV[2]分頁(yè)結(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庫(kù),返回的是一個(gè)json。
到此,關(guān)于“Redis怎么實(shí)現(xiàn)排行榜及相同積分按時(shí)間排序功能”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)?lái)更多實(shí)用的文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。