<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
在日常的開發中,經常會碰到需要對使用者的分值等進行排序,比如在遊戲裡面需要對戰鬥力進行排行,在組隊活動中需要對各個隊伍的貢獻值進行排行,在微信中需要對各個好友的步數進行排行,此時一般會選擇redis的有序集合對使用者的分數進行儲存,從而實現排行榜的需求,但是不同的場景排行榜的方式也略有不同,以下根據自己日常的開發進行了一下歸納總結。
需求:對組隊活動中各個隊伍的貢獻值進行排行。
Redis的Sorted Set是String型別的有序集合。集合成員是唯一的,這就意味著集合中不能出現重複的資料。
每個元素都會關聯一個double型別的分數。redis正是通過分數來為集合中的成員進行從小到大的排序。
有序集合的成員是唯一的,但分數(score)卻可以重複。
下面先不考慮積分相同的情況,實現排行榜:
// 準備資料,其中value為每個隊伍的ID,score為隊伍的貢獻值 > zadd z1 5 a 6 b 1 c 2 d 10 e (integer) 5 // 分頁查詢排行榜所有的隊伍和貢獻值,要使用zrevrange,而不是zrange,貢獻值越大越排在前面 > zrevrange z1 0 2 withscores 1) "e" 2) "10" 3) "b" 4) "6" 5) "a" 6) "5" // 增加某個隊伍的貢獻值 > zincrby z1 3 d "5" > zincrby z1 4 c "5" // 查詢排行榜所有的隊伍 > 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預設實現是相同分數的成員按字典順序排序(09,AZ,a~z),上面使用的是zrevrange,所以是倒序,所以相同分數排序就不能根據時間優先來排序。
在上面的實現中,如果兩個隊伍的貢獻值相同,也就是積分值相同,無法根據時間的先後進行排行。
所以需要設計一個分數 = 貢獻值 + 時間戳 ,誰分數大誰排前面,最後還要能根據分數能解析出來貢獻值。
使用整型儲存分數值,redis中score本身是一個double型別,能精確儲存的最大整型數位為2^53=9007199254740992(16位元)。而精確到毫秒的時間戳需要13位,此時留給儲存貢獻值只有3位數了,當前如果時間只要精確到秒,只需要10位,這樣留給貢獻值就有6位。
整體設計:高3位表示貢獻值,低13位表示時間戳。
如果我們簡單地把score結構由:貢獻值 * 10^13 + 時間戳
拼湊,因為分數越大越靠前,而時間戳越小則越靠前,這樣兩部分的判斷規則是相反的,無法簡單把兩者合成一起成為score。
但是我們可以逆向思維,可以用同一個足夠大的數Integer.MAX減去時間戳,時間戳越小,則得到的差值越大,這樣我們就可以把score的結構改為:貢獻值 * 10^13 + (Integer.MAX-時間戳)
,這樣就能滿足我們的需求了。
由於redis的score值是double型別,可以使用整數部分儲存貢獻值,小數部分儲存時間戳,同樣時間戳的部分使用一個最大值減去它。
這樣,整體設計變為:分數=貢獻值 + (Integer.MAX-時間戳) * 10^-13
弊端:由於分數值是由兩個變數來計算得出,所以在給隊伍增加貢獻值時,無法簡單的使用之前的zincrby來改變score的值了,這樣在並行情況下為隊伍增加貢獻值就會導致score值不準確。
錯誤情況模擬:
假設現在隊伍A的貢獻值為10隊伍A中的隊員X為隊伍增加貢獻值1,在程式中算出score為11.xxx隊伍A中的隊員Y為隊伍增加貢獻值1,在程式中算出score為11.yyy隊伍A中的隊員X呼叫redis的zadd命令設定隊伍的貢獻值為11.xxx隊伍A中的隊員Y呼叫redis的zadd命令設定隊伍的貢獻值為11.yyy最後算出隊伍A的貢獻值為11,無法保證增加貢獻值這一個操作的原子性。
此時需要藉助lua指令碼來保證計算和設定貢獻值這兩個操作的原子性:
// 其中KEYS[1]為排行榜key,KEYS[2]為隊伍ID // 其中ARGV[1]為增加的貢獻值,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中無法使用時間函數,所以(Integer.MAX-時間戳) * 10^-13
部分由指令碼外程式計算好傳入。
分頁查詢排行榜,查詢隊伍的排名等功能都可以繼續使用上面的命令。
所謂並列排行榜,就是存在相同排名情況的排行榜。
我們期望的結果如下表:
隊伍ID | 貢獻值 | 排名 |
---|---|---|
a | 100 | 1 |
b | 99 | 2 |
c | 99 | 2 |
d | 88 | 4 |
e | 87 | 5 |
當然現實中也有排名不跳過的情況,我這裡考慮的是排名跳過的情況。
redis中score的設計還是採用上面的分數=貢獻值 + (Integer.MAX-時間戳) * 10^-13
,只是在查詢排名時需要進行計算。
比如要查上表中隊伍b的排名,思路如下:
使用命令實現上面的步驟如下:
> 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]分頁結束偏移 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。
到此這篇關於Redis實現排行榜及相同積分按時間排序的文章就介紹到這了,更多相關Redis排行榜按時間排序內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45