關於實時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。

Reference

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章