如何實現高效實時排行榜功能

排行榜是業務開發中常見的一個功能,如何設計一個好的資料結構能夠滿足實時的查詢,下面我們一個實際的案例來探討一下。

場景

現在有一個積分捐贈活動,使用者可以捐贈自己的積分。後臺需要提供如下介面:

介面1:返回捐贈積分TOP 15的使用者資訊以及捐贈的積分

介面2:返回活動總參與人數

介面3:對於每個使用者,返回自己捐贈的積分與排名

2。 設計

2。1 資料庫部分

如果每次查詢TOP 10都去流水錶聚攏資料的話,必須是非常耗時的。所以除了常規的表(例如使用者資產表,使用者資產流水錶)之外,我們還設計一張使用者資產開銷的表,用於根據使用者ID與活動ID進行資料聚攏。使用者資產開銷表中,使用者ID與活動ID可以建立唯一索引,保證每個活動中,每個使用者只有一條資料。這張表可以實時更新資料,也可以用定時任務去非同步更新資料。

2。2 redis部分

雖然我們上面加了使用者資產開銷表,但是在資料量的越來越大,我們需要實現排行榜需求是不可能實時查表的。此時redis的有序集合(sorted set)就非常適合做這件事情。

2。3 redis有序集合的相關命令

有序集合和集合一樣可以儲存字串,另外有序集合的成員可以關聯一個分數(score),這個分數用於集合排序。

2。3。1 單權重排序

下面我們以積分捐贈為例講一下涉及到的相關redis命令,其中donate_activity是有序集合的key。

如何實現高效實時排行榜功能

2。3。2 複合權重排序

上面的例子中,只是列舉了針對使用者已捐的單權重場景,實際專案中,還會涉及多權重問題,例如根據使用者已捐積分進行排名,但是同分數下先到達該分數的使用者需要排在前面。因為redis有序集合的score是double型別,我們可以對已捐積分和使用者開銷表中該使用者記錄的最後修改時間做一個複合的權重。使用者已捐積分作為整數位。以活動結束時間為界限,計算這個修改時間距離活動時間的時間戳作為小數位(這個時間需要做位數不足補0操作)。這樣就能實現複合權重的排序功能了。

3。 redis資料安全問題

在使用者積分捐贈成功後,我們可以同步使用zincrby命令對該使用者的排名權重進行疊加。但是我們不能完全相信redis裡面的資料,因為redis通常是作為快取層加速查詢的,有機率丟失資料。為了解決這個問題,我們可以用定時任務定時從根據活動ID與使用者ID聚攏資料的使用者資產開銷表中把資料更新到redis中。在同步過程中,如果使用zincrby來修正資料,可能涉及的中間計算會較多較繁瑣,建議可以直接使用zadd覆蓋redis資料(因為使用者資產開銷表的資料是最真實的)。

4。 總結

redis的有序集合在排序方面是一個非常高效的資料結構,可以替代資料庫裡一些很難實現的操作。它的一個典型場景就是排行榜,透過zrevrank可以快速得到使用者排名,透過zrevrange可以快速得到top n的使用者列表,它們的時間複雜度都是O(log(N))。