- 鳳凰架構(gòu):構(gòu)建可靠的大型分布式系統(tǒng)
- 周志明
- 3016字
- 2021-06-24 11:30:54
3.1.1 實(shí)現(xiàn)原子性和持久性
原子性和持久性在事務(wù)里是密切相關(guān)的兩個(gè)屬性:原子性保證了事務(wù)的多個(gè)操作要么都生效要么都不生效,不會(huì)存在中間狀態(tài);持久性保證了一旦事務(wù)生效,就不會(huì)再因?yàn)槿魏卧蚨鴮?dǎo)致其修改的內(nèi)容被撤銷或丟失。
眾所周知,數(shù)據(jù)必須要成功寫(xiě)入磁盤(pán)、磁帶等持久化存儲(chǔ)器后才能擁有持久性,只存儲(chǔ)在內(nèi)存中的數(shù)據(jù),一旦遇到應(yīng)用程序忽然崩潰,或者數(shù)據(jù)庫(kù)、操作系統(tǒng)一側(cè)崩潰,甚至是機(jī)器突然斷電宕機(jī)等情況就會(huì)丟失,后文我們將這些意外情況都統(tǒng)稱為“崩潰”(Crash)。實(shí)現(xiàn)原子性和持久性的最大困難是“寫(xiě)入磁盤(pán)”這個(gè)操作并不是原子的,不僅有“寫(xiě)入”與“未寫(xiě)入”狀態(tài),還客觀存在著“正在寫(xiě)”的中間狀態(tài)。由于寫(xiě)入中間狀態(tài)與崩潰都不可能消除,所以如果不做額外保障措施的話,將內(nèi)存中的數(shù)據(jù)寫(xiě)入磁盤(pán),并不能保證原子性與持久性。下面通過(guò)具體事例來(lái)說(shuō)明。
按照前面預(yù)設(shè)的場(chǎng)景事例,從Fenix’s Bookstore購(gòu)買一本書(shū)需要修改三個(gè)數(shù)據(jù):在用戶賬戶中減去貨款、在商家賬戶中增加貨款、在商品倉(cāng)庫(kù)中標(biāo)記一本書(shū)為配送狀態(tài)。由于寫(xiě)入存在中間狀態(tài),所以可能出現(xiàn)以下情形。
·未提交事務(wù),寫(xiě)入后崩潰:程序還沒(méi)修改完三個(gè)數(shù)據(jù),但數(shù)據(jù)庫(kù)已經(jīng)將其中一個(gè)或兩個(gè)數(shù)據(jù)的變動(dòng)寫(xiě)入磁盤(pán),若此時(shí)出現(xiàn)崩潰,一旦重啟之后,數(shù)據(jù)庫(kù)必須要有辦法得知崩潰前發(fā)生過(guò)一次不完整的購(gòu)物操作,將已經(jīng)修改過(guò)的數(shù)據(jù)從磁盤(pán)中恢復(fù)成沒(méi)有改過(guò)的樣子,以保證原子性。
·已提交事務(wù),寫(xiě)入前崩潰:程序已經(jīng)修改完三個(gè)數(shù)據(jù),但數(shù)據(jù)庫(kù)還未將全部三個(gè)數(shù)據(jù)的變動(dòng)都寫(xiě)入磁盤(pán),若此時(shí)出現(xiàn)崩潰,一旦重啟之后,數(shù)據(jù)庫(kù)必須要有辦法得知崩潰前發(fā)生過(guò)一次完整的購(gòu)物操作,將還沒(méi)來(lái)得及寫(xiě)入磁盤(pán)的那部分?jǐn)?shù)據(jù)重新寫(xiě)入,以保證持久性。
由于寫(xiě)入中間狀態(tài)與崩潰都是無(wú)法避免的,為了保證原子性和持久性,就只能在崩潰后采取恢復(fù)的補(bǔ)救措施,這種數(shù)據(jù)恢復(fù)操作被稱為“崩潰恢復(fù)”(Crash Recovery,也有資料稱作Failure Recovery或Transaction Recovery)。
為了能夠順利地完成崩潰恢復(fù),在磁盤(pán)中寫(xiě)入數(shù)據(jù)就不能像程序修改內(nèi)存中的變量值那樣,直接改變某表某行某列的某個(gè)值,而是必須將修改數(shù)據(jù)這個(gè)操作所需的全部信息,包括修改什么數(shù)據(jù)、數(shù)據(jù)物理上位于哪個(gè)內(nèi)存頁(yè)和磁盤(pán)塊中、從什么值改成什么值,等等,以日志的形式——即以僅進(jìn)行順序追加的文件寫(xiě)入的形式(這是最高效的寫(xiě)入方式)先記錄到磁盤(pán)中。只有在日志記錄全部安全落盤(pán),數(shù)據(jù)庫(kù)在日志中看到代表事務(wù)成功提交的“提交記錄”(Commit Record)后,才會(huì)根據(jù)日志上的信息對(duì)真正的數(shù)據(jù)進(jìn)行修改,修改完成后,再在日志中加入一條“結(jié)束記錄”(End Record)表示事務(wù)已完成持久化,這種事務(wù)實(shí)現(xiàn)方法被稱為“提交日志”(Commit Logging)。
額外知識(shí)
Shadow Paging
通過(guò)日志實(shí)現(xiàn)事務(wù)的原子性和持久性是當(dāng)今的主流方案,但并不是唯一的選擇。除日志外,還有另外一種稱為“Shadow Paging”(有中文資料翻譯為“影子分頁(yè)”)的事務(wù)實(shí)現(xiàn)機(jī)制,常用的輕量級(jí)數(shù)據(jù)庫(kù)SQLite Version 3采用的事務(wù)機(jī)制就是Shadow Paging。
Shadow Paging的大體思路是對(duì)數(shù)據(jù)的變動(dòng)會(huì)寫(xiě)到硬盤(pán)的數(shù)據(jù)中,但不是直接就地修改原先的數(shù)據(jù),而是先復(fù)制一份副本,保留原數(shù)據(jù),修改副本數(shù)據(jù)。在事務(wù)處理過(guò)程中,被修改的數(shù)據(jù)會(huì)同時(shí)存在兩份,一份是修改前的數(shù)據(jù),一份是修改后的數(shù)據(jù),這也是“影子”(Shadow)這個(gè)名字的由來(lái)。當(dāng)事務(wù)成功提交,所有數(shù)據(jù)的修改都成功持久化之后,最后一步是修改數(shù)據(jù)的引用指針,將引用從原數(shù)據(jù)改為新復(fù)制并修改后的副本,最后的“修改指針”這個(gè)操作將被認(rèn)為是原子操作,現(xiàn)代磁盤(pán)的寫(xiě)操作的作用可以認(rèn)為是保證了在硬件上不會(huì)出現(xiàn)“改了半個(gè)值”的現(xiàn)象。所以Shadow Paging也可以保證原子性和持久性。Shadow Paging實(shí)現(xiàn)事務(wù)要比Commit Logging更加簡(jiǎn)單,但涉及隔離性與并發(fā)鎖時(shí),Shadow Paging實(shí)現(xiàn)的事務(wù)并發(fā)能力就相對(duì)有限,因此在高性能的數(shù)據(jù)庫(kù)中應(yīng)用不多。
Commit Logging保障數(shù)據(jù)持久性、原子性的原理并不難理解:首先,日志一旦成功寫(xiě)入Commit Record,那整個(gè)事務(wù)就是成功的,即使真正修改數(shù)據(jù)時(shí)崩潰了,重啟后根據(jù)已經(jīng)寫(xiě)入磁盤(pán)的日志信息恢復(fù)現(xiàn)場(chǎng)、繼續(xù)修改數(shù)據(jù)即可,這保證了持久性;其次,如果日志沒(méi)有成功寫(xiě)入Commit Record就發(fā)生崩潰,那整個(gè)事務(wù)就是失敗的,系統(tǒng)重啟后會(huì)看到一部分沒(méi)有Commit Record的日志,將這部分日志標(biāo)記為回滾狀態(tài)即可,整個(gè)事務(wù)就像完全沒(méi)有發(fā)生過(guò)一樣,這保證了原子性。
Commit Logging的原理很清晰,也確實(shí)有一些數(shù)據(jù)庫(kù)就是直接采用Commit Logging機(jī)制來(lái)實(shí)現(xiàn)事務(wù)的,譬如較具代表性的是阿里的OceanBase。但是,Commit Logging存在一個(gè)巨大的先天缺陷:所有對(duì)數(shù)據(jù)的真實(shí)修改都必須發(fā)生在事務(wù)提交以后,即日志寫(xiě)入了Commit Record之后。在此之前,即使磁盤(pán)I/O有足夠空閑,即使某個(gè)事務(wù)修改的數(shù)據(jù)量非常龐大,占用了大量的內(nèi)存緩沖區(qū),無(wú)論何種理由,都決不允許在事務(wù)提交之前就修改磁盤(pán)上的數(shù)據(jù),這一點(diǎn)是Commit Logging成立的前提,卻對(duì)提升數(shù)據(jù)庫(kù)的性能十分不利。為此,ARIES提出了“提前寫(xiě)入日志”(Write-Ahead Logging)的日志改進(jìn)方案,所謂“提前寫(xiě)入”(Write-Ahead),就是允許在事務(wù)提交之前寫(xiě)入變動(dòng)數(shù)據(jù)的意思。
Write-Ahead Logging按照事務(wù)提交時(shí)點(diǎn),將何時(shí)寫(xiě)入變動(dòng)數(shù)據(jù)劃分為FORCE和STEAL兩類情況。
·FORCE:當(dāng)事務(wù)提交后,要求變動(dòng)數(shù)據(jù)必須同時(shí)完成寫(xiě)入則稱為FORCE,如果不強(qiáng)制變動(dòng)數(shù)據(jù)必須同時(shí)完成寫(xiě)入則稱為NO-FORCE。現(xiàn)實(shí)中絕大多數(shù)數(shù)據(jù)庫(kù)采用的都是NO-FORCE策略,因?yàn)橹灰辛巳罩荆儎?dòng)數(shù)據(jù)隨時(shí)可以持久化,從優(yōu)化磁盤(pán)I/O性能考慮,沒(méi)有必要強(qiáng)制數(shù)據(jù)寫(xiě)入時(shí)立即進(jìn)行。
·STEAL:在事務(wù)提交前,允許變動(dòng)數(shù)據(jù)提前寫(xiě)入則稱為STEAL,不允許則稱為NO-STEAL。從優(yōu)化磁盤(pán)I/O性能考慮,允許數(shù)據(jù)提前寫(xiě)入,有利于利用空閑I/O資源,也有利于節(jié)省數(shù)據(jù)庫(kù)緩存區(qū)的內(nèi)存。
Commit Logging允許NO-FORCE,但不允許STEAL。因?yàn)榧偃缡聞?wù)提交前就有部分變動(dòng)數(shù)據(jù)寫(xiě)入磁盤(pán),那一旦事務(wù)要回滾,或者發(fā)生了崩潰,這些提前寫(xiě)入的變動(dòng)數(shù)據(jù)就都成了錯(cuò)誤。
Write-Ahead Logging允許NO-FORCE,也允許STEAL,它給出的解決辦法是增加了另一種被稱為Undo Log的日志類型,當(dāng)變動(dòng)數(shù)據(jù)寫(xiě)入磁盤(pán)前,必須先記錄Undo Log,注明修改了哪個(gè)位置的數(shù)據(jù)、從什么值改成什么值等,以便在事務(wù)回滾或者崩潰恢復(fù)時(shí)根據(jù)Undo Log對(duì)提前寫(xiě)入的數(shù)據(jù)變動(dòng)進(jìn)行擦除。Undo Log現(xiàn)在一般被翻譯為“回滾日志”,此前記錄的用于崩潰恢復(fù)時(shí)重演數(shù)據(jù)變動(dòng)的日志就相應(yīng)被命名為Redo Log,一般翻譯為“重做日志”。由于Undo Log的加入,Write-Ahead Logging在崩潰恢復(fù)時(shí)會(huì)經(jīng)歷以下三個(gè)階段。
·分析階段(Analysis):該階段從最后一次檢查點(diǎn)(Checkpoint,可理解為在這個(gè)點(diǎn)之前所有應(yīng)該持久化的變動(dòng)都已安全落盤(pán))開(kāi)始掃描日志,找出所有沒(méi)有End Record的事務(wù),組成待恢復(fù)的事務(wù)集合,這個(gè)集合至少會(huì)包括事務(wù)表(Transaction Table)和臟頁(yè)表(Dirty Page Table)兩個(gè)組成部分。
·重做階段(Redo):該階段依據(jù)分析階段中產(chǎn)生的待恢復(fù)的事務(wù)集合來(lái)重演歷史(Repeat History),具體操作是找出所有包含Commit Record的日志,將這些日志修改的數(shù)據(jù)寫(xiě)入磁盤(pán),寫(xiě)入完成后在日志中增加一條End Record,然后移出待恢復(fù)事務(wù)集合。
·回滾階段(Undo):該階段處理經(jīng)過(guò)分析、重做階段后剩余的恢復(fù)事務(wù)集合,此時(shí)剩下的都是需要回滾的事務(wù),它們被稱為L(zhǎng)oser,根據(jù)Undo Log中的信息,將已經(jīng)提前寫(xiě)入磁盤(pán)的信息重新改寫(xiě)回去,以達(dá)到回滾這些Loser事務(wù)的目的。
重做階段和回滾階段的操作都應(yīng)該設(shè)計(jì)為冪等的。為了追求高I/O性能,以上三個(gè)階段無(wú)可避免地會(huì)涉及非常煩瑣的概念和細(xì)節(jié)(如Redo Log、Undo Log的具體數(shù)據(jù)結(jié)構(gòu)等),囿于篇幅限制,筆者并不打算具體介紹這些內(nèi)容,感興趣的讀者可以閱讀本節(jié)開(kāi)頭引用的那兩篇論文進(jìn)行了解。Write-Ahead Logging是ARIES理論的一部分,整套ARIES擁有嚴(yán)謹(jǐn)、高性能等諸多優(yōu)點(diǎn),但這些也是以高度復(fù)雜為代價(jià)的。數(shù)據(jù)庫(kù)按照是否允許FORCE和STEAL可以產(chǎn)生四種組合,從優(yōu)化磁盤(pán)I/O的角度看,NO-FORCE加STEAL的組合的性能無(wú)疑是最高的;從算法實(shí)現(xiàn)與日志的角度看,NO-FORCE加STEAL的組合的復(fù)雜度無(wú)疑也是最高的。這四種組合與Undo Log、Redo Log之間的具體關(guān)系如圖3-1所示。

圖3-1 FORCE和STEAL的四種組合關(guān)系
- 電腦組裝與系統(tǒng)安裝
- Windows Server 2012 Hyper-V:Deploying the Hyper-V Enterprise Server Virtualization Platform
- Windows Vista基礎(chǔ)與應(yīng)用精品教程
- Mobile-first Bootstrap
- 嵌入式操作系統(tǒng)(Linux篇)(微課版)
- 混沌工程實(shí)戰(zhàn):手把手教你實(shí)現(xiàn)系統(tǒng)穩(wěn)定性
- 蘋(píng)果OS X Mavericks 10.9應(yīng)用大全
- Learning Magento 2 Administration
- INSTANT Migration from Windows Server 2008 and 2008 R2 to 2012 How-to
- Fedora 12 Linux應(yīng)用基礎(chǔ)
- iOS 8開(kāi)發(fā)指南
- Django Project Blueprints
- 寫(xiě)給架構(gòu)師的Linux實(shí)踐:設(shè)計(jì)并實(shí)現(xiàn)基于Linux的IT解決方案
- 分布式系統(tǒng)設(shè)計(jì)實(shí)踐
- CentOS 6 Linux Server Cookbook