系統設計基礎 — 資料庫分片與複寫 (Database Sharding & Replication)

2023年2月4日

💎 加入 E+ 成長計畫 與超過 300+ 位軟體工程師一同在社群中成長,並且獲得更多的軟體工程學習資源

在負載均衡器 (load balancer) 單元有提到,當網站的流量變大時,我們可以透過負載均衡器,把請求分派給多個不同的伺服器。不過,當我們怎麼做,卻只有一個資料庫時,多個伺服器會同時會同時向資料庫做操作。而資料庫本身也是硬體,硬體也有其上限,能同時處理的請求並非無限,或是能存的資料也非無限。因此,當流量大起來時,很可能單一資料庫會不堪負荷。這時候我們會需要進一步思考如何擴增我們的資料庫。

擴增資料庫有哪些方法?

如果要擴增資料庫,我們可以嘗試以下幾種可能性:

垂直擴展

白話來說就是提升硬體性質,假如本來用 8GB 的 RAM,不夠的話就升級成 16GB,再不夠的話就提升到 32GB,以此類推。同理,儲存空間不夠,那就換一個更大的存儲空間。這種方式在處理不是真的那麼大的流量,或許還堪用。但如果真的是巨量的流量,因為硬體能升級的程度有其上限,很可能再強大的硬體也不夠用。因此實務上,如果是真的很大量的流量,通常不會選這方案。

資料庫分割 (database partitioning)

如果一個資料庫裡的東西太多,導致要撈資料出來時間太長,我們可以把資料庫分割成多個小的表 (或者資料庫實例),這樣一來在比較小的表 (或資料庫)中,要撈出資料的速度也能加快。同時因為有了多個資料庫實例,能夠容納的整體資料量也因此提升。而資料庫分割中,其中水平的分割,如果是把資料庫分割到多個資料庫實例,又會被叫資料庫切片 (database sharding),而這也是此篇要談論的重點之一。

什麼是資料庫切片?為什麼要用它?

Database Sharding
Database Sharding
圖片來源:https://www.digitalocean.com/community/tutorials/understanding-database-sharding

資料庫切片 (database sharding) 就是把資料庫切成多個,並創建多個資料庫實例,把資料分散地放在不同的資料庫中。從上面這張來自 Digital Ocean 的圖可以看到,本來一個有 A、B、C、D 四筆資料,我們切成 A 與 B 放在一個 shard,而 C 與 D 放在另一個 shard。這邊的每個 shard 都會是一個資料庫實例。

從上面這簡要的例子,我們可以推出資料庫切片的幾個好處

  • 如果單一資料庫能同時執行的操作有限,例如某資料庫一秒鐘能處理 1000 個寫的請求,當我們切片成多個實例,那就能突破這個限制。
  • 如果切片分的好,能進一步提升性能。舉例來說,某個航班查詢的應用程式,若把國際航班放一個 shard、國內航班放一個 shard,當今天要找國際航班時,不用從一個大的資料庫撈,而是僅需要從國際航班的 shard 撈,因為單一個 shard 的資料量比較小,這樣撈也會比較快。
  • 更高的可用性 (availability) 避免單一實例的風險。假如今天只有一個資料庫,那麼資料庫掛了就什麼資料都沒辦法寫、沒辦法撈。但如果切片成多個,假如國際航班的資料庫掛了,起碼國內航班的還可以用,不會所有的都不能用。

資料庫切片的方法

將資料庫切成數個小的資料庫,shard key (中文會翻成片鍵)。shard key 的分法常見的有幾種,一種是按照順序,例如 0 - 10 歲的族群放一個 shard,11 - 20 歲的使用者的放一個 shard,以此累推。業務需求也是常見的分法,例如國際航班的放一個 shard,國內航班放一個 shard。或是用雜湊 (hashing) 的方式來分,好的雜湊演算法應該能均勻分布,同時雜湊是 O(1) 操作,當有了 shard key,要找到對應的 shard 也會很快。

分片完,但如何解決單點故障問題?

當我們把資料庫分片成多個小的資料庫,這時仍會遇到單點故障 (Single Point of Failure) 的問題,換句話說,如果某個資料庫掛機 (例如存有國際航班的資料庫掛了),則該資料庫的資料就沒辦法被伺服器請求。這種問題該如何解決呢?這時我們可以透過把資料庫複製多份,放到多台不同機器來避免這問題。

Single-primary Replication

  • 有一個是主要資料庫 (primary database),只有該資料庫可以寫,其他資料庫則只可以讀。這個做法會遇到僅能最終一致性 (eventual consistency) 的問題,因為資料庫同步需要時間,很可能某個寫入的請求已經發生了,但因為還沒同步該資料到讀的資料庫,所以與此同時的讀取請求發生時,可能不是讀到最新的資料,而是要等到同步後才會是最新的。在某些應用程式這沒問題,例如 Facebook 別人的留言,你過幾秒後才看到也不會怎麼樣;但在一些需要高度精準的應用程式 (例如金融類的) 則不太適合。
  • 除此之外,這種方法僅適合大量讀、少量寫的情境,因為讀可以被多個資料庫分散負擔,不過如果應用程式是需要支援同時多個寫的,例如直播超級盃時的聊天室,可能同時上萬人留言,這時大量同時的寫入請求,都是由該主要資料庫來承擔,這也會是有風險的。除此之外,如果主要資料庫掛機,那也會變得沒有資料庫可以寫,因為剩下的資料庫都只可以讀。這種情況該如何解決呢?

multi-primary replication

  • 如果說只有一台主要資料庫可以寫,同樣會有單點故障的可能,那或許我們可以讓能夠寫的資料庫變成多台。這個概念又稱作 multi-primary replication。
  • 然而這種方法會遇到,如果有同時多個寫入,如何同步資料庫的問題。以及跨資料庫遇到 race condition 該如何處理的問題。
🧵 如果你想收到最即時的內容更新,可以在 FacebookInstagram 上追蹤我們