Designing-Data-Intensive-Applications Ch5 Note
ref: https://github.com/Vonng/ddia/blob/master/zh-tw/ch5.md
複制
- 使得資料與使用者在地理上接近(從而減少延遲)
- 即使系統的一部分出現故障,系統也能繼續工作(從而提高可用性)
- 伸縮可以接受讀請求的機器數量(從而提高讀取吞吐量)
三種流行的變更復制演算法:
- 單領導者(single leader)
- 多領導者(multi leader)
- 無領導者(leaderless)
— —
master/slave 工作原理如下:
- client 寫在主庫,主庫replicate log 到從庫
- 當客戶想要從資料庫中讀取資料時,它可以向領導者或追隨者查詢。 但只有領導者才能接受寫操作
—
同步複製 / 非同步複製
一般來說 ,多個slave ,可以設一個slave 為同步複製,其他為非同步複製。(如果全部slave 都設為同步,只有一個salve 有問題就變得不可用了)
主庫和同步從庫。 這種配置有時也被稱為 半同步(semi-synchronous)
- 好處: 主庫壞了,還有一個從庫是和主庫一樣的資料
設定新從庫
有時候需要設定一個新的從庫,為了:
- 增加副本的數量
- 換失敗的節點
COPY原理是怎樣?
概念:
- 在某個時刻獲取主庫的一致性快照(如果可能),而不必鎖定整個資料庫。
- 將快照複製到新的從庫節點。
- 從庫連線到主庫,並拉取快照之後發生的所有資料變更。
- 當從庫處理完快照之後積壓的資料變更,我們說它 趕上(caught up) 了主庫。現在它可以繼續處理主庫產生的資料變化了。
— — —
處理節點宕機
資料庫是如何實現高可用,我們可以分為 從庫失效和主庫失效
- 從庫失效:追趕恢復
從庫可以從日誌中知道,在發生故障之前處理的最後一個事務。因此,從庫可以連線到主庫,並請求在從庫斷開連線時發生的所有資料變更。當應用完所有這些變化後,它就趕上了主庫,並可以像以前一樣繼續接收資料變更流。
- 主庫失效:故障切換
其中一個從庫需要被提升為新的主庫,需要重新配置客戶端,以將它們的寫操作傳送給新的主庫,其他從庫需要開始拉取來自新主庫的資料變更。這個過程被稱為故障切換(failover)。
通常由以下步驟組成:
1.確認主庫失效。
2.選擇一個新的主庫。(主庫的最佳人選通常是擁有舊主庫最新資料副本的從庫(最小化資料損失))
3.重新配置系統以啟用新的主庫。
故障切換會出現很多大麻煩:
- 如果使用非同步複製,則新主庫可能沒有收到老主庫最後的寫入操作。在選出新主庫後,如果老主庫重新加入叢集,新主庫在此期間可能會收到衝突的寫入,那這些寫入該如何處理?
-> 最常見的解決方案是簡單丟棄老主庫未複製的寫入,這很可能打破客戶對於資料永續性的期望。 - 如果資料庫需要和其他外部儲存相協調,那麼丟棄寫入內容是極其危險的操作。EX 舊資料庫有 id 002(沒送到slave),slave 變主庫又生了一個id002
- 發生某些故障時,可能會出現兩個節點都以為自己是主庫的情況
- 設定heart beat 時間多久?
— —
實際是怎樣同步到其他資料庫?
複製日誌的實現
- 基於語句的複製 — ex: send “INSERT xxx” 到 從庫
問題: 如果用到函數 就有問題(RAND(), NOW()…) - 傳輸預寫式日誌(WAL) — 之前提到的 日誌結構儲存引擎 的日誌或B樹 預寫式日誌(Write Ahead Log, WAL)
問題:
- 日誌記錄的資料非常底層,有時和資料庫版本 也會影響 - 邏輯日誌複製(基於行) — 另一種日誌,比較易parse 和操作
- 基於觸發器的複製 — 資料庫可以註冊程式,只要資料修改都會觸發
問題: 基於觸發器的複製通常比其他複製方法具有更高的開銷,並且比資料庫的內建複製更容易出錯
— — — —
複製延遲問題
解決方法
- 讀己之寫
- 單調讀
讀己之寫
使用者可以看到自己寫的最新內容,但其他用者是從 slave 來讀(比較慢),方法:
- 讀使用者可能已經修改過的內容時,都從主庫讀。EX USER看自己的個人資料,都是從主庫,其他人看則是從 Slave
- 系統 記住 最後一次的更新時間,如果在1分鐘內,則全部都改到主庫讀。
- client 記住最後一次的更新時間,系統從某個資料庫讀到的更新時間 < 最後一次的更新時間, 就從其他資料庫讀
如果user 使用多裝置,一邊更新完,另一邊要看到更新就更麻煩:
- client 記住最後一次的更新時間 ,不同裝置不一樣
- 如果副本分佈在不同的資料中心,很難保證來自不同裝置的連線會路由到同一資料中心。
- 單調讀
就是按user id 來分配,只能固定到某一個slave
以下是是其他人從 SLAVE讀有問題,單調讀就是可以解決下面的問題
user 2345 都是從slave 讀,有時某個slave 比較慢,就會遇到有時看到資料更新,但有時還是舊的情狀
— — — —
如果是分多個partion 時,可能會遇到 上述的 情況(可能網路延遲)
observer 就會先聽到後面的內容
這種有因果順序的資料,可以用都寫在同一個 partion 來解決
/*一致字首讀(consistent prefix reads)。 這個保證說:如果一系列寫入按某個順序發生,那麼任何人讀取這些寫入時,也會看見它們以同樣的順序出現。 */
— — —
多主複製
之前討論的都是單一個master , 缺點就是如果掛了,就無法寫入 或者有多個data center
- 多領導者配置中可以在每個資料中心都有主庫。
/*在單個數據中心內部使用多個主庫很少是有意義的,因為好處很少超過複雜性的代價。/
好處:
效能: 因為使用者不知道有異步 sync 資料到另一個DATA center ,無差別
容忍資料中心停機: 一邊壞了還可以寫入
容忍網路問題:如果和同一個data center , 因為是另一個DATA center 所以使用非同步,所以容忍度更高
需要離線操作的客戶端:試想手機的LOCLA DATA(也比喻為一個DATA center) 可以讓使用者在沒有網路下使用
— —
多領導者複製的最大問題是可能發生寫衝突,這意味著需要解決衝突。
同步衝突檢測: 要等到全部副本OK,才和使用者說,但這樣就沒有義意
避免衝突: 都由某一個data center 的主庫做寫入(最常做的方法)
收斂至一致的狀態:
實現衝突合併解決有多種途徑:
- 給每個寫入一個唯一的ID,挑選最高ID的寫入作為勝利者,並丟棄其他寫入。如果使用時間戳,這種技術被稱為最後寫入勝利(LWW, last write wins)。雖然這種方法很流行,但是很容易造成資料丟失
- 為每個副本分配一個唯一的ID,ID編號更高的寫入具有更高的優先順序。這種方法也意味著資料丟失。
- 以某種方式將這些值合併在一起 — 例如,按字母順序排序,然後連線它們(在圖5–7中,合併的標題可能類似於“B/C”)。
- 用一種可保留所有資訊的顯式資料結構來記錄衝突,並編寫解決衝突的應用程式程式碼(也許透過提示使用者的方式)。
自定義衝突解決邏輯
太部分多主的資料庫,有一些設定程式碼來解決衝突。該程式碼可以在寫入或讀取時執行:
寫入執行:檢測到複製更改日誌中存在衝突,就會呼叫衝突處理程式。
讀取時執行:當檢測到衝突時,所有衝突寫入被儲存。 然後使用者讀的使用,會跳出來不同的版本選擇更新。
— — —
多主複製拓撲
在描述如果有多個master 時,要怎樣傳送消息
一般使用一個unique id 來決定有沒有收過那樣的訊息防止LOOP
— — — — — — — — — — — -
無master mode
如果有些update where xxx= ? 因為網路問題會遇到以下問題:
insert x=1
update x=2 where x=1
update x=3 where x=2 //< 這個就會遇到其他主庫還沒update為2 就update 不成功
因為主庫update 有不一樣的棘手的問題, 所以有 無主庫的概念
— -
那無主庫怎樣handel 資料
讀修復和反熵
複製方案應確保最終將所有資料複製到每個副本。在一個不可用的節點重新聯機之後,它如何趕上它錯過的寫入?
在Dynamo風格的資料儲存中經常使用兩種機制:
- 讀修復(Read repair)
當client 對全部replica 讀取資料,只要有一個不符時,則比較那個比較新,主動發起update
反熵過程(Anti-entropy process)
某些資料庫會不斷查詢其他replica 有沒有不一樣
— —
如果有三個副本,只要有兩個成功寫入就叫成功。那大於三個時怎樣決定?
讀寫的法定人數
如果有n個副本,每個寫入必須由w節點確認才能被認為是成功的,並且我們必須至少為每個讀取查詢r個節點。
只要$w + r> n
ex: 5(n) 個節點,寫入時至少3 個寫入成功的話,則讀時要至少向r=2 個副本來讀
但這種還有一定的風險
— -