為大家介紹一下所謂的原子寫(xiě),Atomic Write。
一、從文件系統刪除文件說(shuō)起
文件刪除操作過(guò)程比較復雜,如果簡(jiǎn)化的來(lái)講,可以分為兩步:
1. 刪除該文件在文件記錄表中的條目。
2. 將該文件之前所占據的空間對應的塊在空間追蹤bitmap中將對應的bit置0。
假設該文件的文件名非常短,尺寸也非常小,只有不到4KB,那么,上述這兩個(gè)動(dòng)作,就可以分別只對應一個(gè)4K的IO(如果文件系統格式化時(shí)選擇4K的分塊大小的話(huà)),第一個(gè)4K將更新后的記錄表覆蓋到硬盤(pán)對應的區域,第二個(gè)IO將更新后的bitmap的這4K部分覆蓋下去。僅當這兩個(gè)IO都結束時(shí),該文件才會(huì )徹底被刪除。
該是問(wèn)“如果”和“為什么”的時(shí)候了。如果,文件系統將更新記錄表這個(gè)IO發(fā)到了硬盤(pán)上并且成功寫(xiě)入,而更新bitmap的IO沒(méi)有發(fā)出、或者發(fā)出了但是正在去往硬盤(pán)的路上的某處,此時(shí)系統突然斷電,那會(huì )有什么結果?
放在早期的文件系統,再次重啟系統之后,會(huì )進(jìn)入FSCK(文件系統一致性檢查及修復)階段,也就是WinXP那個(gè)經(jīng)典的藍底黃滾動(dòng)條界面。因為文件系統會(huì )維護一個(gè)dirty/clean位,在做任何變更操作之后,只要操作完成,該位就被置為clean,那么下次重啟就不會(huì )進(jìn)入FSCK過(guò)程,而我們上述的例子中,這兩筆IO是一組不可分割的“事務(wù)(Transaction)”,一筆事務(wù)中的所有IO要么都被執行,要么干脆別被執行,結果就是這文件要么完全被刪除,要么就不被刪除還在那,大不了再刪除一次。但是,如果一筆事務(wù)中的某個(gè)/些IO完成,另一些沒(méi)完成,比如,記錄表中已經(jīng)看不到這個(gè)文件,但是空間占用追蹤bitmap中卻還記錄著(zhù)該文件之前被占用的空間的話(huà),那么表象上就會(huì )看到這樣的情況:雙擊我的電腦進(jìn)去某個(gè)目錄,看不到對應的文件,而右鍵點(diǎn)擊硬盤(pán)屬性,卻發(fā)現該文件占用的空間并沒(méi)有被清掉。這就產(chǎn)生了不一致。所以,FSCK此時(shí)需要介入,重新掃描全部的記錄表,與bitmap中每個(gè)塊占用與否重新匹配,最后便會(huì )將bitmap中應該被回收卻沒(méi)有來(lái)得及回收的bit重新回收回來(lái)。
所謂原子寫(xiě),就是指一筆不可分隔開(kāi)的事務(wù)中的所有寫(xiě)IO必須一起結束或者一起回退,就像原子作為化學(xué)變化中不可分割的最小單位一樣。
二、單筆寫(xiě)IO會(huì )不會(huì )被原子寫(xiě)?
上面的場(chǎng)景指出,一筆事務(wù)中的多筆IO可能不會(huì )被原子寫(xiě),那么單筆IO總能被原子寫(xiě)了吧?很不幸,也無(wú)法被原子寫(xiě)。原因和場(chǎng)景有下面三個(gè):
2.1 上層一筆IO被分解成多筆IO
上層發(fā)出的一筆IO可能會(huì )被下層模塊分解為多筆IO,這多筆IO執行之間如果斷電,無(wú)法保證原子性。有多種情況可以導致一筆IO被分解,比如:
A. IO size大于底層設備或者IO通道控制器可接受的最大IO size時(shí),此時(shí)會(huì )由Device Driver將IO分解之后再發(fā)送給Host Driver。
B. 做了Raid,條帶深度小于該IO的size,那么raid層會(huì )將該IO分解成多個(gè)IO。
2.2 外部IO控制器不會(huì )主動(dòng)原子寫(xiě)
那么,當一筆IO(分解之后的或者未分解的,無(wú)所謂)請求到了底層,由Host Driver發(fā)送給外部IO控制器硬件的時(shí)候,外部IO控制器總可以實(shí)現原子寫(xiě)了吧?IO控制器硬件總不可能只把這筆IO的一部分發(fā)給硬盤(pán)執行吧?很不幸,IO控制器的確就是這樣做的。比如,假設某筆寫(xiě)IO為32KB大小,IO控制器并不是從主存將這32KB數據都取到控制器內緩沖區才開(kāi)始向后端硬盤(pán)發(fā)起IO,而是根據后端SAS鏈路控制器前端的buffer空閑情況,來(lái)決定從Host主存DMA多少數據進(jìn)去,數據一旦進(jìn)入該buffer,那么后端SAS鏈路控制器就會(huì )將其封裝為SAS幀寫(xiě)到后端硬盤(pán)上。這個(gè)buffer一般只有幾KB大小。所以很有可能一筆主機端的32KB的IO,在斷電之前,有部分已經(jīng)寫(xiě)入硬盤(pán)了,而剩余的部分則未被寫(xiě)入。雖然主機端的協(xié)議棧、應用都沒(méi)有收到這筆IO的完成應答,但是硬盤(pán)上的數據已經(jīng)被撕裂了,一半是舊的,一半是新的。
(Adaptec的Raid控制器一般會(huì )將整個(gè)IO取回到板載DDR RAM,然后將對應的RAM pages設為dirty,然后返回給host寫(xiě)應答(向competition queue中入隊一個(gè)io完成描述結構體)。也就是說(shuō),Adaptec的Raid卡是可以保證單IO原子寫(xiě)的,但有個(gè)前提是Cache未滿(mǎn),當Cache滿(mǎn)或者某種原因被disable比如電容故障等的時(shí)候,就無(wú)法實(shí)現原子寫(xiě)了。至于其他的卡是否保證,冬瓜哥并不清楚。)
2.3 硬盤(pán)也不會(huì )主動(dòng)原子寫(xiě)
硬盤(pán)本身并不會(huì )原子寫(xiě)。硬盤(pán)接收到的數據也是一份一份的,每個(gè)SAS幀是1KB的Payload,SAS HBA會(huì )分多次將一筆IO發(fā)送給硬盤(pán)。至于硬盤(pán)是否會(huì )將這筆IO的所有數據都接收到才往盤(pán)片上寫(xiě)入,冬瓜哥不是硬盤(pán)廠(chǎng)商的研發(fā)所以并不知曉,但是冬瓜哥知道的是,不管硬盤(pán)是攢足了再寫(xiě)還是收到一個(gè)分片就寫(xiě),其內部的磁頭控制電路前端一定也是有一定buffer的,該buffer被充滿(mǎn)就寫(xiě)一次。不管怎么樣,當磁頭在盤(pán)片上劃過(guò)將數據寫(xiě)入盤(pán)片期間,突然斷電之后,盤(pán)片上的數據幾乎一定是一部分新一部分舊的,不一致,甚至一個(gè)扇區內部都有可能被撕裂??v使Host端的確會(huì )認為該IO未完成,但是木已成半舟。
Every Enterprise SCSI drive provides 64k powerfail write-atomicity. We depend upon it and can silently corrupt data without it.
對于PCIE接口的固態(tài)盤(pán),情形也是一樣的。SSD從主存DMA時(shí)一般每次DMA 512Byte,也就是PCIE Payload的普遍尺寸。當攢足了一個(gè)Page的數據時(shí),SSD就開(kāi)始寫(xiě)入Flash了,而并不是等整個(gè)IO數據全部DMA過(guò)來(lái)才寫(xiě)入Flash。但是僅當整個(gè)IO都寫(xiě)入完畢之后,才會(huì )向host端competition queue寫(xiě)入io完成描述結構。如果是打開(kāi)了write back模式的寫(xiě)緩存,那么僅當整個(gè)io數據全部DMA到寫(xiě)緩存中之后才會(huì )返回io完成描述ack,但是掉電之后,不管是完整取回的還是部分取回的,未完成的io會(huì )不會(huì )由固態(tài)盤(pán)固件繼續完成,就取決于固件的實(shí)現了。
三、IO未完成,再來(lái)一遍不就行了么?
有人說(shuō)了,既然Host端知道某筆IO未完成,那么重啟之后,對應的應用完全可以再重新發(fā)送這筆IO吧,重新把之前寫(xiě)了一部分的數據全部再寫(xiě)一遍不就行了么?這個(gè)問(wèn)題很復雜,要分很多場(chǎng)景。
比如,Host未宕機,而是存儲系統突然宕機,或者突然承載存儲IO的網(wǎng)線(xiàn)斷掉。此時(shí)應用程序會(huì )收到IO錯誤,取決于應用程序如何處理,結果可能不同。比如應用程序層可能會(huì )保存有緩沖,在這里實(shí)現原子寫(xiě),比如應用可以在GUI彈出一個(gè)重試按鈕,當外部IO系統恢復之后,用戶(hù)點(diǎn)擊重試之后,應用會(huì )將該原子Transaction涉及的所有IO再次重新執行一遍,此時(shí)便可以覆蓋之前不一致的數據為一致的。而如果外部存儲系統長(cháng)時(shí)間不能恢復,而應用程序也被重啟或者強行關(guān)閉的話(huà),那么該Transaction未完成,而且在硬盤(pán)上留下不一致的數據。當應用再次啟動(dòng)的時(shí)候,取決于應用處理方式的不同,結果也不同。
比如應用完全依靠其操作員來(lái)決定該如何處理,比如如果是數據庫錄入,錄入員上一筆錄入失敗,那么其勢必再次錄入,此時(shí)應用可以將錄入員再次錄入的數據覆蓋之前不一致的數據。但是更多實(shí)際場(chǎng)景未必如此,比如,錄入員可能并不是根本不管其要錄入的記錄之前是什么而直接錄入新數據,而是必須參考之前的數據來(lái)決定新數據,而之前的數據已經(jīng)不完整,或者錄入員并不知道該數據是錯誤的,而在錯誤數據的基礎上計算出了更加錯誤的新數據,從而將更加錯誤的數據更新到硬盤(pán)上,埋了一顆雷,這就是所謂數據的”連環(huán)污染“。
再比如數據庫類(lèi)的程序,其雖然記錄了redo log用于追蹤所有的變更操作,但是一旦某個(gè)數據塊發(fā)生不一致,redo log是無(wú)能為力的。如下圖所示的場(chǎng)景:
可以看到,1時(shí)刻內存中的該數據塊,其CRC與數據是匹配的,而掉電后硬盤(pán)上的狀態(tài),CRC與整個(gè)數據塊是不匹配的。數據庫之類(lèi)對數據一致性要求非常高的程序都會(huì )對每個(gè)數據塊做校驗以防止數據位由于各種原因發(fā)生bit躍變。但是對于上圖最右邊的情況,數據庫程序是無(wú)法判斷該塊到底是發(fā)生了bit翻轉,還是由于底層沒(méi)有原子寫(xiě)而導致的CRC不匹配,校驗錯誤,所以會(huì )認為該塊是一個(gè)壞塊。當然,本例中,我們預先知道該壞塊其實(shí)是由于原子寫(xiě)失敗而導致的,但是程序并不知道。其實(shí),此時(shí)用redo log強行把4321再覆蓋到第一行上,就可以恢復數據,但是數據庫并不敢去這么做,已經(jīng)說(shuō)了,數據庫并不知道該塊是不是由于比如第二行或者第三行里某些數據位發(fā)生反轉而導致的CRC校驗錯誤,所以不能直接把4321再寫(xiě)一遍到第一行上就認為該塊被恢復了,為了驗證該壞塊是否是由于4321未被原子寫(xiě)入所導致,數據庫可以先讀入該塊到內存,然后根據redo log把第一行改為4321,然后再算一遍CRC如果與壞塊中的CRC一致了,證明該壞塊的確是由于4321被撕裂而導致CRC不一致,此時(shí)數據庫可以把CRC更正過(guò)來(lái)然后恢復該塊。但是,如果是下面這種場(chǎng)景,數據庫就無(wú)能為力了:
該例中,第一行被完整更新,但是CRC未被完整更新,導致撕裂。數據庫發(fā)現redo log中的4321已經(jīng)被完整更新到了數據塊上,但是CRC依然錯誤,那么此時(shí)數據庫無(wú)法判斷到底是因為數據塊中其他的數據位發(fā)生了翻轉出錯,還是因為CRC未被原子寫(xiě),此時(shí)數據庫無(wú)計可施,只能報告壞塊。
四、業(yè)界為了避免數據不一致而做的妥協(xié)——兩次/三次提交
4.1 文件系統日志
如果將要寫(xiě)入硬盤(pán)數據文件中的數據/元數據先不往原始文件中寫(xiě),而是寫(xiě)到硬盤(pán)中的一個(gè)單獨的文件中,這個(gè)文件被稱(chēng)為journal/日志(也有人叫log),FS收到下游協(xié)議棧返回成功信號之后才向應用返回寫(xiě)入成功信號。(這里又牽扯到如果設備寫(xiě)成功了,而FS在向app返回成功信號時(shí)斷電,那么app認為沒(méi)成功,而底層其實(shí)已經(jīng)成功了,此時(shí)就需要靠app來(lái)決定下一步動(dòng)作,比如重新再來(lái)一遍,或者后續發(fā)現其實(shí)已經(jīng)成功了)。這個(gè)過(guò)程中,原始數據文件是沒(méi)被寫(xiě)入的,依然保持上一個(gè)一致的狀態(tài)。日志中的數據/元數據在某些時(shí)候會(huì )寫(xiě)入到硬盤(pán)上的原始數據/元數據文件中,比如每隔幾秒鐘會(huì )觸發(fā)一次針對原始數據/元數據文件的寫(xiě)入,這個(gè)過(guò)程叫做checkpoint或者commit,如果這期間發(fā)生掉電宕機,重啟之后FS可以分析日志,將日志中沒(méi)有commit完成的操作再commit一遍到數據文件中。
有人會(huì )有疑問(wèn):如果FS在寫(xiě)日志的時(shí)候,發(fā)生了宕機掉電導致的數據塊撕裂怎么辦?那么發(fā)生撕裂的這筆IO就處于未完成狀態(tài),此時(shí)該IO的數據會(huì )被完全丟棄,以原始數據文件中對應的數據為準,也就是應用再發(fā)起針對該IO目標地址的讀操作時(shí),FS會(huì )從原始數據文件中讀出內容,此時(shí)讀出的便是上一次的一致?tīng)顟B(tài)的數據,此時(shí)應用可以基于這個(gè)數據繼續工作,比如選擇重新錄入新數據。這就是兩次提交的好處,第一次先提交到日志文件,一旦IO被撕裂,那么原始數據文件中的數據依然是完好的。那么,如果數據被commit到原始數據文件的過(guò)程中,如果一旦發(fā)生數據塊撕裂,怎么辦?這個(gè)可以參考上述那兩個(gè)圖中所示的場(chǎng)景,FS只要重放(replay、redo)日志中未完成的操作,重新覆蓋一邊對應的數據塊即可,由于多數FS并不對數據塊做校驗,所以不會(huì )出現上述那個(gè)問(wèn)題。
4.2 MySQL Double Write Buffer
為了解決帶校驗的數據塊撕裂導致的壞塊誤判問(wèn)題,MySQL采用了三次提交的方式。第一次先將IO寫(xiě)操作數據提交到redo log日志中(如果IO尚未寫(xiě)入log或者寫(xiě)了一部分尚未commit之前宕機,那么重啟之后根據這個(gè)斷點(diǎn)undo回上一個(gè)commit點(diǎn)時(shí)的數據),第二次將本應commit到原始數據文件中的數據再寫(xiě)入到硬盤(pán)上一個(gè)單獨的文件中,叫做Double Write Buffer,當commit到DWB的期間一旦發(fā)生掉電宕機,那么DWB里的數據就是不一致的,那么重啟之后,數據庫可以利用redo log+原始數據文件(一定是一致的),來(lái)重放/redo,從而將系統恢復到最近的時(shí)刻,重放期間依然是先從log寫(xiě)入DWB,再從DWB寫(xiě)入到數據文件(因為如果繞過(guò)DWB,直接從redo重放到原始數據文件的話(huà),一旦該過(guò)程再宕機,原始數據文件就可能不一致,最后的希望也就沒(méi)了),如果重啟之后又宕機了,就再來(lái)一遍,循環(huán)。數據文件在被提交到DWB之后,就相當于有了一份備份,數據庫再從這個(gè)備份中將數據導入到原始數據文件,如果導入過(guò)程中出現宕機,沒(méi)關(guān)系,重啟后只需要再從DWB的斷點(diǎn)重新再次覆蓋一下原始文件即可。
ext4文件系統的日志方式中有一個(gè)是data=jurnal,其底層就是先將數據和元數據更新都寫(xiě)到日志中,然后再提交到原始數據文件中,這種機制相當于MySQL的DWB了。
4.3 某傳統廠(chǎng)商文件系統的做法
該文件系統也采用日志技術(shù),但是為了保證速度,采用帶電池保護的RAM來(lái)承載日志。在向數據文件commit數據的時(shí)候,也采用類(lèi)似MySQL DWB類(lèi)似思想,但是形式卻不同。其每次都會(huì )將更新的數據寫(xiě)入到硬盤(pán)上空閑的空間上,并且同時(shí)更改映射表指針的指向。每隔10秒鐘,或者系統內其他一些功能所觸發(fā),該文件系統對數據文件批量提交更新的數據,只不過(guò),該過(guò)程并不是把數據拷貝到原始數據文件覆蓋,而是把元數據提交,由于元數據的更改每次也都是寫(xiě)入空閑空間,所以元數據的提交無(wú)非就是最終將根指針的指向做一次跳轉而已。一旦在這個(gè)過(guò)程中任何一處發(fā)生宕機,那么重啟之后其可以利用日志重放之前的變更。由于其對每個(gè)數據塊也做了checksum校驗,所以其也會(huì )存在塊撕裂導致的壞塊問(wèn)題。與DWB機制一樣,其每次將數據寫(xiě)入空閑空間,上一次commit之后的數據文件并沒(méi)有被更改,所以一旦遇到壞塊,那么其可以利用日志+上一次的原始數據文件來(lái)進(jìn)行重放,如果在這個(gè)過(guò)程中又出現問(wèn)題,或者各種bug或者未知原因導致的重放失敗,那么至少上一次成功commit之后的數據是可用的,其可以回滾到上一個(gè)狀態(tài),雖然有丟數據,但是至少可以保證數據一致。
據不可靠消息,Oracle并沒(méi)有像MySQL一樣對數據塊采用三次提交的辦法,而是數據直接由內存持續的寫(xiě)入硬盤(pán)中的數據文件,此時(shí),存在一定幾率由底層無(wú)法原子寫(xiě)而導致的塊撕裂而無(wú)法使用redo log恢復,從而出現各種級別的錯誤,嚴重者甚至整個(gè)庫無(wú)法被拉起來(lái)。
五、如何在外部存儲設備上實(shí)現原子寫(xiě)
如果能夠在外部設備中保證IO的原子寫(xiě),那么諸如MySQL的DWB就可以不要了,會(huì )節省開(kāi)銷(xiāo)提升IO性能。如果在硬盤(pán)中可以實(shí)現多IO為一組的原子寫(xiě),那么存儲系統控制器里為了保證一致性而做的復雜機制就可以被簡(jiǎn)化。單IO原子寫(xiě)的前提是,操作系統內核模塊比如塊層里的LVM、軟Raid等,不能把單筆寫(xiě)IO分割成多筆,如果在這里分割了,外部設備的單IO原子寫(xiě)就失去了意義。然而,根本就無(wú)法保證軟raid和LVM不分割,這完全取決于條帶或塊大小以及應用下發(fā)的IO大小。另外,Device Driver會(huì )向系統上報一個(gè)對應設備所能支持的最大IO size,如果應用的IO size大于這個(gè)size,Device Driver也會(huì )將其分割,所以,應用層必須預先得到這些參數,然后加以配合來(lái)實(shí)現單IO原子寫(xiě)。
5.1 SAS/SATA硬盤(pán)實(shí)現單IO原子寫(xiě)
HBA場(chǎng)景:HBA處不適合負責原子寫(xiě),因為HBA控制器內部要盡量簡(jiǎn)單和高效。那么當HBA將數據源源不斷的用SAS或者SATA鏈路一幀一幀的傳送給硬盤(pán)時(shí),硬盤(pán)僅當將一筆IO的所有數據都接收到其內部緩沖之后,才發(fā)起寫(xiě)盤(pán)操作。而僅僅如此的話(huà)也并不能保證原子寫(xiě),硬盤(pán)必須在內部采取日志方式,將該IO先寫(xiě)入硬盤(pán)上保留的日志區,日志成功寫(xiě)入后,再向HBA控制器發(fā)送cmd complement幀,同時(shí)才能開(kāi)始向數據區寫(xiě)入數據,同時(shí)HBA再向host端的competition queue入隊io完成描述。一旦上述過(guò)程宕機,硬盤(pán)重啟后可以用日志redo。如果IO之前尚未完整的寫(xiě)入日志,則硬盤(pán)實(shí)際數據區的內容也依然完好,硬盤(pán)只需要從日志中刪除該筆IO的不完整數據即可,就當該IO沒(méi)發(fā)生過(guò)。這就是實(shí)現原子寫(xiě)的代價(jià),一定是降低了性能。有一個(gè)要注意的地方是,采用了日志方式之后,要保證IO的時(shí)序一致性,比如有一筆IO已經(jīng)被成功commit到日志,那么后續如果收到針對有重疊的目標地址的讀IO,硬盤(pán)要返回已經(jīng)提交的數據而不是從盤(pán)片上讀出數據返回,這會(huì )增加計算量。
Raid卡場(chǎng)景: Raid卡是個(gè)帶內虛擬化設備,適合實(shí)現原子寫(xiě),由于其具有天然的優(yōu)勢——有超級電容的保護。Raid卡只需要保證將一筆IO數據完全DMA到其內部的緩沖器中,并將對應的緩沖器page置為dirty,之后,便可以向host端完成隊列入隊該io已完成的描述了。這便實(shí)現了原子寫(xiě)。
5.2 PCIE接口的固態(tài)盤(pán)實(shí)現單IO原子寫(xiě)
如果打開(kāi)了wb模式的寫(xiě)緩存,固態(tài)盤(pán)必須將一筆寫(xiě)IO的全部數據都收到自己的緩沖內部之后,然后即可向host端完成隊列入隊io完成應答。如果沒(méi)有打開(kāi)寫(xiě)緩存,那么僅當該筆IO全部?jì)热荻急籧ommit到flash之后,固態(tài)盤(pán)固件才會(huì )向主機應答。一旦上述過(guò)程中發(fā)生宕機,那么固態(tài)盤(pán)此時(shí)有兩種方式來(lái)保證IO的原子性。
方式1:redo模式。當打開(kāi)wb模式緩存時(shí),該IO完整數據如果已經(jīng)進(jìn)入了緩存并且應答,那么固態(tài)盤(pán)要保證該IO在掉電之后,依靠電容將其數據寫(xiě)入flash,也就是掉電后必須redo。如果沒(méi)有電容或者電容容量太低,那么就得將該IO先寫(xiě)入flash的日志區之后,再向主機應答,重啟后redo日志。
方式2:undo模式。如果不打開(kāi)寫(xiě)緩存,固態(tài)盤(pán)控制器從host端主存源源不斷的DMA數據到內部的容量非常有限的緩沖區,只要緩沖滿(mǎn)一個(gè)頁(yè)面,控制器就開(kāi)始向后端flash寫(xiě)入。那么,根據上文所述,會(huì )產(chǎn)生不一致。但是,此時(shí)可以利用SSD內部機制的一個(gè)天然優(yōu)勢,那就是,Redirect on Write機制,提到這個(gè)機制,大家可以想到上文中介紹的那個(gè)每次都重定向寫(xiě)的文件系統的機制,也是RoW。上一次的舊數據并不被原地覆蓋,所以可以完好的保留。所以,SSD可以不等整個(gè)IO的數據都到緩沖就可以先讓數據寫(xiě)到Flash,寫(xiě)成功之后,再更新地址映射表,正如那個(gè)特殊文件系統的做法一樣,一旦數據寫(xiě)入過(guò)程中發(fā)生宕機,那么下次重啟,由于地址映射表尚未更新,所以該目標地址自然就會(huì )被指向之前的舊數據,達到了“要么全寫(xiě)入要么不寫(xiě)入”的原子寫(xiě)效果。這種方式可以認為是出錯即undo的方式,與數據庫里的undo機制不同,由于SSD內部天然的RoW,不需要像數據庫一樣采用CoW方式將舊數據拷貝出來(lái)形成回滾段。
5.3 實(shí)現多IO為一組的原子寫(xiě)
要實(shí)現多個(gè)IO要么一起全寫(xiě)入要么一起都不寫(xiě)入的效果,就必須增加對應的軟件描述接口,以便讓上游程序告訴下游部件“哪幾個(gè)IO需要實(shí)現原子性”,必須這樣,別無(wú)他法。
方式1:帶外方式。比如增加一種通知機制,單獨描述給下游部件,比如“事務(wù)開(kāi)始,后續發(fā)送的32個(gè)IO為一組原子IO。帶外方式的缺點(diǎn)在于,IO必須連續,期間不能被亂入其他非該事務(wù)的IO,而優(yōu)點(diǎn)在于能夠節省開(kāi)銷(xiāo);
方式2:帶內方式。帶內方式則是將該信息直接嵌入到每個(gè)IO上,比如第一個(gè)IO中嵌入“事務(wù)開(kāi)始,事務(wù)ID=1,共32個(gè)IO,此為第一個(gè)”,期間可以被亂入其他非事務(wù)性IO或者其他事務(wù)ID的IO。該事務(wù)的最后一個(gè)IO(第32個(gè))中會(huì )攜帶“事務(wù)結束,ID=1,共32個(gè)IO,此為第32個(gè)”的信息。帶內方式優(yōu)點(diǎn)在于靈活,可以亂入其他事務(wù)或者非事務(wù)的IO,以供底層更充分的重排優(yōu)化,缺點(diǎn)在于開(kāi)銷(xiāo)大,每筆IO都需要攜帶對應信息。
由于多IO原子寫(xiě)無(wú)法透明實(shí)現,需要修改應用、內核以及外部硬件固件,生態(tài)關(guān)系協(xié)調困難,認知度低,場(chǎng)景少,基本上可以靠數據庫自身的日志機制解決,所以目前實(shí)際產(chǎn)品中只有極少數采用私有訪(fǎng)問(wèn)協(xié)議的產(chǎn)品實(shí)現了,而實(shí)現方式是上面的哪一種冬瓜哥也不清楚。
然而,理想雖好,由于實(shí)現方式復雜,所以一般產(chǎn)品都選擇不支持原子寫(xiě)。即便支持,也基本上是上述的出錯即undo的方式,這樣能降低實(shí)現復雜度,同時(shí)不增加IO的時(shí)延,因為IO流程與非原子寫(xiě)狀態(tài)下是一樣的,采用wormhle數據傳輸方式,只是寫(xiě)成功后才更新映射表,寫(xiě)不成功沒(méi)關(guān)系,回滾到舊數據塊上。而緩存模式+redo模式下,會(huì )增加IO時(shí)延,因為必須等IO數據全部DMA到緩存中之后才對主機端應答,相當于stor-forwarding數據傳輸方式。
摘自冬瓜哥:【冬瓜哥論文】原子寫(xiě),什么鬼?!
聯(lián)系客服