日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

java千万用户实现实时排名_想知道谁是你的最佳用户?基于Redis实现排行榜周期榜与最近N期榜...

發(fā)布時間:2023/12/10 数据库 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java千万用户实现实时排名_想知道谁是你的最佳用户?基于Redis实现排行榜周期榜与最近N期榜... 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

本文由云+社區(qū)發(fā)表

前言

業(yè)務(wù)已基于Redis實現(xiàn)了一個高可用的排行榜服務(wù),長期以來相安無事。有一天,產(chǎn)品說:我要一個按周排名的排行榜,以反映本周內(nèi)用戶的活躍情況。于是周榜(按周重置更新的榜單)誕生了。為了滿足產(chǎn)品多變的需求,我們一并實現(xiàn)了小時榜、日榜、周榜、月榜幾種周期榜。本以為可長治久安了,又有一天,產(chǎn)品體驗業(yè)務(wù)后說:我想要一個最近7天榜,反映最近一段時間的用戶活躍情況,不想讓歷史的高分用戶長期占據(jù)榜首,可否?于是,滾動榜(最近N期榜)的需求誕生了。

周期榜

周期榜實現(xiàn)還是很容易的,給每個周期算出一個序號,作為榜單名后綴,進入新的周期自然切換讀寫新榜單,平滑過度。以日榜為例,根據(jù)時間戳ts計算每日序號s=ts/86400,以日序號s作為后綴即可實現(xiàn)零點后自動讀寫新日榜。小時榜與此雷同,不再贅述。

對于周榜,可以選定某一個周一(或周日,看需求)的時間戳為基準,計算基準到當(dāng)前經(jīng)過的周數(shù)為周序號,以此作為榜單后綴。

對于月榜,稍有不同,因為月份天數(shù)不固定,所以不能按照上述方法計算。但我們可以根據(jù)時間戳取得年、月信息,以年月做標志(如201810)后綴,即可實現(xiàn)月榜。

滾動榜

方案探討

滾動榜需要考慮多個周期榜數(shù)據(jù)的聚合與自動迭代更新,實現(xiàn)起來就沒那么容易了。下面分析幾個方案。

方案1:每日一個滾動榜,當(dāng)日離線補齊數(shù)據(jù)

還以日榜為例,最近N天榜就是把前N-1天到當(dāng)天的每一個日榜榜單累加即可,比如最近7天榜,就是前6天到當(dāng)天的每一個日榜中相同元素數(shù)據(jù)累加。因此,最直觀的一個方案是:首先記錄每天的排行榜R,那么第i天的最近N天榜Si=∑N?1n=0Ri?n,其中,Ri?x表示第i天的前x天的日榜。實現(xiàn)上,可以每日生成一個滾動榜S和當(dāng)天日榜R,加分時同時寫入S和R,每日零點后跑工具將前N-1天數(shù)據(jù)累加寫入當(dāng)日滾動榜S。

這個方案的優(yōu)點是直觀,實現(xiàn)簡單。但缺點也很明顯,一是每日一個滾動榜,消耗內(nèi)存較多;二是數(shù)據(jù)更新不實時,需要等待離線作業(yè)完成累加后S中的數(shù)據(jù)才完全正確;三是時間復(fù)雜度高,7天榜還好,只需要讀過去6天數(shù)據(jù),如果是100天榜,該方案需要讀過去99天榜,顯然不可接受。

方案2:全局一個滾動榜,當(dāng)日離線補齊數(shù)據(jù)

基于方案1,如果業(yè)務(wù)無需查詢歷史的S,可以只使用全局一個S,無需每日創(chuàng)建一個Si。加分操作還是同時加當(dāng)日的Ri和全局唯一的S,但每日零點的離線作業(yè)改為從S中減去Ri?(N?1)的數(shù)據(jù)(即將最早一天的數(shù)據(jù)淘汰,從而實現(xiàn)S的計數(shù)滾動)。

此方案減少了內(nèi)存使用,同時離線任務(wù)每次只需讀取一個日榜做減法,時間復(fù)雜度為O(1);但仍需要離線作業(yè)完成才能保證數(shù)據(jù)正確性,還是無法做到平滑過渡。

方案3:每日一個滾動榜,實時更新

要做到每日零點后榜單實時生效,而不需要等待離線作業(yè)的完成,一種方案是預(yù)寫未來的榜單。不難得出,當(dāng)日分數(shù)會計入往后N-1天的滾動榜中。因此,可以寫當(dāng)天的滾動榜Si的同時,寫往后N-1天的榜單Si+1到Si+N?1。

該方案不僅能脫離離線作業(yè)做到實時更新,且可以省略每天的日榜。但缺點也不難看出,對于7天滾動榜,每次寫操作需要更新7個榜單,寫入量小時還勉強能接受,如果寫操作量大或者需要的是30天、60天滾動榜,此方案可行性幾乎為零。

方案4:實時更新,常數(shù)次寫操作

有不有辦法做到既能實時更新,寫榜數(shù)量也不隨N的增加而增加呢?不難看出,第i天滾動榜Si=∑N?1n=0Ri?n,而第i+1天的滾動榜Si+1=∑N?1n=0R(i+1)?n=∑N?2n=0Ri?n+Ri+1。顯然,Si+1=Si?Ri?(N?1)+Ri+1。由于Ri+1在剛達到零點時必然為空且可以在次日實時加到Si+1上,因此如果我們能提前準備好Si?Ri?(N?1)這部分數(shù)據(jù),那么在零點進入i+1天后,Ri+1自然就是可用狀態(tài)了。

以3天滾動榜為例,次日滾動榜初始態(tài)為當(dāng)日滾動榜減去n-2天的日榜數(shù)據(jù)。

+-------------------------------------------+

| |

+----+---+ +--------+ +--------+ |

| | | | | | |

| R(i-2) | | R(i-1) | | R(i) | |

| | | | | | |

+----+---+ +----+---+ +---+----+ |

| | | |

| | | |

| | | |

| | v+ v-

| |

| | + +--------+ +--------+

| +-----> | | + | |

| + | S(i) | +---+> | S(i+1) |

+-----------------+> | | | |

+--------+ +--------+

那么,如何提前準備好Si?Ri?(N?1)這部分數(shù)據(jù)呢?可以如下處理:

對一個元素加分時,加當(dāng)日周期榜Ri、滾動榜Si;還需根據(jù)其在今日滾動榜中的分數(shù)s、及n-1天日榜中的分數(shù)r,計算出其在明日滾動榜中的初始分數(shù)s-r寫入明日滾動榜中;即3個寫操作;

如果一個元素在當(dāng)日沒有任何加分操作,那么不會觸發(fā)寫入初始分數(shù)操作,所以還需要一個離線工具補齊。與方案1、2不同的是,該離線工具可提前一天運行,即當(dāng)日運行離線工具補齊次日的滾動榜數(shù)據(jù)即可。

簡而言之:第一步是運行離線工具生成次日的滾動榜;第二步是在寫操作時同時更新次日的滾動榜。

該方案也是每日一個滾動榜。相對方案3而言,是空間換時間。如果空間不足且無保留歷史的需求,可在離線工具中清理歷史數(shù)據(jù)。

+--------------+

| |

| AddScore |

| |

+-+----+-----+-+

| | |

v | |

+--------+ +--------+ +-------++ | |

| | | | | | | |

| R(i-2) | | R(i-1) | | R(i) | | |

| | | | | | | |

+--------+ +--------+ +--------+ | |

| v

+--------+ | ++-------+

| | | | |

| S(i) +

| | | |

+--------+ +----+---+

^

|

|

+------+-----+

| |

| Tool |

| |

+------------+

方案4的實現(xiàn)

以下是實現(xiàn)參考。此處僅列出核心的lua腳本。Redis命令調(diào)用腳本的參數(shù)定義為:

eval script 4 當(dāng)日日榜key 當(dāng)日滾動榜key 即將淘汰的日榜key 明日滾動榜key 榜單元素名 加分數(shù)

lua腳本script如下:

--加今日日榜分數(shù)

redis.call('ZINCRBY', KEYS[1], ARGV[2], ARGV[1])

--加今日滾動榜分數(shù)

local rs = redis.call('ZINCRBY', KEYS[2], ARGV[2], ARGV[1])

local curRoundScore = 0

if (rs) then

curRoundScore = tonumber(rs)

end

--取即將淘汰的日榜分數(shù)

rs = redis.call('ZSCORE', KEYS[3], ARGV[1])

local oldCycleScore = 0

if (rs) then

oldCycleScore = tonumber(rs)

end

--計算次日滾動榜初始分數(shù)

local nextRoundScore = curRoundScore - oldCycleScore

if nextRoundScore < 0 then

nextRoundScore = 0

end

--設(shè)置次日滾動榜分數(shù)

redis.call('ZADD', KEYS[4], nextRoundScore, ARGV[1])

--返回今日分數(shù)

rs = redis.call('ZREVRANK', KEYS[2], ARGV[1])

return {curRoundScore, rs}

關(guān)于榜單key計算準確度的探討 我們的業(yè)務(wù)是在排行榜接入層邏輯中計算榜單后綴的,這種方案對邏輯層多臺機器的時間一致性要求較高,如果邏輯層服務(wù)器時鐘不一致,可能在時間切換點上出現(xiàn)不同機器讀寫不同榜單的問題。如果業(yè)務(wù)對時間精確度要求嚴格,可以考慮通過lua腳步在redis端計算后綴。

.

關(guān)于內(nèi)存容量限制的探討 基于ZSet實現(xiàn)的排行榜,每個元素約需要100字節(jié)內(nèi)存。如果榜單長度為1000萬,則每個榜單約需要1G內(nèi)存。滾動榜的計算需要每日保留一個日榜,如果滾動周期較長,則可能單機內(nèi)存容量不足以容納所有需要的榜單。 考慮到歷史日榜數(shù)據(jù)是不會變更的,因此不在lua腳本中讀取歷史日榜數(shù)據(jù)也無一致性問題。故可以將榜單打散到多個Redis實例,在接入層做邏輯讀取歷史日榜的分數(shù),再以參數(shù)形式傳入給lua腳本處理。

總結(jié)

在榜單長度不大且并發(fā)量不高的場景下,使用關(guān)系數(shù)據(jù)庫+Cache的方案實現(xiàn)排行榜有更高的靈活性。而在海量數(shù)據(jù)與高并發(fā)的場景下,Redis是一個更好的選擇。本文基于Redis實現(xiàn)的滾動榜,不論滾動周期多長,都只需要常數(shù)(3)次數(shù)的寫操作,有較好的性能和可擴展性。且通過離線+在線的雙預(yù)生成機制,確保了榜單實時生效,可用性較強。

此文已由作者授權(quán)騰訊云+社區(qū)發(fā)布

總結(jié)

以上是生活随笔為你收集整理的java千万用户实现实时排名_想知道谁是你的最佳用户?基于Redis实现排行榜周期榜与最近N期榜...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。