實際工程中NWR是寬松模式,中心節點(第三方zookeeper)讀取R個副本,選擇R個副本中 版本號最高 的為新的primary。還可以利用paxos等協議選出新的primary,每個節點以自己的版本號 發起paxos提議 ,選出的新primary是某個超過半數副本中版本號最大的副本 。
當機器宕機的時候,我們需要如何去恢復宕機之前那些數據? 日志技術 是宕機恢復的主要技術之一。日志技術最初使用在數據庫系統中。嚴格來說日志技術不是一種分布式系統的技術,但在分布式系統的實踐中,卻廣泛使用了日志技術做宕機恢復,甚至如BigTable等系統將日志保存到一個分布式系統中進一步增強了系統容錯能力。
先簡單介紹數據庫系統中的日志技術,抽象簡化問題模式,在簡化模型的基礎上介紹兩種實用的日志技術 Redo Log 與 No Redo/ No Undo Log 。
數據庫系統日志技術簡述
在數據庫系統中實現 宕機恢復 ,其難點在于數據庫操作需要滿足ACID,尤其在支持事務的數據庫系統中宕機往往發生在某些事務只執行了部分操作的時候。此時宕機恢復的主要目標就是數據庫系統恢復到一個穩定可靠狀態,消除未完成的事物對數據庫狀態的影響。
數據庫日志主要分為Undo Log、Redo Log、Redo/Undo Log與No Redo/ No Undo Log。這四類日志的區別在更新日志文件和數據文件的時間點要求不同,從而造成性能和效率也不相同。可以參考有關數據庫系統方面的資料去深入了解這四類日志技術 。
Redo Log與Check point
問題模型 :首先簡化原數據庫系統中的問題模型為一個較為簡單的模型:假設需要涉及一個高速的單機查詢系統,將數據全部存放在內存中以實現高速的數據查詢,每次更新操作更新一小部分數據(列如key-value中的某一個key)。現在問題為利用日志技術實現該內存查詢系統的宕機恢復。與數據庫的事務不同的是,這個問題模型中的每個成功的更新操作都會生效。也等效為數據庫的每個事務只有一個更新操作,且每次更新操作都可以也必須立即提交。
Redo Log :Redo Log是一種非常簡單實用的日志技術。在上面的問題模型中,只需按照如下流程更新既可以使用 Redo Log 。
Redo Log更新流程
- 將 更新操作的結果 (例如set k1=1,則記錄k1=1)以追加(Append)的方式寫入磁盤日志文件
- 按更新操作修改內存中的數據
- 返回更新成功
上述更新流程中第2步如果沒有考慮修改內存數據需要多線程互斥等問題,對于說明Redo Log的原理沒有影響。
從Redo Log的流程可以看出,Redo寫入日志的內容是更新操作完成后的結果(這點是與Undo Log的區別之一),由于是 順序追加日志 文件,在磁盤等對 順序寫 有力的存儲設備上效率較高。
用Redo Log進行宕機恢復非常簡單,只需要” 回放 ”日志即可。
Redo Log的宕機恢復
- 從頭讀取日志文件中的每次更新操作的結果,用這些結果修改內存中的數據。
從Redo Log的宕機恢復流程也可以看出,只有寫入日志文件的更新結果才能在宕機后恢復。這也是為什么在Redo Log流程中需要先更新日志文件再更新內存中的數據的原因。假如先更新內存中的數據,那么用戶立刻就能讀到更新后的數據,一旦在完成內存修改與寫入日志直接發生宕機,那么最后一次更新操作無法恢復,但之前與困難已經讀取到了更新后的數據,從而引起不一致的問題。
Check point
宕機恢復流量的缺點是需要回放所有redo日志,效率較低,假如需要恢復的操作非常多,那么這個宕機恢復過程將非常漫長。解決這一問題的方法引入了check point技術。在簡化的模型下,check point技術的過程即將內存中的數據以某種易于重新加載的數據組織方式完整的dump到磁盤,從而減少宕機恢復時需要回放的日志數據。
check point流程
- 向日志文件中記錄“begin check point”
- 將內存中的數據以某種易于重新加載的數據組織方式dump到磁盤上
- 像日志文件中記錄“end check point”
在check point流程中,數據可以繼續按照Redo Log更新流程走,這段過程中新更新的數據可以dump到磁盤也可以不dump到磁盤,具體取決于實現。例如,check point開始時k1=v1,check point過程中某次更新為k1=v2,那么dump到磁盤上的k1的值可以是v1也可以是v2。
check point的宕機恢復
- 將dump到磁盤的數據加載到內存
- 從后向前掃描日志文件,尋找最后一個“end check point”日志
- 從最后一個“end check point”日志向前找到最近的一個“begin check point”日志,并回放該日志之后的所有更新操作日志
上述的check point的方式依賴redo日志中記錄的都是更新后的數據結果這一特征,即使dump的數據已經包含了某些操作的結果,重新回放這些操作的日志也不會造成數據錯誤。同一條日志可以重復回放的操作即所謂具有” 冪等性 ”的操作。工程中,有些時候Redo日志無法具有冪等性,例如 加法操作、append操作 等。此時,dump的內存數據一定不能包括“begin check point”日志之后的操作。為此,有兩種方法,其一是checkpoint的過程中停更新服務,不能進行新的操作,另一種方法是,設計一種支持快照(snapshot)的內存數據結構,可以快速的將內存生成快照,然后寫入check point日志再dump快照數據。至于如何設計支持快照的內存數據結構,方式也很多,例如假設內存數據結構維護key-value值,那么可以使用哈希表數據結構,當做快照時,新建一個哈希表接收新的更新,原哈希表用于dump數據,此時內存存在兩個哈希表,查詢數據時查詢兩個哈希表并合并結果 。
No Undo/ No Redo Log
介紹另一種特殊的日志技術“No Undo/ No Redo Log”,這種技術也稱之為“0/1目錄”(0/1 directory) 假設另一種問題場景:若數據維護在磁盤中,某批更新由若干個更新操作組成,這些更新操作需要 原子生效 ,即要么同時生效,要么都不生效。
0/1目錄技術中有兩個目錄結構,稱為目錄0和目錄1。另有一個結構稱為主記錄(master record)記錄當前正在使用的目錄稱為活動目錄。主記錄中要么記錄使用目錄0,要么記錄使用目錄1。目錄0或者目錄1記錄了各個數據在日志文件中的位置。
圖中給出了一個0/1目錄的例子。活動目錄為目錄1,數據有A、B、C三項。查目錄1可得A、B、C三項的值分別為2、5、2。0/1目錄的數據更新過程始終在非活動目錄上進行,只是在數據生效前。將主記錄中的0、1值反轉,從而切換主記錄。
0/1目錄數據更新流程
- 將活動目錄完整拷貝到非活動目錄
- 對于每個更新操縱,新建一個日志項記錄操作后的值,并在非活動目錄中將相應數據的位置修改為新建的日志項的位置
- 原子性修改主記錄: 反轉主記錄中的值,使得非活動目錄生效
0/1目錄的更新流程很簡單,通過0、1目錄的主記錄切換使得一批修改的生效是原子的。
0/1目錄將批量事務操作的原子性通過目錄手段歸結到主記錄的原子切換。 由于多條記錄的原子修改一般較難實現而單條記錄的原子修改往往可以實現,從而降低了問題實現的難度。 在工程中0/1目錄思想運用非常廣泛,其形式也不局限在上面的流程中,可以是內存中兩個數據結構的來回切換,也可以是磁盤上的兩個文件目錄的來回生效切換 。
工程投影
日志技術的使用非常廣泛,在zookeeper系統中,為了實現高效的塑膠訪問,數據完全保存在內存中,但更新操作的日志不斷持久化到磁盤,另一方面,為了實現較快速的宕機恢復,zookeeper周期性的將內存數據以checkpoint的方式dump到磁盤。
MySQL的主從庫設計也是基于日志。從庫只需通過回放主庫的日志,就可以實現與主庫的同步。由于從庫同步的速度與主庫更新的速度沒有強約束,這種方式只能實現 最終一致性 。