1、事務實現原理和 WAL(單機)
屬性 | 含義 | 數據庫系統實現 |
---|---|---|
Atomic(原子性) | 事務中的操作要么全部正確執行,要么完全不執行(要么成功、要么失敗) | Write Ahead Logging 預寫日志,分布式事務:兩階段提交 |
Consistency(一致性) | 數據庫系統必須保證事務的執行使得數據庫從一個一致性狀態轉移到另一個一致性狀態。(滿足完整性約束) | 只要實現了 A、I、D,一致性也就得到了保證? |
Isolation(隔離性) | 多個事務并發執行,并每個事務來說,它并不會感知系統中有其他事務在同時執行 | 多版本并發控制(Multi-Version Concurrency Control)、兩階段鎖(Two Phase Commit,2PC)、樂觀并發控制(OCC) |
Durablity(持久性) | 一個事務被提交后,該事務對數據庫的改變是持久的 | WAL + 存儲管理 |
DBMS 組成
- ?索引/文件/記錄管理器也叫做資源管理器
- 緩沖區包括 數據頁的 buffer pool 以及 log 文件的 buffer
- 事務的實現需要多個組件協同工作,事務管理器負責協調、調度、跟蹤事務的各個執行階段和狀態,還需要通過資源管理器以及日志和恢復模塊保證事務的原子性和持久性兩個屬性
- 并發控制模塊通過鎖管理器等模塊來保證事務的隔離性
存儲介質的類型
接下來我們需要了解一下緩沖區,在了解緩沖區之前需要了解一下數據庫的存儲介質:
包括三大類:
- 易失性存儲器
- CPU寄存器、Cache、主存等(斷電即失)
- 非易失性存儲器
- Disk、SD、NVM(斷電依然存在)
- 穩定存儲器
緩沖區 Buffer Pool
實際上,數據文件是以 page、block 組成的,每個 page 通常是 32K、64K,數據庫啟動之后會給 Buffer Pool 開啟一個特別大的內存空間。我們操作數據的時候其實是把磁盤中的 page 讀取到 buffer 中進行操作,完成操作后,再從 buffer 寫回到磁盤。這一系列操作可以劃分為 4 個步驟:
- input(page):從磁盤把 page 讀進 buffer pool
- output(page):把 page 更新后刷會磁盤
- pin(page):不允許 page 刷回到磁盤里,防止在并發操作中,一個 page 被事務 pin 住的時候,其它的事務是不應該把這個 page 刷回到磁盤里的
- unpin(page):
?Buffer Pool 管理策略
從兩個維度來看
- Force / No-Force
- Force:事務提交時,所修改的 page 必須強制刷回到持久存儲中
- No-Force:事務提交時,所修改的 page 不需要強制刷回到持久存儲中
Force 策略的問題:只要事務提交了,就需要把 buffer pool 里的臟頁刷回到磁盤,對持久存儲器進行頻繁的隨機寫操作,性能下降。
- Steal / No-Steal
- Steal:允許 Buffer Pool 里未提交事務所修改的臟頁刷回到持久存儲
- No-Steal:不允許 Buffer Pool 里未提交事務所修改的臟頁刷回到持久存儲
No-Steal 策略的問題:不允許未提交事務的臟頁換出,系統的并發量不高。假如一些事務幾乎把整個 buffer pool 里的 page 全都占滿了,但是一直沒有提交,導致別的事務想空閑 page 去取數據是取不出來的。
所以 Force 和 No-steal 對于面向磁盤的數據庫來說是基本不可用的。所以一般我們都會使用 No-Force 和 Steal 這種組合方式來完成數據庫緩沖區的管理。
但是雖然 No-Force / Steal 有很好的性能,但是怎么保證事務的原子性和持久性呢?
- No-Force:事務提交,所修改的數據頁沒有刷回至持久存儲,如果發生斷電或系統崩潰(違背了事務的原子性和持久性)
- Steal:Buffer Pool 中未提交的事務所修改的臟頁刷回到持久存儲,如果發生斷電或者系統崩潰(事務還沒有提交呢,違背了事務的原子性)
所以說雖然 No-Force / Steal 有很好的性能,但是不能保證事務的原子性和持久性,那么數據庫是怎樣解決的呢?
這就引入了日志這種解決方案,來保證事務的原子性和持久性
Logging
- No-force -> Redo Log:
- 事務提交時,數據頁不需要刷回到持久存儲,為了保證持久性,先把 Redo Log 寫入日志文件。Redo Log 記錄修改數據對象的新值(After Image,AFIM)
- Steal -> Undo Log:
- 允許 Buffer Pool 未提交事務所修改的臟頁刷回到持久存儲,為了保證原子性,先把 Undo Log 寫入日志文件。Undo Log 記錄了修改對象的舊值(Before Image,BFIM)
緩沖區管理策略和事務恢復的關系
對于右上角的?No-Force 和 No-steal 組合來說,性能是最好的,但是恢復是最差的,因為它既要做 Redo Log 又要做 Undo Log。相反地,對于左下角的 Force 和 No-Steal 來說,性能是最差的,但是恢復是最快的。
所以不同的 Buffer Pool 管理策略和更新方式決定了數據庫的恢復策略。
Buffer Pool 和 Log Pool
通過日志這種機制,可以把對數據庫文件的隨機的寫操作,變成了順序的寫操作,因為對日志的操作是append的方式進行操作的,buffer 滿了或者需要commit 事務的時候才把 log buffer 刷回到磁盤,這樣就極大地提高了數據庫的性能。
Write Ahead Logging
第一點,任何被修改的 page 在刷回到磁盤之間,必須保證 log 先寫入磁盤
第二,確保事務對數據修改的 log 寫入到磁盤之后,事務才能提交
2、PostgreSQL 和 Greenplum 采用的策略
Steal + No-Force 對于一個硬盤數據庫是最好的,這也是PostgreSQL 和 Greenplum 采用的策略
這就有了一個問題,PG?里只有 redo log,沒有 undo log,事務回滾的時候不需要 undo 操作
這是因為 PG 采用的是 MVCC ,它的更新操作不是 in-place update ,而是重新創建 tuple,所以已經有了 tuple 的舊值,就不需要再去通過 undo log 去記錄這些舊值了。
- MySQL 同樣采用 MVCC 模式的數據去進行并發控制,但為什么 MySQL 事務恢復的時候就需要 undo log?
版本存儲(Version Storge)
可以看到,對于 PGSQL 來說,當對數據修改時,會直接在原表上追加數據,讓被修改的數據通過指針指向新的數據(tuple)上。
而對于 MySQL/Oracle 來說,雖然也采用 MVCC ,但是它們的 Version Storage 采用的是另一種實現方式。也就是把數據的差異變化記到一個 delta storge 中,形成一個鏈表,也叫做回滾段(rollback segment)。
2 PC
這里 2PC 和下面的 ZAB協議參考自這里?
2PC,是Two-Phase Commit的縮寫,即二階段提交,是計算機網絡尤其是在數據庫領域內,為了使基于分布式系統架構下的所有節點在進行事務處理過程中能夠保持原子性和一致性而設計的一種算法。通常,二階段提交協議也被認為是一種一致性協議,用來保證分布式系統數據的一致性。目前,絕大部分的關系型數據庫都是采用二階段提交協議來完成分布式事務處理的,利用該協議能夠非常方便地完成所有分布式事務參與者的協調,統一決定事務的提交或回滾,從而能夠有效地保證分布式數據一致性,因此二階段提交協議被廣泛地應用在許多分布式系統中。
一階段:提交事務請求
1、事務詢問
協調者向所有的參與者發送事務內容,詢問是否可以執行事務提交操作,并開始等待各參與者的響應。
2、執行事務
各參與者節點執行事務操作,并將Undo和Redo信息記入事務日志中。
3、參與者向協調者反饋
如果各參與者成功執行了事務操作,那么就反饋給協調者Yes響應,表示事務可以執行;如果參與者沒有成功執行事務,那么就反饋給協調者No響應,表示事務不可以執行。
二階段:執行事務提交
事務提交
協調者接收到所有參與者的ACK消息都是YES,執行事務提交
1、發送提交申請
協調者給參與者發出Commit請求
2、事務提交
參與者收到Commit請求后,執行事務提交,完成后釋放整個事務執行期間占用的事務資源
3、反饋結果
參與者在完成事務提交后,給協調者發送ACK消息
4、事務完成
協調者接收到所有參與者反饋的ACK消息后,事務完成
事務中斷
任何一個參與者反饋了NO,或者等待超時了導致協調者沒有接收到所有參與者的反饋就會中斷事務
1、發送回滾請求
協調者向所有參與者發送Rollback請求
2、事務回滾
參與者接收到Rollback請求后,會根據一階段中的Undo日志進行事務回滾,
3、事務回滾結果反饋
參與者在完成回滾后,向協調者發送ACK消息
4、中斷事務
協調者接收到所有參與者反饋的ACK消息后完成事務中斷
ZAB 協議
Zookeeper 是通過 Zab 協議來保證分布式事務的最終一致性
ZAB又名原子廣播協議(Zookeeper Atomic Broadcast ) 作用在可用狀態,有Leader時
原子:要么成功,要么失敗,沒有中間狀態(FIFO隊列+類似2PC操作)
廣播:分布式多節點的,所以執行操作都是由Leader(協調者)向所有Follower(參與者)統一發送請求
PS:
ZK的數據狀態存儲在內存
ZK是日志存儲在磁盤
- 第一步:在ZK客戶端對任意一個Follower節點執行一個寫操作create /rhys "aaa"
- 第二步:Follower節點將這筆寫操作轉發給Leader節點
- 第三步:Leader會創建一個事務ID(zxid),假設本次給出的事務ID為1
- 第四步:其實在Leader對于每個Follower都維護著一個發送隊列(FIFO隊列),緊接著Leader會給兩臺Follower發起關于創建XXX節點這件事的第一階段操作寫日志,那么這個寫日志操作就會先入發送隊列。再順序執行隊列中操作,當寫日志操作執行成功后,Follower會返回一個ok/yes的狀態,那對應的Leader中也會生成一個ok/yes的狀態,由于我們是一主兩從,那有了兩臺機返回了ok狀態,滿足了過半通過條件 (3/2+1),這時Leader會再次對兩臺Follower發起第二階段write寫內存操作,其實就是類似兩階段提交(2PC),只是這里的兩階段提交和開始回顧的兩階段提交不一樣的地方時沒有中斷事務操作,因為這里的兩階段提交不需要接收到所有Follower(參與者)的ACK反饋,只需要超過一半的機器ACK就可以了,依然是入發送隊列,然后從隊列中順序執行操作,操作完成同樣的會返回一個ok/yes狀態,達到過半條件則Leader會給Follower返回一個over-ok狀態,再由Follower傳遞給客戶端
這邊有一點需要提一下,我們剛提到過半提交這個概念對吧,那另一臺Follower機器沒有返回ok狀態,對應的發送隊列依舊會放入一個write操作,只要最終那臺沒有返回ok的Follower機器能把隊列中操作消費完,那這個節點的數據最終還是會跟其他兩個節點保持一致的,這邊就體現出了最終一致性。
總結:回過頭再看ZAB的原子沒有中間狀態其實就是依據FIFO隊列+類似2PC操作,廣播其實就是體現了過半通過的概念
ZAB協議(Zookeeper Atomic Broadcast)是Zookeeper分布式協調服務中用于保證數據一致性的核心協議。它之所以被描述為“沒有中間狀態(2PC+FIFO),只有成功和失敗”,這主要源于其設計原理和實現機制。以下是對這一說法的詳細解釋:
1. 類似2PC但移除了中斷邏輯
**二階段提交(2PC, Two-Phase Commit)**協議通常包含兩個階段:準備階段(Prepare)和提交階段(Commit)。在準備階段,協調者會詢問參與者是否可以執行事務,參與者如果同意則進行預提交并鎖定資源;在提交階段,如果所有參與者都同意提交,則協調者會發送提交命令,否則發送回滾命令。然而,2PC協議存在事務中斷的風險,即任何一個參與者反饋了NO或等待超時,都會導致事務中斷。
ZAB協議的廣播模式則類似于一個移除了中斷邏輯的2PC協議。在ZAB中,Leader(協調者)在收到寫請求后,會為其分配一個ZXID(事務ID)并生成提案發送給所有Follower(參與者)。Follower在接收到提案后,首先將其寫入本地日志但不提交,成功寫入后返回ACK給Leader。當Leader收到過半Follower的ACK響應后,會發出commit請求執行提交。這里的關鍵是,ZAB協議移除了中斷邏輯,即使有部分Follower因為網絡延遲或故障未能及時響應,也不會導致整個事務中斷。只要過半的Follower成功響應,事務就會被認為成功,剩余的Follower則會在后續的數據同步階段與集群達成一致。
2. FIFO隊列保證順序性
ZAB協議通過為每個Follower維護一個FIFO(先進先出)隊列來保證事務的順序性。Leader會將需要廣播的提案依次放入到每個Follower的隊列中,并按照隊列中的順序執行操作。這種機制確保了即使在網絡延遲或故障的情況下,Follower最終也能按照正確的順序執行事務,從而實現最終一致性。
3. 成功和失敗狀態
由于ZAB協議移除了中斷邏輯,并采用了FIFO隊列來保證順序性,因此事務的執行結果只有兩種狀態:成功或失敗。成功狀態意味著事務已經被過半的Follower成功執行并提交;失敗狀態則通常發生在Leader選舉失敗或無法與過半的Follower通信時,此時整個集群會進入恢復模式,直到選舉出新的Leader并完成數據同步。
綜上所述,ZAB協議通過類似但移除了中斷邏輯的2PC協議和FIFO隊列機制,實現了事務的原子性和順序性,同時保證了事務的執行結果只有成功和失敗兩種狀態。這種設計使得Zookeeper能夠在分布式環境下提供高可靠性和高性能的數據一致性服務。
?3、分布式事務和兩階段提交的原理
一階段提交協議
?分布式事務的原子性要求事務中的操作要么全部成功、要么全部失敗。上面的一階段提交明顯不能保證。
兩階段提交協議
在兩階段提交中,任意參與者如果回復 no,則該事務不能被提交。
兩階段提交與日志操作
兩階段提交協議可能會產生阻塞:
1、資源鎖定(參與者在 prepare 之后,在抽到 commit 之前故障了):
在準備階段(Prepare phase),參與者需要執行事務但不提交,同時保留對事務的修改。這意味著在參與者投票Prepared之后,在接收到Commit之前,資源會處于被鎖狀態。如果因為網絡中斷、協調者故障等原因導致長時間無法收到Commit或Abort指令,這些資源將一直被鎖定,無法被其他事務使用,從而導致系統性能下降。
關于這一點,PGSQL 有自己的恢復機制(下面寫了)。
2、參與者阻塞:
在參與者投票Prepared后,如果協調者因為某種原因(如故障)無法發送Commit或Abort指令,參與者將處于阻塞狀態,無法繼續執行其他操作。這種情況在協調者發起COMMIT之后尤為嚴重,因為所有參與者都在等待協調者的最終指令,而協調者的故障將導致所有參與者都無法完成事務。
兩階段提交協議需要處理的故障
4、Greenplum 兩階段提交協議的實現
Greenplum 是基于 PGSQL ,所以我們先看一下 PGSQL 的兩階段提交:
所以,PGSQL 是通過 prepare transaction、commit prepared 和 rollback prepared 三個語句完成對分布式事務兩階段提交協議的支持。
Greenplum 實現分布式事務與并發控制
Greenplum 的兩階段提交函數調用關系
5、Greenplum 兩階段提交協議的優化
一階段提交
當參與者只有一個時,參與者自己就決定了事務提交是否成功,所以可以簡化兩階段提交為一階段提交:
這里的只讀事務指的就是查詢數據這種操作,在數據庫中不是說只有修改數據的操作才能被叫做事務,讀取操作也是事務。?