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