程序員經(jīng)典面試題:如何實現(xiàn)一個海量計數(shù)系統(tǒng)
我們在開發(fā)中,經(jīng)常有一些計數(shù)相關的工作。例如,抖音上的短視頻,有評論數(shù),播放數(shù)。發(fā)送微博的時候,也有轉發(fā)數(shù),評論數(shù),點贊數(shù),電商網(wǎng)站上,有著商品售賣金額,商品的出售件數(shù)等統(tǒng)計。那么,這些統(tǒng)計是怎么做到的呢?
不難想到,我們可以在用戶查看這條評論的時候,去統(tǒng)計總共有多少個贊。舉個例子,你在虎撲上發(fā)表一條評論,每當有人點贊的時候,都會在評論下面增加一條點贊記錄,記錄誰誰誰點贊了。當有人訪問這條評論的時候,我們可以使用Mysql的SelectCount語句,對其進行統(tǒng)計,查詢總共有多少條記錄,并且返回給前段。這種方案有什么問題呢?當數(shù)據(jù)量小的時候,我們總可以非常迅速地計算到結果。但是隨著數(shù)據(jù)量的增大,例如一條非?;鸬脑u論,可能已經(jīng)有數(shù)十萬個贊了,每次都去查詢效率都比較低。評論的內(nèi)容是核心數(shù)據(jù),評論的點贊數(shù)是次要數(shù)據(jù),如果查詢點贊數(shù)量的耗時過長,反而會影響主要業(yè)務。我們要做的,是盡量地少去查詢,或者讓查詢的速度更快。
在此之前的方案,我們每次都是全量Count一遍,那么,我們能不能把這個數(shù)據(jù)存起來呢?這樣子,就不需要每次有人來查詢的時候,都去統(tǒng)計。每次有變更的時候,就去數(shù)據(jù)庫里面進行selectCount,查詢后將結果保存下來。之后每次有人訪問這條評論,我們就直接返回,而不需要進行SelectCount。當然,每次都去SelectCount也是一個笨方法,我們?yōu)槭裁床幻看斡腥它c贊就+1,有人取消點贊就減一呢。我們將點贊數(shù)據(jù)的電話分為兩種事件,一種是+1,另一種是-1。當然,這種做法的潛在風險點就是并發(fā)問題,例如原來的數(shù)據(jù)是5,突然來了兩個+1的請求,由于他們并發(fā)執(zhí)行了,最終的結果變成6。這種情況下我們需要加鎖防止并發(fā),或者讓數(shù)據(jù)庫幫我們實現(xiàn)。
僅是這樣是遠遠不夠的,我們?nèi)绾蚊鎸ν话l(fā)的流量呢?例如突然爆發(fā)了一個熱點事件,突然火了一條評論,可能10分鐘之內(nèi)就已經(jīng)幾萬個點贊了。假如我們每次都去數(shù)據(jù)庫更新,那勢必效率會大大降低。為了應對這種突發(fā)性的流量,業(yè)內(nèi)的解決方案大家其實都非常熟悉,不就采用消息隊列進行削峰嘛。但是,這種突發(fā)性的流量容易造成MQ堆積,那么有沒有什么好的方法呢?其實非常簡單,本來我們要進行10次+1的操作,為什么我們不合并成1次+10操作呢。這樣就能大大減少數(shù)據(jù)庫的壓力,我們一般有兩場常見的實現(xiàn)方式,一種是對異步隊列進行任務合并,另外一種是我們可以將計數(shù)存在緩存上,定期將數(shù)據(jù)刷到數(shù)據(jù)庫中。
我們更多采用的是第二種方法,并且我們還會進一步優(yōu)化,就是我們會將點贊的統(tǒng)計存放在Redis這種的緩存中間件上,然后再定期刷到磁盤上。對于這樣的統(tǒng)計數(shù)據(jù),冷熱是非常明顯的,基本上,采用采用這種方案,已經(jīng)可以適用大部分計數(shù)系統(tǒng)了。歡迎大家關注我,共同學習,共同進步。大家的支持是我繼續(xù)嘮嗑的動力。