有了 Message Queue 之後, 我們已經將服務之間解綁, 現在只剩下 Database 還是單一節點, 所以今天就來 Scaling Database 吧!
由於 Database 是本身就帶有狀態, 所以處理起來會非常麻煩, 等之後再說明
先來看看幾種常見的 Scaling 策略
即將 Database 複製到其他節點上, 以提高可用性
但是由於 Database 帶有狀態, 需要一個方法來同步不同節點 Database 的資料
就像前面所介紹, 我們可以根據 CAP Theorem 將系統分類
強調資料 一致性 勝過 可用性, 所以在所有節點同步完成前, 我們會擋住所有使用者的操作, 造成可用性下降
強調 可用性 勝過 一致性, 可以允許一段時間內各節點間資料不一致的情形, 讓使用者能夠持續使用服務
一種模式是 Master-Slave 架構, 通常是一個 Master 節點搭配多個 Slave 節點
只有 Master 節點允許 寫入 的操作, Slave 節點只允許讀取, Master 節點定期將資料同步到 Slave 節點
優點是針對 "讀取" 的可用性提高了
代價是 "寫入" 仍然會有 SPOF 和效能瓶頸的問題
所以適合 Read-heavy 的情境
由於 Master 節點仍然有效能瓶頸, 所以可以進一步透過 Sharding 優化
即是將資料根據 sharding key 切開分散儲存在不同的節點, 比如我們的訂單系統中 Booking 的 schema 設計如下
timestamp 對照表
room_type
booking
id | id_user | id_room_type | check_in_date | check_out_date | ctime | mtime |
---|---|---|---|---|---|---|
1 | 1 | 1 | 1724601600000 | 1724688000000 | 1722441600000 | 1722441600000 |
2 | 1 | 2 | 1724601600000 | 1724688000000 | 1722441600000 | 1722441600000 |
3 | 2 | 1 | 1724601600000 | 1724688000000 | 1722441600000 | 1722441600000 |
4 | 3 | 2 | 1724601600000 | 1724688000000 | 1722441600000 | 1722441600000 |
(題外話: 實務上要記得處理時區的問題)
這邊偷懶一下都用相同的訂房時間
可以看到我們有 3 個使用者, 使用者 1 下了兩筆訂單, 使用者 2, 3 分別下了一筆訂單
假設我們的 Database 只能夠負擔 2 個並行請求, 假設這時候有 4 個併行請求, 此時就會有效能問題了 (先不管使用者 1 是怎麼做到同時下兩單的...瀏覽器多開之類的)
如果平均的併行請求已經超過我們的系統處理上限, 且沒有 Hotspot 的問題 (某些使用者特別常使用), 就可以考慮以使用者作為 sharding key, 將使用者的訂單資料分散到不同節點儲存, 比如我們將 id_user 對 2 取餘數, 0 存到節點 A, 1 存到節點 B
節點 A
id | id_user | id_room_type | check_in_date | check_out_date | ctime | mtime |
---|---|---|---|---|---|---|
2 | 1 | 2 | 1724601600000 | 1724688000000 | 1722441600000 | 1722441600000 |
4 | 3 | 2 | 1724601600000 | 1724688000000 | 1722441600000 | 1722441600000 |
節點 B
id | id_user | id_room_type | check_in_date | check_out_date | ctime | mtime |
---|---|---|---|---|---|---|
1 | 1 | 1 | 1724601600000 | 1724688000000 | 1722441600000 | 1722441600000 |
3 | 2 | 1 | 1724601600000 | 1724688000000 | 1722441600000 | 1722441600000 |
好處是能夠分散 Master 節點的寫入請求, 加快寫入速度, 提高可用性
缺點是由於只有 booking 表 sharding, 若需要跨節點的 transaction 就會很麻煩, 需要使用如 2PC, Saga Pattern 等方法解決, 前面已經提過, 就不贅述
除了 Replica 以外, 另一個常用於提高可用性和一致性的方法是 Consensus Algorithm
共識演算法雖然是 去中心化架構, 但也算是分散式系統的一種
和 Replica 架構中將節點 讀寫 的職責分離不同, 共識演算法中所有的節點職責都是相同的, 沒有一個集中管理的節點負責分派任務或是同步節點, 因此沒有 SPOF 或是單一節點效能上限的問題
這邊以 Raft 為例, 簡單說明運作方式
參與 Raft 的所有節點稱作 群 (Cluster), 在每次 選舉 (Election) 中, 會選出一個 代表 (Leader) 節點, 其他節點為 跟隨者 (Follower)
當 代表節點 下線
好處是確保資料的 強一致性 (透過選舉機制)
代價是犧牲了 可用性 (選舉期間無法寫入)
並且系統會變得很複雜 (跨節點一致性都很複雜...)
也可能受到 51% 攻擊 (內網環境下, 要入侵並控制我們系統節點的機會不高)
實作 Raft 的 RDBMS 有: YugaByteDB, CockroachDB, TiDB 等等
(題外話: 支援 Raft 的 RDBMS 有點難找囧 大部分都是以 Replication, Sharding 為主)
Replica 和 共識演算法 的選擇視對於 一致性 的要求 (不考慮實作和維護的複雜度)
如果要求 強一致性 則建議用 共識演算法; 選擇 最終一致性 則建議用 Replica