“ Top K 系統(tǒng) ” 是非常常見的一種子系統(tǒng),基本上,就是從全量巨大的統(tǒng)計數(shù)據(jù)中,篩選出數(shù)值最大的 K 個來并按序展示。這樣的篩選可以是全時間內(nèi)的,也可以是最近某一段時間內(nèi)的;可以是全分類的,也可以是某個特定分類的。
具體來說,像 Twitter 的 Trending Topic,微博熱搜,視頻網(wǎng)站的點擊排行,下載排行(可以是日榜、月榜、總榜)等等。這樣的系統(tǒng),在統(tǒng)計數(shù)據(jù)非常大(heavy hitters)的時候,其中的挑戰(zhàn)性在于兩個:
無法簡單地在單臺機(jī)器的內(nèi)存中進(jìn)行目標(biāo) id -> count 計數(shù)的簡單映射,因為數(shù)據(jù)量太大,內(nèi)存放不下。
無法用實時的方式高效地顯示出動態(tài)變化的 Top K 列表來。
image.png
- 上圖包含了兩個思路,一個是實時排行(榜單),通過 Count-min Sketch 實現(xiàn),快速,但是不夠精確(或者使用 Lossy Counting);另一個是周期性排行,通過異步的 MR 數(shù)據(jù)處理實現(xiàn),數(shù)據(jù)上看比較準(zhǔn)確,但是處理是異步的,實時性差。
- 第一個思路方面,統(tǒng)計要盡可能實時。為了提高處理效率,用戶的話題引用可以直接進(jìn)入 filter 進(jìn)行處理。在我讀到的某些材料中,類似系統(tǒng)這一步也有通過異步批量的方式進(jìn)入隊列并處理的。不過在這里,我還是保留了比較簡單的一種實現(xiàn)。
- 接著經(jīng)過簡單的 filtering 和 parsing 去掉不關(guān)心的數(shù)據(jù),比如對于微博的話題來說,某一些詞是小詞,或者是我們不希望成為話題的詞;而某一些近似詞可以合并。完成以后數(shù)據(jù)有兩個去向,一個是右側(cè)的即時統(tǒng)計,一個是持久化到下方的數(shù)據(jù)庫中(這個數(shù)據(jù)庫可以是 Redis 這樣的 KV 數(shù)據(jù)庫)。
- 對于每一個詞,經(jīng)過 hash 以后,到 Count-min Sketch 表格中累積計數(shù),并根據(jù)計數(shù)到當(dāng)前大小為 K 的最小堆(這個最小堆用來存放一定時間內(nèi)累計的前 K 大條目)中尋找是否比堆頂更大,如果是,就入堆并移除原堆頂,從而保持堆的大小為 K。由于 Count-min Sketch 這個堆的大小都是確定并可控的,這樣的統(tǒng)計就可以在單個節(jié)點上完成了。
- 如果需要的即時統(tǒng)計數(shù)據(jù)不是 “總榜”,而是最近一段時間的 “趨勢榜”,那就可以借助 Ring Buffer——比如我們只關(guān)心最近一小時的趨勢,就可以把一小時劃分為 ring 上的 60 個區(qū)間,每個區(qū)間使用 Count-min Sketch 甚至簡單的 Map 分別統(tǒng)計,趨勢榜每次可聚合這 60 個區(qū)間得出 top K;每過一分鐘都覆寫最老的那一個區(qū)間的數(shù)據(jù),從而保證 ring 上的數(shù)據(jù)始終是最近一小時的。
- 第二個思路方面,統(tǒng)計不實時,但相對精確。對于這些持久化的數(shù)據(jù),由 MR 的 job 定期執(zhí)行來處理,并更新結(jié)果到數(shù)據(jù)庫中。
- 讀取數(shù)據(jù)的時候,根據(jù)需要可以讀取即時統(tǒng)計或者異步計算得到的統(tǒng)計數(shù)據(jù),數(shù)據(jù)可以在外部緩存。
轉(zhuǎn)自:https://www.raychase.net/6275