設計一個可以追蹤讚數的系統

Chunting Wu
Jul 6, 2021

--

問題描述

設計一個可以追蹤在文章/動態/照片的讚數系統,且能夠擴容

如何達成

這是一個有趣的問題,一開始看起來很簡單,但是實際上需要小心思考才能夠做對。我會在下面解釋我的想法,然後展示如何在需求改變時演進架構。

最簡單的設計我們可以看到像這樣:

Fig 1. 使用單一資料表

下面是這個簡單架構的優缺點:

  • 保證資料一致性。任何在寫入之後的讀取都保證能夠拿到最新的讚數,使用者不會看到鎖死的數值。
  • 這無法很好的擴容。當數以百萬計的同時寫入會拖慢資料庫的效能。雖然我們可以用post_id來分shard,但是這無法解決大量的同時寫入。
  • 這在即時計算數量上有著高昂成本(注意,Innodb沒有維護一個內部計數,相反的,當下了SELECT COUNT(*) FROM LIKES WHERE...時幾乎總是重掃整張資料表)。我們也許可以透過別的組件,例如:快取或別張資料表來儲存計數,但即便我們這麼做,還是有一個重要的問題需要克服:如何確保實際的讚和額外儲存的讚數是一致的。

為了解決剛提到的兩個缺點,我們必須稍微犧牲一點資料一致性:在我們按下讚之後,後端立即回覆而不需要等這筆紀錄寫進資料庫。我們也會在下面介紹一些額外的組件:

Fig 2. MQ, Batch updater, Cache
  • MQ:一個訊息佇列用來充當非同步更新的緩衝,此外也能可靠地將(post_id, user_id)廣播給多個訊息消費者。Apache Kafka是在業界很受歡迎的選擇。
  • Updater:這是一個訊息消費者,負責將資料插入點讚資料表。
  • Batch updater:透過降低資料一致性,我們可以將一樣的post_id做批次處理來降低資料庫寫入的負載。麻煩的是,必須要要引入額外的基建來確保批次更新是可靠的,例如一些串流處理集群,如:Apache FlinkApache Storm
  • Cache:負責儲存最新的讚數。

這第二個設計的優缺點如下:

  • 透過使用MQ做為緩衝,可以降低資料庫的寫入負載。
  • 透過使用batch updater,可以進一步降低熱門文章的寫入負載。
  • 點讚表和讚數表可能存在資料不一致。即使沒有任何組件失效也有可能發生,畢竟大部分的MQ無法保證訊息只被處理一次;我們可能會看到比實際更多的讚數。

為了解決資料不一致,我們可以採取的方案是週期性的從點讚表重算讚數。因為點讚表總是有最原始的資料,重算的結果會更可靠。為了從文章得到即時的讚數,重算的結果必須和線上的結果合併,如下圖展示的:

Fig 3. 線上+線下

如上圖所示,我們每天定時在12:00 AM從點讚表計算讚數。另一方面,在Real time events資料表中應該保留從12:00 AM到當下的讚數。透過讀取兩張資料表,我們應該可以得到相當精準且即時的讚數。這個設計參考了Lambda Architecture

後續回應

From Timofey Asyrkin

在這個情境下,我們真的在乎重複嗎?這很有趣,來看我們有甚麼選項:

  1. 當使用者按下讚,我們真的要立刻更新前端而不知會後端嗎(因為前後端非同步)?如果這樣做,當立刻刷新頁面會發生甚麼事?很大的機會我們會沒看到我們自己的讚,然後重新讚一次,這樣就會導致重複。
  2. 我們不需要前端馬上更新讚數,當資料送達到後端,後端發送通知給前端。這樣做的問題是,如果使用者沒有馬上看到更新,在沒有驗證機制下他們很有可能重新讚一次,這個新的要求還是會送進系統。
  3. 我們同步更新快取後將要求送進MQ來更新主要的儲存結構。如果使用者重新刷新頁面,我們從主要儲存和快取(如果有存在的話)拿資料。這麼做的問題在於有可能在主要儲存上會有錯誤。

譯者話:關於第三點我覺得應該是正解,但為什麼會說有可能發生問題呢?因為無論有沒有快取,都有可能在主要儲存上發生問題。我猜應該是指當主要儲存失效時,會有資料不一致吧,但如果在MQ前還卡一關快取,應該在做資料校對的時候會有更多可以參考的資訊才對。

--

--

Chunting Wu
Chunting Wu

Written by Chunting Wu

Architect at SHOPLINE. Experienced in system design, backend development, and data engineering.

No responses yet