Citus 總結:分佈式事務及死鎖檢測
- 基礎理論
1.1 ACID 原則
-
Atomicity(原子性):一個事務(transaction)中的所有操作,要麼全部完成,要麼全部不完成,不會結束在中間某個環節。事務在執行過程中發生錯誤,會被恢復(Rollback)到事務開始前的狀態,就像這個事務從來沒有執行過一樣。
-
Consistency(一致性):在事務開始之前和事務結束以後,數據庫的完整性沒有被破壞。這表示寫入的資料必須完全符合所有的預設規則,這包含資料的精確度、串聯性以及後續數據庫可以自發性地完成預定的工作。
-
Isolation(隔離性):數據庫允許多個併發事務同時對其數據進行讀寫和修改的能力,隔離性可以防止多個事務併發執行時由於交叉執行而導致數據的不一致。事務隔離分爲不同級別,包括讀未提交(Read uncommitted)、讀提交(read committed)、可重複讀(repeatable read)和串行化(Serializable)。
-
Durability(持久性):事務處理結束後,對數據的修改就是永久的,即便系統故障也不會丟失。
1.2 CAP 理論
在分佈式數據庫場景下,還要結合分佈式系統的 CAP 理論,定義如下:
-
Consistency(一致性):對某個指定的客戶端來說,讀操作能返回最新的寫操作。對於數據分佈在不同節點上的數據上來說,如果在某個節點更新了數據,那麼在其他節點如果都能讀取到這個最新的數據,那麼就稱爲強一致,如果有某個節點沒有讀取到,那就是分佈式不一致。
-
Availability(可用性):非故障的節點在合理的時間內返回合理的響應 (不是錯誤和超時的響應)。可用性的兩個關鍵一個是合理的時間,一個是合理的響應。合理的時間指的是請求不能無限被阻塞,應該在合理的時間給出返回。合理的響應指的是系統應該明確返回結果並且結果是正確的。
-
Partition tolerance(分區容忍性):當出現網絡分區後,系統能夠繼續工作。打個比方,集羣有多臺機器,有臺機器網絡出現了問題,但是這個集羣仍然可以正常工作。
CAP 理論表明,在存在網絡分區的情況下,一致性和可用性必須二選一。而在沒有發生網絡故障時,即分佈式系統正常運行時,一致性和可用性是可以同時被滿足的。但是,對於分佈式數據庫來說,網絡故障是不可避免的,可用性是必須要保證的,所以只有捨棄一致性來保證服務的 AP。但是對於一些金融相關行業,它有很多場景需要確保一致性,這種情況通常會權衡 CA 和 CP 模型,CA 模型網絡故障時完全不可用,CP 模型具備部分可用性。
1.3 BASE 理論
分佈式數據庫一般採用 2PC(兩階段提交)來提供跨越多個數據庫分片的 ACID 保證。但是引入 2PC 的直接影響就是可用性下降。假設數據分佈在兩個數據庫實例上,每個數據庫實例的可用性是 99.9%,那麼數據庫分片後可用性爲 99.8%。作爲商用軟件,可用性下降是不可容忍的。
BASE 理論就是對 CAP 理論中的一致性和可用性權衡的結果,核心思想是即使無法做到強一致性(Strong consistency),但每個應用都可以根據自身的業務特點,採用適當的方式來使系統達到最終一致性(Eventual consistency)。
BASE 理論定義如下:
-
Basically Available(基本可用):指分佈式系統在出現不可預知故障的時候,允許損失部分可用性,如:響應時間上的損失,或者功能上的損失。
-
Soft state(軟狀態):和硬狀態相對,是指允許系統中的數據存在中間狀態,並認爲該中間狀態的存在不會影響系統的整體可用性,即允許系統在不同節點的數據副本之間進行數據同步的過程存在延時。
-
Eventually consistent(最終一致性):強調的是系統中所有的數據副本,在經過一段時間的同步後,最終能夠達到一個一致的狀態。因此,最終一致性的本質是需要系統保證最終數據能夠達到一致,而不需要實時保證系統數據的強一致性。
- Citus 的分佈式事務方案
2.1 執行步驟
如上圖所示,由 Coordinator 節點負責協調分佈式事務的執行,各 Worker 節點作爲參與者參與分佈式事務的執行:
-
步驟 1 和 2:Coordinator 在本地,以及兩個 worker 節點分別啓動一個事務,並把 worker 節點的事務和 Coordiantor 上的全局事務進行關聯;然後把相關 SQL 語句推送到對應 worker 執行;
-
步驟 3 和 4:客戶端發起 commit 後,由 Coordinator 發起 2PC 提交協議:
-
PREPARE TRANSACTION
:通知所有參與 Worker 節點做好事務提交準備,各 Worker 節點持久化相關數據,反饋可以提交或者需要回滾; -
COMMIT PREPARED
:當 Coordinator 節點收到所有 Worker 節點都投票可以提交事務後,發起提交事務命令,Worker 節點收到消息後完成本地事務提交;如果前一階段有一個 Worker 節點反饋需要歸滾事務,則 Coordiantor 在本階段會發送ROLLBACK
命令;
2.2 故障處理
分佈式事務執行過程中,可能會碰到節點或網絡異常,2PC 協議爲了保證數據的強一致,實際上選擇的是 CAP 理論中的 CA 部分,不能容忍網絡分區異常,實際上降低了分佈式系統的可用性。爲了解決這個問題,Citus 建議針對 Coordinator 和 Worker 節點,都採用 Streaming Replication 的方式保證節點的高可用,當一個節點故障時,可以快速由另外一個 Standby 節點接管並繼續推進事務的執行。
還有一種故障場景,2PC 不能很好的解決,就是當所有 Worker 節點都做出反饋後(可以是提交或回滾),Coordinator 節點故障,此時接管的 Coordinator 節點不知道該如何進一步推進事務(不知道之前的投票結果)。爲了解決這個問題,Citus 使用了類似 3PC 的解決思路,當 Coordinator 節點收到所有 Worker 節點都投票可以提交時,在本地pg_dist_xact
表中記錄該全局事務可以提交,同時還增加定時檢測機制,將可以提交但尚未成功提交的事務繼續推進執行,直到成功提交後,刪除pg_dist_xact
表中對應記錄。
2.3 數據一致性
雖然 Citus 採用 2PC 協議,保證了在同一個分佈式事務中操作的多條記錄的原子性更新。但從客戶端讀取的視角來看,可能會讀到中間態的數據(Base 中的軟狀態),是最終一致性的模型。
原因在於 Citus 並沒有引入集羣級的全局授時機制,對於只讀事務,無法獲取集羣級的全局一致性快照,而是由各 Worker 節點自己進行可見性判斷,當一個分佈式事務正在提交時,在不同的 Worker 節點提交存在時間差,在這個時間間隙內發起的查詢事務,就會在已經提交的 Worker 節點看到已提交數據,在尚未完成提交的 Worker 節點看不到同一個事務寫入的數據,等所有 Worker 節點都完成提交後,再次查詢又能看到最新一致的數據,因此嚴格說 Citus 提供的是最終一致性模型。
2.4 隔離級別
因爲沒有集羣級全局一致性快照的原因,Citus 提供的隔離級別只有 Read Commited,不管客戶端是如何設置的。
- 死鎖檢測
3.1 發生死鎖的場景
在 PostgreSQL 中,當事務更新一行時,需要獲取該行的鎖並一直持有到事務提交爲止,如果有需要獲取相同鎖集的併發事務,但獲取鎖的順序不同,那麼就會出現死鎖。
下面是一個發生死鎖的例子:
S1: UPDATE table SET value = 1 WHERE key = 'hello'; A takes 'hello’ lockS2: UPDATE table SET value = 2 WHERE key = 'world'; B takes 'world’ lockS1: UPDATE table SET value = 1 WHERE key = 'world'; wait for 'hello’ lock held by 2S2: UPDATE table SET value = 2 WHERE key = 'hello'; wait for 'world’ lock held by 1
爲了檢測並解決死鎖的問題,數據庫都會提供定期死鎖檢測機制,如果會話等待鎖持續一段時間,PostgreSQL 依賴 Depedency Graph 技術檢查進程間是否在鎖上形成循環依賴。如果是這種情況,將強制終止其中部分事務,直到死鎖問題解除。
3.2 分佈式死鎖檢測
上面的例子,如果發生在 Citus 分佈式數據庫場景下,則會變成這樣:
-- 在Worker A節點執行:
S1: UPDATE table_123 SET value = 5 WHERE key = 'hello';
S2: UPDATE table_123 SET value = 6 WHERE key = 'hello'; waits for 'hello’ lock held by 1
-- 在Worker B節點執行:
S1: UPDATE table_234 SET value = 6 WHERE key = 'world';
S2: UPDATE table_234 SET value = 5 WHERE key = 'world'; waits for 'world’ lock held by 2
如上圖所示,從每個 Worker 節點來看,都不知道已經發生死鎖情況,但在集羣層面,Session1 和 Session2 處於相互阻塞等待狀態,且不能自行解除,已經發生分佈式死鎖現象。
爲了解決分佈式死鎖問題,Citus 在 Coordiantor 節點運行分佈式死鎖檢測後臺任務來負責分佈式死鎖檢測:Coordinator 定期檢測各個 Worker 節點是否存在長時間(1s)等鎖的現象,如果存在,則從所有 Worker 節點蒐集鎖表信息,並在 Coordinator 節點構建事務間的 Dependency Graph,檢測是否存在相互依賴並形成死鎖,如果存在,則按照策略取消部分事務來解鎖。
3.3 如何避免死鎖發生
一旦死鎖現象發生,不只是相應的事務無法推進執行,還會因爲持有鎖進一步阻塞更多的事務執行,進而產生類似交通擁塞的現象,嚴重影響數據庫的性能。因此,除了死鎖檢測技術外,還會結合降低死鎖概率甚至避免死鎖的技術,以獲得更好的性能。
3.3.1 Predicate Locks 謂詞鎖
針對 Update 語句的謂詞進行加鎖,這樣可以避免可能會產生死鎖的事務並行執行,例如:
UPDATE ... WHERE key = `hello`
針對key='hello'
加謂詞鎖,可以避免其他按照同樣謂詞條件(key='hello'
)的 UPDATE 事務並行執行,也就避免了可能產生的死鎖現象。
在 Coordinator 節點加謂詞鎖,同樣還可以起到死鎖檢測的效果,在多個 Worker 節點形成分佈式死鎖現象前,Coordiantor 節點會先在謂詞鎖上形成死鎖並被提前檢測出來。
Spanner/F1 系統就採用了謂詞鎖技術來降低死鎖現象的發生。
3.3.2 Wait-Die or Wound-Wait
給事務定義優先級的思路,可以認爲先發起的事務(較小的事務 ID)擁有較高的優先級,後發起的事務(較大的事務 ID)擁有較低的優先級。當事務執行過程中,發生了先發起的事務被後發起的事務阻塞現象稱爲優先級反轉,可以採用兩種策略中的一種主動解鎖:
-
Wait-Die:取消並重啓先發起的優先級更高的事務;
-
Wound-Wait:取消並重啓後發起的優先級更低的事務;
一般來說,先發起的事務會更早的獲取鎖,因此發生優先級反轉的概率不高,Wound-Wait 策略的效率會更好些。
Spanner/F1 系統採用的是 Wound-Wait 策略。
參考
https://dzone.com/articles/how-citus-executes-distributed-transactions-on-pos
https://www.citusdata.com/blog/2017/08/31/databases-and-distributed-deadlocks-a-faq/
https://zhuanlan.zhihu.com/p/143504602
http://matt33.com/2018/07/08/distribute-system-consistency-protocol/
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/Qan-EMdtWxacxLv2Pfne0Q