關於實時TopN排名演算法的思考
阿新 • • 發佈:2018-11-23
關於實時TopN排名演算法的思考
0.引言
實時排名是網路應用中常見的功能。根據需求不同,大概可以分為以下幾類:
- i. TopN排名
- ii. 全資料排名
作為通用需求,我們必須做如下假設:
- a. 使用者基數較大
- b. 排名資料更新較頻繁
- c. 用於排序的資料(score)範圍不確定
- d. 用作排名的score只會增加,不會減少
基於以上假設,全資料排名就是海量資料處理問題,一般認為記憶體難以勝任,一般通過資料庫(如redis)實現,本文暫不討論。
1.TopN實時排名演算法
這裡,我們假設:
- N的範圍有限(如1000),榜單資料記憶體完全可以處理
那麼,問題來了。如何設計資料結構和演算法,來滿足大批量使用者頻繁更新榜單情況下的實時排名需求?
1.1 一個失敗的方案
曾經看到一個實現,方案如下:
- a. 定義一個長度為N+1的陣列
- b. 更新資料時,將新資料放到陣列尾部
- c. 對陣列排序,如果陣列長度為N+1,則移除尾部元素
咋一看,由於榜單資料較小,每次更新排序好像可行,但是隨著使用者基數和更新頻度增加,
這個演算法無疑跟DB設計把query建立在沒有index的table上一樣可怕。
隨著更新頻度提升,這個sort操作無疑將成為CPU的噩夢。
低效率的演算法不會出bug,但是隱藏在角落偷偷的吃CPU,還很難被發現。
因此,該方案不可取。
1.2 現成的資料結構?
經典的有序資料結構,如
- 紅黑樹
- Heap
貌似可以滿足這種需求。
但是,我們知道,平衡二叉樹的高效操作是在於快速查詢,然而維護一棵樹的平衡(插入,刪除元素),卻是成本很高的操作。
因此,不可取。
1.3 合理的方案
這個功能裡有一個令人頭疼的要求,就是客戶端要實時能夠檢視到排名情況,那麼問題來了:
- 這裡的實時是否意味著服務端資料必須實時有序?
答案是未必。我們知道,排序演算法是計算機裡面一個比較耗時的操作,頻繁的排序更是無法忍受。
比較可行的方案是服務端不排序,只保證榜單記錄TopN的資料,把實時排序的任務交給客戶端執行。
- 伺服器如何保證榜單上只記錄TopN的資料?
資料結構定義如下:
type RankInfo struct{
name string // name of this rank element
score int // rank score
}
type RankList struct {
list []*RankInfo // unordered top N rank list
min *RankInfo // the last one who own the minimum score in list
nameMap map[string]*RankInfo // name->rankInfo
}
更新積分榜UdateRankList(name, score)的操作流程如下(假設score只會增加不會減少):
- a. 從nameMap查詢指定的name是否已在榜單上,如果存在,更新積分。如果該物件==min,則重新查詢積分最低物件賦值給min,返回。
- b. 如果list長度<N, 則直接將新資料插入list尾部,如果score<min.score,則將新物件賦值給min, 返回。
- c. 如果score>min.score, 則頂替min物件資料,更新min.name,min.score為name,score,重新查詢積分最低物件賦值給min, 返回。
- d. 所給score不能上榜。
至此,排行榜更新完成。不需要排序,不需要支援排序的資料結構,高效的完成榜單維護,perfect。