設計一個可以追蹤讚數的系統
問題描述
設計一個可以追蹤在文章/動態/照片的讚數系統,且能夠擴容
如何達成
這是一個有趣的問題,一開始看起來很簡單,但是實際上需要小心思考才能夠做對。我會在下面解釋我的想法,然後展示如何在需求改變時演進架構。
最簡單的設計我們可以看到像這樣:
下面是這個簡單架構的優缺點:
- 保證資料一致性。任何在寫入之後的讀取都保證能夠拿到最新的讚數,使用者不會看到鎖死的數值。
- 這無法很好的擴容。當數以百萬計的同時寫入會拖慢資料庫的效能。雖然我們可以用post_id來分shard,但是這無法解決大量的同時寫入。
- 這在即時計算數量上有著高昂成本(注意,Innodb沒有維護一個內部計數,相反的,當下了
SELECT COUNT(*) FROM LIKES WHERE...
時幾乎總是重掃整張資料表)。我們也許可以透過別的組件,例如:快取或別張資料表來儲存計數,但即便我們這麼做,還是有一個重要的問題需要克服:如何確保實際的讚和額外儲存的讚數是一致的。
為了解決剛提到的兩個缺點,我們必須稍微犧牲一點資料一致性:在我們按下讚之後,後端立即回覆而不需要等這筆紀錄寫進資料庫。我們也會在下面介紹一些額外的組件:
- MQ:一個訊息佇列用來充當非同步更新的緩衝,此外也能可靠地將(post_id, user_id)廣播給多個訊息消費者。Apache Kafka是在業界很受歡迎的選擇。
- Updater:這是一個訊息消費者,負責將資料插入點讚資料表。
- Batch updater:透過降低資料一致性,我們可以將一樣的post_id做批次處理來降低資料庫寫入的負載。麻煩的是,必須要要引入額外的基建來確保批次更新是可靠的,例如一些串流處理集群,如:Apache Flink和Apache Storm。
- Cache:負責儲存最新的讚數。
這第二個設計的優缺點如下:
- 透過使用MQ做為緩衝,可以降低資料庫的寫入負載。
- 透過使用batch updater,可以進一步降低熱門文章的寫入負載。
- 點讚表和讚數表可能存在資料不一致。即使沒有任何組件失效也有可能發生,畢竟大部分的MQ無法保證訊息只被處理一次;我們可能會看到比實際更多的讚數。
為了解決資料不一致,我們可以採取的方案是週期性的從點讚表重算讚數。因為點讚表總是有最原始的資料,重算的結果會更可靠。為了從文章得到即時的讚數,重算的結果必須和線上的結果合併,如下圖展示的:
如上圖所示,我們每天定時在12:00 AM從點讚表計算讚數。另一方面,在Real time events資料表中應該保留從12:00 AM到當下的讚數。透過讀取兩張資料表,我們應該可以得到相當精準且即時的讚數。這個設計參考了Lambda Architecture。
後續回應
From Timofey Asyrkin
在這個情境下,我們真的在乎重複嗎?這很有趣,來看我們有甚麼選項:
- 當使用者按下讚,我們真的要立刻更新前端而不知會後端嗎(因為前後端非同步)?如果這樣做,當立刻刷新頁面會發生甚麼事?很大的機會我們會沒看到我們自己的讚,然後重新讚一次,這樣就會導致重複。
- 我們不需要前端馬上更新讚數,當資料送達到後端,後端發送通知給前端。這樣做的問題是,如果使用者沒有馬上看到更新,在沒有驗證機制下他們很有可能重新讚一次,這個新的要求還是會送進系統。
- 我們同步更新快取後將要求送進MQ來更新主要的儲存結構。如果使用者重新刷新頁面,我們從主要儲存和快取(如果有存在的話)拿資料。這麼做的問題在於有可能在主要儲存上會有錯誤。
譯者話:關於第三點我覺得應該是正解,但為什麼會說有可能發生問題呢?因為無論有沒有快取,都有可能在主要儲存上發生問題。我猜應該是指當主要儲存失效時,會有資料不一致吧,但如果在MQ前還卡一關快取,應該在做資料校對的時候會有更多可以參考的資訊才對。