分布式數據
引言
你可能會出於各種各樣的原因,希望將數據庫分布到多台機器上:
可擴展性
如果你的數據量、讀取負載、寫⼊負載超出單台機器的處理能⼒,可以將負載分散到多台計算機上。
容錯/⾼可⽤性
如果你的應⽤需要在單台機器(或多台機器,⽹絡或整個數據中⼼)出現故障的情況下仍然能繼續⼯
作,則可使⽤多台機器,以提供冗余。⼀台故障時,另⼀台可以接管。
延遲
如果在世界各地都有⽤戶,你也許會考慮在全球范圍部署多個服務器,從⽽每個⽤戶可以從地理上最近
的數據中⼼獲取服務,避免了等待⽹絡數據包穿越半個世界。
在具體實現上,⽆共享架構(shared-nothing architecture)因其較高的性價比,以及強大的功能而被廣泛使用,無共享架構有時稱為⽔平擴展(horizontal scale) 或向外擴展(scale out))。
在這種架構中,運⾏數據庫軟件的每台機器/虛擬機都稱為節點(node)。每個節點只使⽤各⾃的處理器,內存和磁盤。節點之間的任何協調,都是在軟件層⾯使⽤傳統⽹絡實現的。
雖然分布式⽆共享架構有許多優點,但它通常也會給應⽤帶來額外的復雜度,有時也會限制你可⽤數據模型的表達⼒。接下來我們將詳細討論分布式系統帶來的各種問題,以及問題的解決方案;
五.復制
-
適用場景:少數據量,單庫可承載的場景。
-
目的:
- 1.擴展性。提高吞吐量
- 2.降低延遲。通過將數據放在離用戶較近的地方,以便能夠更快的訪問數據。
- 3.高可用。即使部分節點故障停機,也能保持服務正常運行。
-
領導者&追隨者
-
I.同步復制 vs 異步復制
-
a.同步復制
-
優點
- 從庫保證有與主庫⼀致的最新數據副本。如果主庫突然失效,我們可以確信這些數據仍然能在從庫上上找到。
-
缺點
- 如果同步從庫沒有響應(⽐如崩潰,或其它任何原因),主庫就⽆法處理寫⼊操作。
-
將所有從庫都設置為同步的是不切實際的:任何⼀個節點的中斷都會導致整個系統停滯不前。
-
-
b.異步復制
-
優點
- 即使所有的從庫都落后了,主庫也可以繼續處理
寫⼊。
- 即使所有的從庫都落后了,主庫也可以繼續處理
-
缺點
- 會出現復制延遲問題,見下文。
-
通常情況下,基於領導者的復制都配置為完全異步。
-
-
c.半同步
- 只保證一部分節點是保持與主庫一致性的數據。
-
-
II.設置新從庫
-
步驟:
- 1.獲取主庫某時刻一致性快照。
2.快照復制
3.鏈接主庫,拉取快照后的數據變更。要求主庫中日志位置精確,如日志序列號,二進制日志坐標。
4.從庫趕上主庫后,就可以正常處理主庫的產生數據變化了。
- 1.獲取主庫某時刻一致性快照。
-
-
III.處理節點宕機
- 從庫失效:追趕恢復
- 主庫失效:故障切換
-
IV.復制日志的實現
-
定義:每當領導者將新數據寫⼊本地存儲時,它
也會將數據變更發送給所有的追隨者,稱之為復制⽇志(replication log)記錄或變更流
(change stream)。 -
實現1.基於語句的復制
- 優點:可讀性較強
缺點:執行依賴於語句的復制會有問題,如:自增主鍵、隨機字符串、now()函數,存儲過程等。 - 實踐:mysql默認使用該模式,並在特定情況下切換至基於日志的復制模式。
- 優點:可讀性較強
-
實現2.傳輸預寫式日志WAL(Write Ahead Log)
- 優點:基於追加日志的方式,記錄數據的變更,可以解決以上復制問題。
缺點:1.wal偏底層,通常記錄的是磁盤某分區數據更改;2.數據與存儲耦合性較強; - 實踐:PostgreSQL和oracle使用此復制方式。
- 優點:基於追加日志的方式,記錄數據的變更,可以解決以上復制問題。
-
實現3.基於邏輯日志(Binlog)的復制
- 優點:數據變更的二進制字節流,傳輸速度快,且保證數據復制的正確型。
缺點:不可讀。 - 實踐:mysql的二進制日志使用該方式
- 優點:數據變更的二進制字節流,傳輸速度快,且保證數據復制的正確型。
-
實現4.基於觸發器的復制
- 使用應⽤程序代碼復制
-
-
-
復制延遲問題
- 最終一致性:同時對主庫和從庫執⾏相同的查詢,可能得到不同的結果,因為並⾮所有的寫⼊都反映在從庫中。如果停⽌寫⼊數據庫並等待⼀段時間,從庫最終會趕上並與主庫保持⼀致,這種效應成為最終一致性。
- 1.寫后讀:用戶總是可以讀到自己提交的數據。
- 2.單調性:即用戶在讀取某個時間的數據后,不應再讀取到更早時間點的數據。
- 3.一致前綴讀:用戶應該將數據視為具有因果意義的狀態。例如:按照正確的順序查看問題及其答復。
-
三種流行的復制算法
-
1.單主復制
- 客戶端將所有的寫操作發送到單個節點(領導者),該節點將數據更改事件流發送到所有的副本(追隨者)。讀操作可以在任何節點執行,但可能會讀取到舊數據。
- 優點:容易理解,且沒有沖突問題。
-
2.多主復制
- 客戶端的將寫操作發送到幾個領導者節點之一,其中任何一個都可以接受寫入。然后由該節點將數據更改事件流發送給其他領導者節點及其跟隨者節點。
- 優點:出現故障節點、網絡中斷和延遲峰值情況下,多主和無主復制更加穩健。但以僅提供弱一致性為代價。
- 並發問題
-
3.無主復制
- 客戶端發送每個寫入到多個節點,並從多個節點並行讀取,以檢測和糾正具有陳舊數據的節點。
- 並發問題
-
六.分區
-
適用場景:數據量⾮常⼤的時候,在單台機器上
存儲和處理不再可⾏,則分區⼗分必要。
此時需要將數據進⾏分區(partitions),也稱為分⽚(sharding) 。 -
目的:分區的⽬標是在多台機器上均勻分布數據和查詢負載,避免出現熱點(負載不成⽐例的節點)。
-
分區與復制:
- 分區通常與復制結合使⽤,使得每個分區的副本存儲在多個節點上。 這意味着,即使每條記錄屬於⼀個分區,它仍然可以存儲在多個不同的節點上以獲得容錯能⼒。
-
兩種主要的分區⽅法:
-
鍵范圍分區
- 核心思想:為每個分區指定⼀塊連續的鍵范圍(從最⼩值到最⼤值),如紙百科全書的卷)。如果知道范圍之間的邊界,則可以輕松確定哪個分區包含某個值。
- 優點:鍵是有序的,並且分區擁有從某個最⼩值到某個最⼤值的所有鍵。可以進⾏有效的范圍查詢。
- 缺點:如果應⽤程序經常訪問相鄰的主鍵,則存在熱點和偏斜的⻛險。
-
散列分區
- 核心思想:散列進⾏分區,通常先提前創建固定數量的分區,為每個節點分配多個分區,並在添加或刪除節點時將整個分區從⼀個節點移動到另⼀個節點。也可以使⽤動態分區。
- 優點:可以將將偏斜的數據均勻分布
- 缺點:散列分區破壞了鍵的排序,使得范圍查詢效率低下
-
鍵范圍&散列組合分區
- 兩種⽅法搭配使⽤也是可⾏的,例如使⽤復合主鍵:使⽤鍵的⼀部分來標識分區,⽽使⽤另⼀部分作為排序順序。
-
-
分區與二級索引:分區和⼆級索引之間的相互作⽤。二級索引也需要分區,有兩種分區⽅法:
- 方法1: 按⽂檔分區(本地索引)。特點:
1.⼆級索引存儲在與主鍵和值相同的分區中。
2.這意味着只有⼀個分區需要在寫⼊時更新,
3.但是讀取⼆級索引需要在所有分區之間進⾏分散/收集。 - 方法2: 按關鍵詞Term分區(全局索引)。
特點:
- 方法1: 按⽂檔分區(本地索引)。特點:
- ⼆級索引存在不同的分區。輔助索引中的條⽬可以包括來⾃主鍵的所有分區的記錄。
2.當⽂檔寫⼊時,需要更新多個分區中的⼆級索引;
3.但是可以從單個分區中進⾏讀取。
-
分區再平衡
-
定義:隨着時間的推移,數據庫會有各種變化。如查詢吞吐量增加、數據集大小增加、節點機器故障下線等,此時需要數據和請求從⼀個節點移動到另⼀個節點。 將負載從集群中的⼀個節點向另⼀個節點移動的過程稱為再平衡(reblancing)。
-
平衡策略
-
反面教材:hash mod N
- 模\(N\)⽅法的問題是,如果節點數量N發⽣變化,⼤多數密鑰將需要從⼀個節點移動到另⼀個節點。使得重新平衡過於昂貴。我們需要⼀種只移動必需數據的⽅法。
-
固定數量的分區
- ⼀種只移動必需數據的簡單⽅法:創建⽐節點更多的分區,並為每個節點分配多個分區。只有分區在節點之間的移動。分區的數量不會改變,鍵所指定的分區也不會改變。唯⼀改變的是分區所
在的節點。 - 如果分區⾮常⼤,再平衡和從節點故障恢復變得昂貴。如果分區太⼩,則會產⽣太多的開銷。
當分區⼤⼩“恰到好處”的時候才能獲得很好的性能,如果分區數量固定,但數據量變動很⼤,則難以達到最佳性能。
- ⼀種只移動必需數據的簡單⽅法:創建⽐節點更多的分區,並為每個節點分配多個分區。只有分區在節點之間的移動。分區的數量不會改變,鍵所指定的分區也不會改變。唯⼀改變的是分區所
-
動態分區
- 每個分區分配給⼀個節點,每個節點可以處理多個分區,就像固定數量的分區⼀樣。當分區增⻓到超過配置的⼤⼩時,拆分⼤型分區將其中的⼀半轉移到另⼀個節點,以平衡負載;與之相反,如果⼤量數據被刪除並且分區縮⼩到某個閾值以下,則可以將其與相鄰分區合並。此過程與B樹頂層發⽣的過程類似(參閱“B樹”)。
- 動態分區的⼀個優點是分區數量適應總數據量。如果只有少量的數據,少量的分區就⾜夠了,所以開銷很⼩;如果有⼤量的數據,每個分區的⼤⼩被限制在⼀個可配置的最⼤值【23】
- 動態分區不僅適⽤於數據的范圍分區,⽽且也適⽤於散列分區。從版本2.4開始,MongoDB同時⽀持范圍和哈希分區,並且都是進⾏動態分割分區。
-
按節點⽐例分區
- Cassandra和Ketama使⽤的第三種⽅法是使分區數與節點數成正⽐——換句話說,每個節點具有固定數量的分區【23,27,28】。在這種情況下,每個分區的⼤⼩與數據集⼤⼩成⽐例地增⻓,⽽節點數量保持不變,但是當增加節點數時,分區將再次變⼩。由於較⼤的數據量通常需要較⼤數量的節點進⾏存儲,因此這種⽅法也使每個分區的⼤⼩較為穩定。
-
-
-
請求路由
- 分區負載平衡
- 並⾏查詢執⾏引
擎
七、事務
-
事務是⼀個抽象層,允許應⽤程序假裝某些並發問題和某些類型的硬件和軟件故障不存在。各式各樣的錯誤被簡化為⼀種簡單情況:事務中⽌(transaction abort),⽽應⽤需要的僅僅是重試。
-
弱隔離級別
-
1.讀已提交Read Commit
-
兩個保證:
- 無臟讀
- 無臟寫
-
實現讀已提交
-
防止臟寫
- 使用行鎖實現:當事務想要修改特定的對象(行或文檔)時,它必須首先獲得該對象的鎖。然后必須持有該鎖直到事務提交或中止;
一次只有一個事務可以持有任何給定對象的鎖;
如果另一事務想要寫入同一個對象,則必須等到第一個事務提交或中止后才能獲取該鎖並繼續。
- 使用行鎖實現:當事務想要修改特定的對象(行或文檔)時,它必須首先獲得該對象的鎖。然后必須持有該鎖直到事務提交或中止;
-
防止臟讀
- 方法一:使用行鎖。但實踐效果並不好,損失了只讀事務的響應時間,並且可能因為等待鎖導致連鎖反應從而使整體響應遲緩。
- 方法二:對於寫入的每個對象,數據庫都會記住舊的已經提交的值,和由當前持有寫入鎖的事務設置的新值;
當事務正在進行時,任何其他讀取對象的事務都會拿到舊值;
只有當新值提交以后,事務才會切換到讀取新值。
-
-
不足:
- 不可重復讀 nonrepeatable 或讀取偏差 read skew
- 丟失更新Lost update
-
-
2.快照隔離和可重復讀Read Repeatable
-
一個保證:
- 可重復讀
-
不可重復讀問題
-
描述:事務A在對一個對象寫入期間,另一個事務B分別在事務A提交前、后讀取到不同值的現象。
-
解決
- 快照隔離:每個事務都從數據庫的一致快照(consistent snapshot)中讀取,即事務可以看到事務開始時在數據庫中提交的所有數據,即使這些數據隨后被另一個事務更改,每個事務也只能看到該特定時間點的舊數據。
- 快照隔離對長時間運行只讀查詢非常有用,如備份和分析。
-
-
實現快照隔離
-
關鍵原則:讀不阻塞寫,寫不阻塞讀。
-
多版本並發控制MVCC,multi-version concurrency control:數據庫保留和維護同一個對象在不同時間點的多個提交版本。
-
PostSQL中MVCC的實現:
- 當一個事務開始時會被賦予一個唯一的永遠遞增的事務ID,每當事務向數據庫寫入任何內容時,它所寫入的數據都會被標記上寫入者的事務ID。
- 可見性規則:同時滿足以下2個條件,則可見一個對象:
a。讀事務開始時,創建該對象的事務已經提交。
b。對象未被標記刪除或已被標記刪除,請求刪除的事務在讀事務開始時尚未提交。
-
-
-
3.防止丟失更新Lost update
-
並發寫入事務可能導致的問題
-
解決方案
-
a。原子寫
-
許多數據庫提供了原⼦更新操作,從⽽消除了在應⽤程序代碼中執⾏讀取-修改-寫⼊序列的需要。如下在大多數數據庫是並發安全的:
UPDATE counters SET value = value + 1 WHERE key = 'foo';
如果你的代碼需要,那這通常是最好的解決⽅案。 -
a。實現
- 方案一:游標穩定性技術,事務在讀取對象時獲取其上的排他鎖,在更新操作完成之前沒有其他事務可以讀取該對象。通常的實現方式。
- 方案二:簡單地強制所有的原子操作在單一線程上執行。
-
-
b。顯示鎖定
- 防⽌丟失更新的另⼀個選擇是讓應⽤程序顯式地鎖定將要更新的對象。然后應⽤程序可以執⾏讀取-修改-寫⼊序列,如果任何其他事務嘗試同時讀取同⼀個對象,則強制等待,直到第⼀個讀取-修改-寫⼊序列完成。
- FOR UPDATE ⼦句告訴數據庫應該對該查詢返回的所有⾏加鎖。
-
c。自動檢測丟失的更新
-
原⼦操作和鎖是通過強制讀取-修改-寫⼊序列按順序發⽣,來防⽌丟失更新的⽅法。
-
另⼀種⽅法是允許它們並⾏執⾏,如果事務管理器檢測到丟失更新,則中⽌事務並強制它們重試其讀取-修改-寫⼊序列。
-
優點:數據庫可以結合快照隔離⾼效地執⾏此檢查。
- Oracle可串行化和SQL server快照隔離級別都會自動檢測丟失的更新。
- MySQL/InnoDB的可重復讀並不會檢測丟失更新。
-
-
d。比較並設置(CAS, Compare And Set)
- 此操作的⽬的是為了避免丟失更新。
- 但是,如果數據庫允許 WHERE ⼦句從舊快照中讀取,則此語句可能⽆法防⽌丟失更新(MVCC)
- 在依賴數據庫的CAS操作前要檢查其是否安
全。
-
e。沖突解決和復制
-
多節點情況
- 防⽌丟失的更新需要考慮另⼀個維度:由於在多個節點上存在數據副本,並且在不同節點上的數據可能被並發地修改,基於鎖或CAS操作的技術不適⽤於這種情況,因此需要采取⼀些額外的步驟來防⽌丟失更新。
- ⼀種常⻅⽅法是允許並發寫⼊創建多個沖突版
本的值(也稱為兄弟),並使⽤應⽤代碼或特殊數據結構在事實發⽣之后解決和合並這些版本。 - 另⼀⽅⾯,最后寫⼊為准(LWW)也可解決沖突,但該⽅法很容易丟失更新。
-
-
f。寫入偏差和幻讀
-
I.不同事務並發寫入相同對象情況
-
導致的問題
- 臟寫,丟失更新
-
解決方式
- 通過【鎖】和【原子寫操作】這類手動安全措施。
-
-
II.不同事務並發寫入不同對象情況
-
導致的問題
- 寫偏差,幻讀
-
寫偏差
- 如果兩個事務讀取相同的對象,然后更新其中⼀些對象(不同的事務可能更新不同的對象),則可能發⽣寫⼊偏差。
-
導致寫偏差的幻讀
- ⼀個事務中的寫⼊改變另⼀個事務的搜索查詢的結果,被稱為幻讀【3】
-
解決方式
- 較優:使用觸發器,或者物化視圖
- 次優:使用FOR UPDATE顯示鎖定事務所依賴的所有行。
-
物化沖突
- 如果幻讀的問題是沒有對象可以加鎖,也許可以⼈為地在數據庫中引⼊⼀個鎖對象?
- 例如,在會議室預訂的場景中,可以想象創建⼀個關於時間槽和房間的表。此表中的每⼀⾏對應於特定時間段(例如15分鍾)的特定房間。可以提前插⼊房間和時間的所有可能組合⾏(如接下來的六個⽉)。
現在,要創建預訂的事務可以鎖定( SELECT FOR UPDATE )表中與所需房間和時間段對應的⾏。在獲得鎖定之后,它可以檢查重疊的預訂並像以前⼀樣插⼊新的預訂。
請注意,這個表並不是⽤來存儲預訂相關的信息——它完全就是⼀組鎖,⽤於防⽌同時修改同⼀房間和時間范圍內的預訂。 - 這種⽅法被稱為物化沖突(materializing conflicts),因為它將幻讀變為數據庫中⼀組具體⾏上的鎖沖突【11】。
不幸的是,弄清楚如何物化沖突可能很難,也很容易出錯。在⼤多數情況下。可序列化 的隔離級別是更可取的方案。
-
-
-
-
-
-
4.可序列化(Serializable)
-
目標:解決幻讀
-
實現
-
方案一:真正的串行化
-
順序執⾏所有事務使並發控制簡單多了,但數據庫的事務吞吐量被限制為單機單核的速度。
只讀事務可以使⽤快照隔離在其它地⽅執⾏,但對於寫⼊吞吐量較⾼的應⽤,單線程事務處理器可能成為⼀個嚴重的瓶頸。 -
最佳實踐
- 1.每個事務都必須小而快,只要有一個緩慢的事務,就會拖慢所有事務處理。
- II。僅限於活躍數據集可以放入內存的情況,因為訪問磁盤會很慢。
- III。寫入吞吐量必須低到能在單個CPU核上處理,否則事務需要化分支單個分區,且不需要跨分區協調。
- IV。跨分區事務是可能的,但是他們的使用成都有很大的限制。
-
分區
- 為了擴展到多個CPU核⼼和多個節點,可以對數據進⾏分區。
- 找到⼀種對數據集進⾏分區的⽅法,以便每個事務只需要在單個分區中讀寫數據,那么每個
分區就可以擁有⾃⼰獨⽴運⾏的事務處理線程。在這種情況下可以為每個分區指派⼀個獨⽴的CPU核,事務吞吐量就可以與CPU核數保持線性擴展【47】。
-
-
方案二:兩階段鎖定(2PL)
-
實現
-
讀阻塞寫,寫阻塞讀。表級別
-
悲觀鎖
-
性能如何
- 性能非常差
-
變形一:謂詞鎖
- 它類似於2PL描述的共享/排它鎖,但不屬於特定的對象(例如,表中的⼀⾏),它屬於所有符合某些搜索條件的對象。
- 讀阻塞寫,寫阻塞讀。條件匹配的數據行級別
- 性能較差
-
變形二:索引范圍鎖
- 又叫間隙鎖next-key locking,大多數2PL數據的實現。近似版的謂詞鎖
- 這種方法可能會鎖定更大范圍的對象,而不是維持可串性化所必須的范圍。
它可以有效防止幻讀和寫入偏差,開銷也較低,是一個很好的折中選擇。 - 讀阻塞寫,寫阻塞讀。條件中索引列的級別,如果無索引則是表級別
-
-
方案三:可序列化快照隔離(SSI,)
-
序列化的隔離級別和⾼性能是從根本上相互⽭盾的嗎?可序列化隔離提供了一種選擇,它提供了完整的可序列化隔離級別,但與快照隔離相⽐只有只有很⼩的性能損失。
-
樂觀鎖:即如果存在潛在的危險也不阻止事務,而是繼續執行事務,希望一起都好起來。當一個事務想要提交時,數據庫檢查是否有什么不好的事情發生(即隔離是否被違反);如果是,事務將被中止,並且必須重拾。只有可序列化的事務才被允許提交。
-
樂觀鎖的優點和缺點:
- 如果存在很多爭用/競爭,則表現不佳,因為這會導致很大一部分事務需要中止。如果系統已經接近最大吞吐量,來自重試事務的額外負載可能會使性能變差。
- 但是如果有足夠的備用容量,並且事務之間的爭用不是太高,樂觀的並發控制技術往往比悲觀的性能要好。
-
顧名思義SSI基於快照隔離,也就是事務中所有讀取都是來自數據庫的一致性快照。在快照隔離的基礎上,SSI增加了一種算法來檢測寫入之間的序列化沖突,並確定要中止哪些事務。
-
性能
- 中⽌率顯着影響SSI的整體表現。SSI要求同時讀寫的事務盡量短(只讀⻓事務可能沒問題)。此時性能較好
-
-
-
八.分布式系統的麻煩
-
子主題 4
- 時鍾錯誤
- 進程暫停
- 無上限的網絡延遲
-
故障與部分失效
- 單個計算機上的軟件,通常會以⼀種相當可預測的⽅式運⾏,它沒有根本性的不可靠原因。
在分布式系統中,情況有本質上的區別。在分布式系統中,盡管系統的其他部分⼯作正常,但系統的某些部分可能會以某種不可預知的⽅式被破
壞。這被稱為部分失效(partial failure)。 - 難點在於部分失效是不確定性的
(nonderterministic):如果你試圖做任何涉及多個節點和⽹絡的事情,它有時可能會⼯作,有時會出現不可預知的失敗。 - 如果要使分布式系統⼯作,就必須接受部分故障的可能性,並在軟件中建⽴容錯機制。換句話說,我們需要從不可靠的組件構建⼀個可靠的系統。
故障處理必須是軟件設計的⼀部分,並且作為軟件的運維,您需要知道在發⽣故障的情況下,軟件可能會表現出怎樣的⾏為。
- 單個計算機上的軟件,通常會以⼀種相當可預測的⽅式運⾏,它沒有根本性的不可靠原因。
-
不可靠的網絡
-
真實的網絡環境很不穩定,⽹絡故障時有發生,如果⽹絡故障的錯誤處理沒有定義與測試,武斷地講,各種錯誤可能都會發⽣。
您需要知道您的軟件如何應對⽹絡問題,並確保系統能夠從中恢復。
有意識地觸發⽹絡問題並測試系統響應。 -
檢測故障
- 如果你想確保⼀個請求是成功的,你需要應⽤程序本身的積極響應【24】。
-
超時與⽆窮的延遲
-
如果超時是檢測故障的唯⼀可靠⽅法,那么超時應該等待多久?不幸的是沒有簡單的答案。
- ⽹絡擁塞和排隊
-
⻓時間的超時意味着⻓時間等待,直到⼀個節點被宣告死亡。在這段時間內⽤戶可能不得不等待或者看到錯誤信息。
短暫的超時可以更快地檢測到故障,但是實際上它只是經歷了暫時的減速⽽導致錯誤地宣布節點失效的⻛險更⾼。例如,由於節點或⽹絡上的負載峰值。 -
更好的⼀種做法是,系統不是使⽤配置的常量超時,⽽是連續測量響應時間及其變化(抖動),並根據觀察到的響應時間分布⾃動調整超時。
-
-
同步網絡vs異步網絡
-
電話電路
- 使用固定的網絡帶寬,傳輸的數據量固定、可預測。可保證的最⼤往返時間。
- 低請求時浪費帶寬,超過可支持帶寬的高請求時網絡阻塞。
-
分組交換協議
- 根據網絡中數據類型及大小的不同,傳輸的數據量也是不可預測的,是動態變化的。
- 有利於應對突發流量的情況。
-
-
-
不可靠的時鍾
-
時鍾和時間很重要!因為很多業務場景都依賴於時鍾或時間
-
分布式系統中,時間是一個比較棘手的事情:
1.因為網絡的傳輸需要時間,故節點間通信是不及時的。
2.每台節點機器都有自己的時鍾,他們不是完全准確的。一組服務器通常使用“網絡時間協議NTP”在一定程度上同步時鍾。 -
單調鍾和時鍾
-
現代計算機中至少有兩種不同的時鍾:即時鍾和單調鍾。
-
時鍾
- 也成為掛鍾時間wall-clock time,他根據某個日歷返回當前的日期和時間。如:
Linux上的clock_gettime(CLOCK_REALTIME)和
Java中的System.currentTimemillis()返回epoch(即一個特定的時間:1970年1月1日午夜UTC)以來的秒數或毫秒數,不包括閏秒。 - 時鍾通常與NTP同步,如果本地使用在NTP服務器之前太遠,則他可能會被重置到先前的時間點,發生時鍾跳回。
- 因為時鍾跳回和忽略閏秒,使用不用用於測量經過的時間。可以作為一個時間日期的參考值。
- 也成為掛鍾時間wall-clock time,他根據某個日歷返回當前的日期和時間。如:
-
單調鍾
-
單調鍾永續測量持續時間,即間隔時間,這個名字來源於單調鍾保證前進的,而不會像時鍾一樣跳回。如:
Linux上clock_gettime(CLOCK_MONOTONIC) ,和Java中的 System.nanoTime() 都是單調時鍾。- 在具有多個CPU插槽的服務器上,每個CPU可能有⼀個單獨的計時器,但不⼀定與其他CPU同步。明智的做法是不要太把這種單調性保證當回事
-
單調鍾的絕對值是毫無意義的,因為他可能是任何值。
-
單調鍾的分辨率相當好:大多數系統中,他們能在幾微秒或更短時間內測量時間間隔。
-
單調鍾不需要同步。同一機器的不同CPU間,分布式系統的不同節點間。。等
-
-
時鍾的不准確性:單調鍾不需要同步,但是時鍾需要根據NTP服務器或其他外部時間源來設置才能有⽤。但計算機中的⽯英鍾不夠精確:它會漂移(drifts)(運⾏速度快於或慢於預期)。時鍾漂移取決於機器的溫度。
使⽤GPS接收機,精確時間協議(PTP)【52】以及仔細的部署和監測可以實現這種精確度。 -
暫停進程?
- GC暫停
- 虛擬機掛起
- 長時間的I/O操作
- 。。。
-
代價:如果某個軟件依賴於精確同步的時鍾,那么結果更可能是悄⽆聲息且⾏蹤渺茫數據的數據丟失,⽽不是⼀次驚天動地的崩潰【53,54】。
-
-
-
知識、真相與謊言
-
真理由多數所定義
- a。分布式系統不能完全依賴於單個節點,因為節點回會是失效,可能會使系統卡死,無法恢復。相反,許多分布式算法都依賴於【法定人數】,即在多個節點之間投票決定減少對某個節點的依賴。
也包括宣告節點死亡的決定,即使一個節點仍然感覺到自己活着,他也必須認為是死的(錯誤的宣告死亡)。個體節點必須遵守法定決定並下台。 - b。通常情況下,⼀些東⻄在⼀個系統中只能有⼀個。如單主復制中的領導者節點、鎖等。
如果⼀個節點繼續表現為“天選者”,即使⼤多數節點已經聲明它已經死了,則在考慮不周的系統中可能會導致問題。
- a。分布式系統不能完全依賴於單個節點,因為節點回會是失效,可能會使系統卡死,無法恢復。相反,許多分布式算法都依賴於【法定人數】,即在多個節點之間投票決定減少對某個節點的依賴。
-
防護令牌:當使⽤鎖或租約來保護對某些資源的訪問時,需要確保⼀個被誤認為⾃⼰是“天選者”的節點不能中斷系統的其它部分。實現這⼀⽬標的⼀個相當簡單的技術就是防護令牌。(fencing)屏蔽令牌保證它是單調遞增,資源僅接受最新的寫入。
- c。請注意,這種機制要求資源本身在檢查令牌⽅⾯發揮積極作⽤,通過拒絕使⽤舊的令牌,⽽不是已經被處理的令牌來進⾏寫操作——僅僅依靠客戶端檢查⾃⼰的鎖狀態是不夠的。
- 拜占庭故障
- 拜占庭故障:在不信任的環境中達成共識的問題被稱為拜占庭將軍問題。
- 拜占庭容錯
- 拜占庭容錯:當⼀個系統在部分節點發⽣故障、不遵守協議、甚⾄惡意攻擊、擾亂⽹絡時仍然能繼續正確⼯作,稱之為拜占庭容錯(Byzantine fault-tolerant)
- 拜占庭容錯相當復雜&實現成本很高:在本書討論的那些系統中,我們通常可以安全地假設沒有拜占庭式的錯誤。在你的數據中⼼⾥,
所有的節點都是由你的組織控制的(所以他們可以信任),輻射⽔平⾜夠低,內存損壞不是⼀個⼤問題。
制作拜占庭容錯系統的協議相當復雜【84】,部署拜占庭容錯解決⽅案的成本使其變得不切實際。
- 弱謊言形式
- 弱謊言形式提供簡單實用的可靠性保證:盡管我們假設節點通常是誠實的,但值得向軟件中添加防⽌“撒謊”弱形式的機制——例如,由硬件問題導致的⽆效消息,軟件錯誤和錯誤配置。這種保護機制並不是完全的拜占庭容錯,但它們仍然是簡單⽽實⽤的步驟,以提⾼可靠性。
- 系統模型與實現
- 算法
- 有很多算法被設計以解決分布式系統問題,即容忍分布式系統的各種故障。
- 系統模型:這個模型是⼀個抽象,描述⼀個
【算法】可能承擔的事情。以某種方式將我們期望在系統中發生的錯誤形式化。
- 定時假說系統模型
- a。同步模型:假設⽹絡延遲,進程暫停和和時鍾誤差都是有界限的,即假設⽹絡延遲,暫停和時鍾漂移將永遠不會超過某個固定的上限。需要注意⽆限延遲的實際情況。
- b。部分同步模型:⼀個系統在⼤多數情況下像⼀個同步系統⼀樣運⾏,但有時候會超出⽹絡延遲,進程暫停和時鍾漂移的界限。
- c。異步模型:不允許對時機做任何假設。
- 節點失效系統模型
- a。崩潰-停⽌故障(crash-stop):意味着節點可能在任意時刻突然停⽌響應,此后該節點永遠消失——它永遠不會回來
- b。崩潰-恢復故障(crashrecovery):假設節點可能會在任何時候崩潰,但也許會在未知的時間之后再次開始響應。節點具有穩定的存儲且會在崩潰中保留,⽽內存中的狀態會丟失。
- c。拜占庭(任意)故障:節點可以做(絕對意義上的)任何事情,包括試圖戲弄和欺騙其他節點。
- 對於真實系統的建模,具有崩潰-恢復故障(crash-recovery)的部分同步模型(partialsynchronous)通常是最有⽤的模型。
- 算法的正確性
九.一致性與共識
-
一致性保證
- 最終一致性:如果你在同⼀時刻問兩個不同副本相同的問題,可能會得到兩個不同的答案。但如果停止向數據庫寫入數據並等待一段不確定時間,那么最終讀取請求會得到相同的答案。
- 分布式⼀致性模型和我們之前討論的事務隔離級別的層次結構有⼀些相似之處。但它們⼤多是⽆關的問題:事務隔離主要是為了,避免由於【同時執⾏事務⽽導致的競爭狀態】,⽽分布式⼀致性主要關於,⾯對延遲和故障時,如何【協調副本間的狀態】。
-
一致性模型
-
線性一致性
-
最強的一致性模型之一。
也稱為原⼦⼀致性(atomic consistency) 【7】,強⼀致性(strong consistency),⽴即⼀致性(immediate consistency)或外部⼀致性(external consistency )【8】)。
它是⼀個新鮮度的保證(recency guarantee)。 -
線性⼀致性背后的基本思想很簡單:使系統看起來好像只有⼀個數據副本。如果數據庫可以提供只有⼀個副本的假象(即,只有⼀個數據副本)。那么每個客戶端都會有相同的數據視圖,且不必擔⼼【復制】滯后了。
-
線性⼀致性的要求是,操作標記的連線總是按時間(從左到右)向前移動,⽽不是向后移動。
要求新鮮性保證:⼀旦新的值被寫⼊或讀取,所有后續的讀都會看到寫⼊的值,直到它被再次覆蓋。 -
應用場景
- 唯⼀性約束
- 鎖定和領導選舉
- 跨信道的時序依賴
-
實現:
-
方案一:真的只⽤⼀個數據副本。
- 優點:簡單
- 缺點:節點失效時服務不可用、甚至有數據丟失風險。
-
方案二:單主同步復制。
- 它們可能(protential)是線性⼀致性的 4 。
- 優點:可靠,單節點失效時,保持服務可用(只讀)。
- 缺點:性能低下。
-
方案三:共識算法。
- 共識協議包含防⽌【腦裂】和【陳舊副本】的措施。可以安全地實現線性⼀致性存儲。如zookeeper
-
-
線性⼀致性的代價
-
面臨的問題:
- 如果應⽤需要線性⼀致性:當某些副本因為⽹絡問題與其他副本斷開連接,那么這些副本掉線時不能處理請求。請求必須等到⽹絡問題解決,或直接返回錯誤。⽆論哪種⽅式,服務都不可⽤(unavailable)。
- 如果應⽤不需要線性⼀致性:那么某個副本即使與其他副本斷開連接,也可以獨⽴處理請求(例如多主復制)。在這種情況下,應⽤可以在⽹絡問題前保持可⽤,但其⾏為不是線性⼀致的。
-
CAP定理:不需要線性⼀致性的應⽤對⽹絡問題有更強的容錯能⼒。
- CAP有時以這種⾯⽬出現:⼀致性,可⽤性和分區容忍:三者只能擇其⼆。這種說法很有誤導性【32】,因為⽹絡分區並不是⼀個選項:不管你喜不喜歡它都會發⽣【38】。
- 在⽹絡正常⼯作的時候,系統可以提供⼀致性(線性⼀致性)和整體可⽤性。發⽣⽹絡故障時,你必須在【線性⼀致性】和【整體可⽤性】之間做出選擇。
- ⼀個更好的表達CAP的⽅法可以是⼀致
的,或者在分區時可⽤【39】。
-
為了線性一致性犧牲性能和可用性 vs 或者為了提高性能、可用性而使用弱一致性。
-
-
-
-
順序保證
-
全序
-
偏序
-
順序和因果順序
- 因果順序不是全序的
- 關系:線性⼀致性隱含着(implies)因果關系:任何線性⼀致的系統都能正確保持因果性【7】。
- 線性⼀致性簡單、易懂,但可能會損害系統的性能和可⽤性,尤其是在系統具有嚴重的⽹絡延遲的情況下。
在許多情況下,看上去需要線性⼀致性的系統,實際上需要的只是因果⼀致性,而因果⼀致性可以更⾼效地實現。
-
序列號順序
-
我們可以使⽤序列號(sequence nunber)或時間戳(timestamp)來排序事件。時間戳不⼀定來⾃時鍾,它可以來⾃⼀個【邏輯時鍾】(logical clock),這是【⼀個⽤來⽣成標識操作的數字序列的算法】,典型實現是使⽤⼀個每次操作⾃增的計數器。
-
這樣的序列號或時間戳是【緊湊的】(只有⼏個字節⼤⼩),它提供了⼀個【全序關系】:也就是說每操作都有⼀個唯⼀的序列號,⽽且總是可以⽐較兩個序列號,確定哪⼀個更⼤(即哪些操作后發⽣)。
-
非因果序列號生成器
-
適用於主庫不存在情況,可能因為使⽤了多主數據庫或⽆主數據庫,或者因為使⽤了分區的數據庫。
-
實現方案:
- a。每個節點都可以⽣成⾃⼰獨⽴的⼀組序列號。例如有兩個節點,⼀個節點只能⽣成奇數,⽽另⼀個節點只能⽣成偶數。
- b。將具有⾜夠⾼分辨率的時鍾時間戳附加到每個操作上。
- c。可以預先分配序列號區塊。例如,節點 A 可能要求從序列號1到1,000區塊的所有權,⽽節點 B 可能要求序列號1,001到2,000區塊的所有權。
-
問題:⽣成的序列號與因果不⼀致。
但⽐單⼀主庫的⾃增計數器性能表現要好,並且更具可擴展性。
-
-
蘭伯特時間戳
- 定義:蘭伯特時間戳就是兩者的簡單組合:(計數器,節點ID)。它提供了⼀個【全序】:如果你有兩個時間戳,則計數器值⼤者是更⼤的時間戳。如果計數器值相同,則節點ID越⼤的,時間戳越⼤。
- 特點:Lamport時間戳【解決了非因果序列號生成器的問題】,它提供了與因果關系⼀致的總排序。
- 關鍵思想:每個節點和每個客戶端跟蹤迄今為⽌所⻅到的最⼤計數器值,並在每個請求中包含這個最⼤計數器值。當⼀個節點收到最⼤計數器值⼤於⾃身計數器值的請求或響應時,它⽴即將⾃⼰的計數器設置為這個最⼤值。
- 缺點:適用於【事后確定勝利者】場景,需要實時確定的場景會有問題。
-
-
全序⼴播(total order broadcast)
-
但是在分布式系統中,讓所有節點對同⼀個全局操作順序達成⼀致可能相當棘⼿。在上⼀節中,我們討論了按時間戳或序列號進⾏排序,但發現它還不如單主復制給⼒(如果你使⽤時間戳排序來實現唯⼀性約束,⽽且不能容忍任何錯誤)。
-
定義:如前所述,單主復制通過選擇⼀個節點作為主庫來確定操作的全序,並在主庫的單個CPU核上對所有操作進⾏排序。
接下來的挑戰是,如果吞吐量超出單個主庫的處理能⼒,這種情況下【如何擴展系統】;以及,如果主庫失效,【如何處理故障切換】。這個問題被稱為全序⼴播。 -
屬性:全序⼴播通常被描述為在【節點間交換消息的協議】。它要滿⾜兩個安全屬性,即使節點或⽹絡出現故障:
- 1.可靠交付(reliable delivery)
沒有消息丟失,即如果消息被傳遞到⼀個節點,它將被傳遞到所有節點。 - 2.全序交付(totally ordered delivery)*
消息以相同的順序傳遞給每個節點。
- 1.可靠交付(reliable delivery)
-
應用:
- 1.數據庫狀態機復制(state machine replication):如果每個消息都代表⼀次數據庫的寫⼊,且每個副本都按相同的順序處理相同的寫⼊,那么副本間將相互保持⼀致。
- 2.實現可序列化的事務。
- 3.實現提供【防護令牌】的鎖服務:序列號可以當成防護令牌⽤,因為它是單調遞增的。在ZooKeeper中,這個序列號被稱為 zxid。
-
全序廣播 vs 線性一致性存儲
-
全序廣播是異步的,消息保證以固定的順序可靠地傳送,但是【不能保證消息何時被送達】。
線性一致性是【新鮮度】的保證,即讀取一定能看見最新的寫入值。 -
使⽤全序⼴播實現線性⼀致的存儲:
- 可以通過將全序⼴播當成僅追加⽇志致的CAS操作來實現,選擇沖突寫⼊中的第⼀個作為勝利者,並中⽌后來者,以此確定所有節點對某個寫⼊是提交還是中⽌達成⼀致。
-
使⽤線性⼀致性存儲實現全序⼴播:
- 最簡單的⽅法是假設你有⼀個線性⼀致的寄存器來存儲⼀個整數,並且有⼀個原⼦⾃增並返回操作【28】。
- 該算法很簡單:每個要通過全序⼴播發送的消息⾸先對線性⼀致寄存器執⾏⾃增並返回操作。然后將從寄存器獲得的值作為序列號附加到消息中。然后你可以將消息發送到所有節點(重新發送任何丟失的消息),⽽收件⼈將按序列號連續發送消息。
-
-
-
-
分布式事務與共識
-
共識是分布式計算中最重要也是最基本的問題之⼀。
-
現在我們已經討論了復制(第5章),事務(第7章),系統模型(第8章),線性⼀致以及全序(本章),我們終於准備好解決共識問題了。
-
場景
- 領導選舉
- 原子提交:在⽀持跨多節點或跨多分區事務的數據庫中,我們必須讓所有節點對事務的結果達
成⼀致:要么全部中⽌/回滾(如果出現任何錯誤),要么它們全部提交(如果沒有出錯)。這個共識的例⼦被稱為原⼦提交(atomic commit)問題
-
-
原子提交與二階段提交2PC
-
單節點原子提交
- 對於在單個數據庫節點執⾏的事務,原⼦性通常由存儲引擎實現。當客戶端請求數據庫節點提交事務時,數據庫將使事務的寫⼊持久化(通常在預寫式⽇志中:參閱“使B樹可靠”),然后將【提交記錄】追加到磁盤中的⽇志⾥。如果數據庫在這個過程中間崩潰,當節點重啟時,事務會從⽇志中恢復:如果提交記錄在崩潰之前成功地寫⼊磁盤,則認為事務被提交;否則來⾃該事務的任何寫⼊都被回滾。
- 在單個節點上,事務的提交主要取決於數據持久化落盤的順序:⾸先是數據,然后是【提交記錄】。事務提交或終⽌的關鍵決定時刻是磁盤完成寫⼊【提交記錄】的時刻:在此之前,仍有可能中⽌(由於崩潰),但在此之后,事務已經提交,即使數據庫崩潰。因此,是單⼀的設備(連接到單個磁盤驅動的控制器,且掛載在單台機器上)使得提交具有原⼦性。
-
分布式原子提交
-
引言:如果⼀個事務中涉及多個節點,僅向所有節點發送提交請求並獨⽴提交每個節點的事務是不夠的。這樣很容易發⽣【違反原⼦性】的情況:提交在某些節點上成功,⽽在其他節點上失敗。
兩階段提交(two-phase commit) 是⼀種⽤於實現跨多個節點的原⼦事務提交的算法,即確保所有節點提交或所有節點中⽌。 它是分布式數據庫中的經典算法。- PS:事務提交必須是不可撤銷的 —— 事務提交之后,你不能改變主意,並追溯性地中⽌事務。這個規則的原因是,⼀旦數據被提交,其結果就對其他事務可⻅,因此其他客戶端可能會開始依賴這些數據。這個原則構成了【讀已提交隔離等級】的基礎,在“讀已提交”⼀節中討論了這個問題。如果⼀個事務在提交后被允許中⽌,所有那些讀取了已提交卻⼜被追溯聲明不存在數據的事務也必須回滾。
-
兩階段提交
-
兩階段提交(2PC, twophase commit)算法是解決原⼦提交問題最常⻅的辦法。2PC是⼀種共識算法。
-
名詞
-
協調者coordinator/事務管理器transaction manager
- a。協調者通常不會出現在單節點事務中。
b。協調者通常在請求事務的相同應⽤進程中以庫的形式實現(例如,嵌⼊在Java EE容器中),但也可以是單ᇿ的進程或服務。
- a。協調者通常不會出現在單節點事務中。
-
參與者(participate)
- 正常情況下,2PC事務以應⽤在多個數據庫節點上讀寫數據開始。我們稱這些數據庫節點為參與者。
-
-
基本流程
- 階段 1 : 當應⽤准備提交時,協調者發送⼀個【准備(prepare)請求】到每個節點,詢問它們是否能夠提交,並跟蹤參與者的響。
- 階段 2 :
a。如果所有參與者都回答“是”,表示它們已經准備好提交,那么協調者發出【提交(commit)請求】,然后提交真正發⽣。
b。如果任意⼀個參與者回復了“否”,則協調者在階段2 中向所有節點發送【中⽌(abort)請求】。
-
類比:⻄⽅的傳統婚姻儀式
- 司儀-> 協調者, 新郎新娘 -> 參與者
-
-
系統承諾:
-
承諾1.當參與者投票“是”時,它承諾它稍后肯定能夠提交(盡管協調者可能仍然選擇放棄)。
- 參與者收到准備請求時,需要確保在任意情況下都的確可以提交事務。這包括將所有事務數據寫⼊磁盤(出現故障,電源故障,或硬盤空間不⾜都不能是稍后拒絕提交的理由)以及檢查是否存在任何沖突或違反約束。通過向協調者回答“是”,節點承諾,只要請求,這個事務⼀定可以不出差錯地提交。換句話說,參與者放棄了中⽌事務的權利,但沒有實際提交。
-
承諾2.⼀旦協調者做出決定,這⼀決定是不可撤銷的。
- 當協調者收到所有准備請求的答復時,會就提交或中⽌事務作出明確的決定(只有在所有參與者投贊成票的情況下才會提交)。協調者必須把這個決定寫到磁盤上的事務⽇志中,如果它隨后就崩潰,恢復后也能知道⾃⼰所做的決定。這被稱為提交點(commit point)。
- ⼀旦協調者的決定落盤,提交或放棄請求會發送給所有參與者。如果這個請求失敗或超時,協調者必須永遠保持重試,直到成功為⽌。沒有回頭路:如果已經做出決定,不管需要多少次重試它都必須被執⾏。如果參與者在此期間崩潰,事務將在其恢復后提交——由於參與者投了贊成,因此恢復后它不能拒絕提交。
-
兩階段提交協議包含的2個關鍵的“不歸路”點,保證了2PC的原⼦性。
-
類比:⻄⽅的傳統婚姻儀式
-
-
協調者失效
- 存疑:⼀旦參與者收到了准備請求並投了“是”,就不能再單⽅⾯放棄 —— 必須等待協調者回答事務是否已經提交或中⽌。如果此時協調者崩潰或⽹絡出現故障,參與者什么也做不了只能等待。參與者的這種事務狀態稱為【存疑(in doubt)】的 或不確定(uncertain)的。
- 完成2PC的唯⼀⽅法是等待協調者恢復。這就是為什么協調者必須在向參與者發送提交或中⽌請求之前,將其提交或中⽌決定寫⼊磁盤上的事務⽇志:協調者恢復后,通過讀取其事務⽇志來確定所有存疑事務的狀態。
-
-
三階段提交
- 兩階段提交被稱為阻塞(blocking)原⼦提交協議,因為存在2PC可能卡住並等待協調者恢復的情況。
- 三階段提交(3PC)的算法:3PC假定⽹絡延遲有界,節點響應時間有限;在⼤多數具有⽆限⽹絡延遲和進程暫停的實際系統中它並不能保證原⼦性。
- 在具有⽆限延遲的⽹絡中,超時並不是⼀種可靠的故障檢測機制,因為即使沒有節點崩潰,請求也可能由於⽹絡問題⽽超時。出於這個原因,2PC仍然被使⽤,盡管⼤家都清楚可能存在協調者故障的問題。
-
-
實踐中的分布式事務
-
缺點:分布式事務的名聲毀譽參半。一方面它難以實現,另一方面性能損失嚴重,據報告稱Mysql中的分布式事務比單節點事務慢10倍以上。
- 成本來源:兩階段提交所固有的性能成本,⼤部分是由於崩潰恢復所需的額外強制刷盤( fsync )【88】以及額外的⽹絡往返。
-
XA事務
- XA(擴展架構(eXtended Architecture)的縮寫)是跨異構技術實現兩階段提交的標准。
- XA不是⼀個⽹絡協議——它只是⼀個⽤來與事務協調者連接的C API。
- XA假定你的應⽤使⽤⽹絡驅動或客戶端庫來與參與者進⾏通信(數據庫或消息服務)。如果驅動⽀持XA,則意味着它會調⽤XA API 以查明操作是否為分布式事務的⼀部分 —— 如果是,則將必要的信息發往數據庫服務器。驅動還會向協調者暴露回調接⼝,協調者可以通過回調來要求參與者准備,提交或中⽌。
- 事務協調者需要實現XA API。
-
存疑持有鎖
-
為什么我們這么關⼼存疑事務?系統的其他部分就不能繼續正常⼯作,⽆視那些終將被清理的存疑事務嗎?
問題在於鎖(locking)。如果要使⽤可序列化的隔離等級,則使⽤兩階段鎖定的數據庫會為事務所讀取的⾏加上共享鎖(參⻅“兩階段鎖定(2PL)”)。當這些鎖被持有時,其他事務不能修改這些⾏。直到存疑事務被解決。 -
從協調者故障中恢復
- 理論上,如果協調者崩潰並重新啟動,它應該⼲凈地從⽇志中恢復其狀態,並解決任何存疑事務。然⽽在實踐中,【孤⽴(orphaned)的存疑】事務確實會出現,即⽆論出於何種理由,協調者⽆法確定事務的結果(例如事務⽇志已經由於軟件錯誤丟失或損壞)。
這些事務⽆法⾃動解決,所以它們永遠待在數據庫中,持有鎖並阻塞其他事務。即使重啟數據庫服務器也⽆法解決這個問題。
唯⼀的出路是讓管理員⼿動決定提交還是回滾事務。 - 啟發式決策(heuristic decistions):
許多XA的實現都有⼀個叫做啟發式決策的緊急逃⽣艙⼝:允許參與者單⽅⾯決定放棄或提交⼀個存疑事務,⽽⽆需協調者做出最終決定。要清楚的是,這⾥啟發式是【可能破壞原⼦性】(probably breaking atomicity)的委婉說法,因為它【違背了兩階段提交的系統承諾】。
- 理論上,如果協調者崩潰並重新啟動,它應該⼲凈地從⽇志中恢復其狀態,並解決任何存疑事務。然⽽在實踐中,【孤⽴(orphaned)的存疑】事務確實會出現,即⽆論出於何種理由,協調者⽆法確定事務的結果(例如事務⽇志已經由於軟件錯誤丟失或損壞)。
-
-
-
因此,啟發式決策只是為了逃出災難性的情況⽽准備的,⽽不是為了⽇常使⽤的。
- 分布式事務的限制
- 核⼼認識:事務協調者本身就是⼀種數據庫(存儲了事務的結果),需要像其他重要數據庫⼀樣⼩⼼地打交道。
- 限制1.如果協調者沒有復制,只是在單台機器上運行,那么他是整個系統的失效單點。ps:即存疑問題
- 限制2. 應⽤服務器不再是⽆狀態的。
許多服務器端應⽤都是使⽤⽆狀態模式開發的(受HTTP的⻘睞),所有持久狀態都存儲在數據庫中,因此具有應⽤服務器可隨意按需添加刪除的優點。
但是,當協調者成為應⽤服務器的⼀部分時,它會改變部署的性質。突然間,協調者的⽇志成為持久系統狀態的關鍵部分—— 與數據庫本身⼀樣重要,因為協調者⽇志是為了在崩潰后恢復存疑事務所必需的。這樣的應⽤服務器不再是⽆狀態的了。
- 限制3. 由於XA需要兼容各種數據系統,因此它必須是所有系統的最⼩公分⺟。例如,它不能檢測不同系統間的死鎖,因為這將需要⼀個標准協議來讓系統交換每個事務正在等待的鎖的信息。以及無法與SSI 協同⼯作,因為這需要⼀個跨系統定沖突的協議。
- 限制4. 2PC成功提交⼀個事務需要所有參與者的響應。因此,如果系統的任何部分損壞,事務也會失敗。因此,分布式事務⼜有【擴⼤失效】(amplifying failures)的趨勢,這⼜與我
們構建容錯系統的⽬標背道⽽馳。
- 容錯共識
- 概念
- ⾮正式地,共識意味着讓⼏個節點就某事達成⼀致。共識問題通常形式化如下:⼀個或多個節點可以提議(propose)某些值,⽽共識算法決定(decides)采⽤其中的某個值。
- 共識算法必須滿足的性質
- a。協商一致性(Uniform agreement):所有節點都接受相同的決議。
- b。誠實性(Integrity):所有節點不能反悔,即對一項提議不能有兩次決定。
- c。合法性(Validity):如果一個節點決定了值v,則v一定是由某個節點所提議的。
- d。可終止性(Termination):節點如果沒有崩潰,則最終一定可以達成協議。
- 可終⽌性是⼀種活性屬性,⽽另外三種是安全屬性。
- i。協商一致性和誠實性定義了共識的【核⼼思想】:決議一直的結果,⼀旦決定,你就不能改變。
ii。合法性屬性束腰式為了排除一些無意義的方案。
iii。可終止性引入了【容錯】思想:⼀個共識算法不能簡單地永遠閑坐着等死 ,它必須取得進展。即使部分節點出現故障,其他節點也必須達成⼀項決定。
- 共識算法和全序⼴播
- ⼤多數這些算法實際上並不直接使⽤這⾥描述的形式化模型(提議並決定單個值,同時滿足以上4個屬性)。取而代之的是全序⼴播算法,全序⼴播將消息按照相同的順序發送到所有節點,有且只有一次。
- 全序⼴播相當於重復進⾏多輪共識(每一輪共識的決定對應於一條消息):
- 由於協商一致性,所有節點決定以相同的順序發送相同的消息。
- 由於誠實性,消息不能重復。
- 由於合法性,消息不會被破壞,也不會憑空捏造。
- 由於可終止性,消息不會丟失。
- 時代編號和法定⼈數
- 迄今為⽌所討論的所有共識協議,在內部都以某種形式使⽤⼀個領導者,但它們並不能保證領導者是獨⼀⽆⼆的。相反,它們可以做出更弱的保證:協議定義了⼀個時代編號(epoch number),並確保在每個時代中,領導者都是唯⼀的。
- 時代編號在Paxos中稱為投票編號(ballot number,在視圖戳復制中成為視圖編號(view number,以及在Raft中稱為任期號碼(term number)),
- 對領導者想要做出的每⼀個決定,都必須將提議值發送給其他節點,並等待法定⼈數的節點響應並贊成提案。法定⼈數通常(但不總是)由多數節點組成【105】。只有在沒有意識到任何帶有更⾼時代編號的領導者的情況下,⼀個節點才會投票贊成提議。
- 因此,我們有兩輪投票:第⼀次是為了選出⼀位領導者,第⼆次是對領導者的提議進⾏表決。關鍵的洞察在於,這兩次投票的法定⼈群必須相互【重疊】(overlap):如果⼀個提案的表決通過,則⾄少得有⼀個參與投票的節點也必須參加過最近的領導者選舉【105】
- 共識的局限性
- 優點
- 共識算法對於分布式系統來說是⼀個巨⼤的突破:它為其他充滿不確定性的系統帶來了基礎的安全屬性(協商一致性,誠實性和合法性),然⽽它們還能保持容錯(只要多數節點正常⼯作且可達,就能取得進展)。
- 缺點
- 在異步復制模式下,節點發生故障切換時,一些已經提交的數據可能會丟失。
- 共識系統通常依靠超時來檢測失效的節點。在網絡糟糕的情況下,會導致頻繁的領導者選舉,繼而導致糟糕的性能表現,
- 共識系統需要至少三個節點才能容忍單節點故障,如果⽹絡故障切斷了某些節點同其他節點的連接,則只有多數節點所在的⽹絡可以繼續⼯作,其余部分將被阻塞(參閱“線性⼀致性的代價”)。
- 成員與協調服務
-
小結
-
在本章中,我們從⼏個不同的⻆度審視了關於⼀致性與共識的話題。我們深⼊研究了:
- 線性一致性:最強的一致性模型,其⽬標是使多副本數據看起來好像只有⼀個副本⼀樣,並使其上所有操作都原⼦性地⽣效。線性⼀致性簡單易懂,但性能低下,尤其是在網絡延遲很大的環境中。
- 因果一致性:因果⼀致性為我們提供了⼀個較弱的⼀致性模型:某些事件可以是並發的,所以版本歷史就像是⼀條不斷分叉與合並的時間線。因果⼀致性沒有線性⼀致性的協調開銷,⽽且對⽹絡問題的敏感性要低得多。
- 即使做到了因果順序,但有些事情也需要通過達成共識才能做出決定。達成共識意味着所有節點⼀致同意所做決定,且這⼀決定不可撤銷。
-
通過深⼊挖掘,我們發現很⼴泛的⼀系列問題實際上都可以歸結為共識問題,並且彼此等價。
從這個意義上來講,如果你有其中之⼀的解決⽅案,就可以輕易將它轉換為其他問題的解決⽅案。這些等價問題包括:- 線性⼀致性的CAS寄存器
- 原⼦事務提交
- 全序⼴播
- 鎖和租約
- 成員/協調服務
- 唯⼀性約束
-
單領導者數據庫可以提供線性⼀致性,唯一性約束,完全有序的復制日志等,但也需要共識算法
-
針對領導者節點失效或者網絡中斷導致領導者不可達的異常問題,應對方案有三種:
- i。等待領導者節點恢復,接受系統將在這段時間阻塞的事實。但不能達成共識,因為不滿足可終止性。
ii。人工故障切換,故障切換的速度受人類行動速度的限制。
iii。使用共識算法自動選擇一個新的領導者。
- i。等待領導者節點恢復,接受系統將在這段時間阻塞的事實。但不能達成共識,因為不滿足可終止性。
-
-
如果你發現⾃⼰想要解決的問題可以歸結為共識,並且希望它能容錯,使⽤⼀個類似ZooKeeper的東⻄是明智之舉。像ZooKeeper這樣的⼯具為應⽤提供了“外包”的共識、故障檢測和成員服務。
-