如何使用 Redis 实现大规模的帖子浏览计数
本文翻譯自全球訪問量排名第8位的論壇Reddit博客上的文章,講的是關于Reddit如何在海量瀏覽量下實時統(tǒng)計瀏覽量的。
本文我們就來聊一聊,Reddit 是如何在大規(guī)模下統(tǒng)計帖子瀏覽量的。
統(tǒng)計方法
我們對統(tǒng)計瀏覽量有四個基本的要求
-
計數必須達到實時或者接近實時。
-
每個用戶在一個時間窗口內僅被記錄一次。
-
帖子顯示的統(tǒng)計數量的誤差不能超過百分之幾。
-
整個系統(tǒng)必須能在生成環(huán)境下,數秒內完成閱讀計數的處理。
滿足上面四個條件,其實比想象中要復雜。為了在實時統(tǒng)計的情況下保持精準度,我們需要知道某一個用戶之前是否瀏覽過一篇文章,所以我們需要為每一篇文章存儲瀏覽過它的用戶的集合,并且在每次新增瀏覽時檢查該集合進行去重復操作。
一個比較簡單的解決方案是,為每篇文章維護一個哈希表,用文章ID作為key,去重的userid的集合(set數據結構)作為value。
這種方案在文章數量和閱讀數比較小的情況下,還能很好的運行,但當數據量到達大規(guī)模時,它就不適用了。尤其是該文章變成了熱門文章,閱讀數迅速增長,有些受歡迎的文章的閱讀者數量超過百萬級別,想象一下維護一個超過百萬的unqine userId的集合在內存中的,還有經受住不斷的查詢,集合中的用戶是否存在。
自從我們決定不提供100%精準的數據后,我們開始考慮使用幾種不同的基數估計算法。我們綜合考慮下選出量兩個可以滿足需求的算法:
-
線性概率計算方法,它非常精確,但是需要的內存數量是根據用戶數線性增長的。
-
基于HyperLogLog?(HLL)的計算方法,HLL的內存增長是非線性的,但是統(tǒng)計的精準度和線性概率就不是同一級別的了。
為了更好的理解基于HLL的計算方法,究竟能夠節(jié)省多少內存,我們這里使用一個例子。考慮到r/pics文章,在本文開頭提及,該文章收到了超過一百萬用戶的瀏覽過,如果我們存儲一百萬個唯一的用戶ID,每一個id占用8個字節(jié),那么僅僅一篇文章就需要8mb的空間存儲!對照著HLL所需要的存儲空間就非常少了,在這個例子中使用HLL計算方法僅需要 12kb的空間也就是第一種方法的0.15%。
(This article on High Scalability?這篇文章講解了上面的兩種算法.)
有很多的HLL實現是基于上面兩種算法的結合而成的,也就是一開始統(tǒng)計數量少的情況下使用線性概率方法,當數量達到一定閾值時,切換為HLL方法。這種混合方法非常有用,不但能夠為小量數據集提供精準性,也能為大量數據節(jié)省存儲空間。該種實現方式的細節(jié)請參閱論文(Google’s HyperLogLog++ paper)
HLL算法的實現是相當標準的,這里有三種不同的實現方式,要注意的是,基于內存存儲方案的HLL,這里我們只考慮Java和Scale兩種實現
-
Twitter的Algebird庫,Scala實現,Algebird的文檔撰寫非常好,但是關于它是如何實現HLL的,不是很容易理解。
-
stream-lib庫中的HyperLogLog++實現,Java編寫。 stream-lib代碼的文檔化做的很好,但我們對如何適當調優(yōu)它,還是有些困惑的。
-
Redis的HLL實現(我們最終的選擇),我們覺得Redis的實現不管從文檔完善程度還是配置和提供的API接口,來說做的都非常好。另外的加分點是,使用Redis可以減少我們對CPU和內存性能的擔憂。
Reddit的數據管道,主要都是使用Apache Kafka的。每當一個用戶瀏覽一篇文章時,就會觸發(fā)一個事件并且被發(fā)送到事件收集服務器,然后批量的將這些事件發(fā)送打kafka中進行持久化。
Reddit的瀏覽統(tǒng)計系統(tǒng),分為兩個順序執(zhí)行的組成部分,其中的第一部分是,被稱為Nazar的kafka隊列『消費者』(consumer) ,它會從kafka中讀取事件,然后將這些事件通過特定的條件進行過濾,判斷改事件是否應該被算作一次文章閱讀計數,它被稱為『NAZAR』是因為在系統(tǒng)中它有作為『眼鏡』的用處,識別出哪些事件是不應該被加入到統(tǒng)計中的。Nazar使用Redis?維護狀態(tài)還有一個事件不被計數的潛在原因,這個原因可能是用戶短時間內重復瀏覽統(tǒng)一文章。Nazar會在事件被發(fā)送回kafka時,為事件添加一個標識位,根據該事件是否被加入到計數當中的布爾值。
統(tǒng)計系統(tǒng)的第二部是一個稱為Abacus?的kafka『消費者』它會真正的統(tǒng)計瀏覽量,并且讓瀏覽量數據可以在整站和客戶端上顯示, 它接收從Nazar發(fā)送出來的事件消息,然后根據該消息中包含著標識值(Nazar中處理的)來判斷這個事件是否算做一次計數,如果事件被計數,Abacus會首先檢查這個事件中文章的HLL計數是否存在于Redis中,如果存在,Abacus會發(fā)送一個PFADD請求給Redis,如果不存在,Abacus會發(fā)生一個請求到Cassandra集群,Cassandra集群會持久化HLL 計數和真實的原始計數數據,然后再發(fā)送一個SET請求到Redis,這個過程通常出現在用戶閱讀一個已經被Redis剔除的就文章的情況下發(fā)送。
為了讓維護一個在Redis可能被剔除的舊文章,Abacus會定期的,從Redis中將HLL過濾數據,包括每篇文章的計數,全部寫入到Cassandra集群中,當然為了避免集群過載,這個步驟會分為每篇文章10秒一組批次進行寫入。下圖就是整個過程的流程圖。
總結
以上是生活随笔為你收集整理的如何使用 Redis 实现大规模的帖子浏览计数的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 跟着 Github 学习 Restful
- 下一篇: 面试必考-从URL输入到页面展现到底发生