1. 程式人生 > >關於實時TopN排名演算法的思考

關於實時TopN排名演算法的思考

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