數(shù)據(jù)庫系統(tǒng)實現(xiàn)-存儲管理(CheckSum&RAID)
磁盤并不是萬無一失的,也可能發(fā)生故障和損壞。磁盤發(fā)生故障是災(zāi)難性的,存儲介質(zhì)的損壞如果在沒備份的情況下,基本是100%丟失數(shù)據(jù),比rm -rf 還嚴(yán)重,那么磁盤的故障有那么些?
- 間歇性故障
- 介質(zhì)損壞
- 寫故障
- 磁盤崩潰
在這些故障的時候有哪些應(yīng)對策略和辦法?我們這章來來學(xué)習(xí)一下。
間歇性故障
當(dāng)我們嘗試從磁盤讀一個塊的時候,可能這個塊的內(nèi)容未正確的發(fā)送到磁盤控制器,這就是一次間歇性的故障,控制器會以一種辦法來判斷這個磁盤塊的好壞,如果讀的數(shù)據(jù)是壞的,控制器會嘗試再次讀取發(fā)起請求,直到讀取的數(shù)據(jù)正確位置,或者發(fā)送N次請求,然后再停止。
同樣,我們嘗試寫入一個扇區(qū),可能寫入的內(nèi)容不是原本想要寫入的內(nèi)容,唯一的辦法就是磁盤把寫入的內(nèi)容讀取出來跟磁盤控制器的內(nèi)容再比較一下。然而,跟磁盤控制器比較,其實還可以讀取寫入的扇區(qū),并且查看狀態(tài)是否為"好",如果狀態(tài)是"壞"那么就重寫,這個狀態(tài)如何產(chǎn)生的呢?這就引出了是checksum概念。
校驗和(checksum)
在說checksum之前,我們來玩?zhèn)€小游戲:
桌子上擺放了7個黑白棋子,魔術(shù)師蒙著眼睛看不見棋子。魔術(shù)師徒弟在看完7個棋子之后在右邊添加個棋子,和其他棋子并排,這個時候有8個棋子,魔術(shù)師依舊蒙著眼睛。這個時候觀眾可將其中的一個棋子翻轉(zhuǎn),或者不翻轉(zhuǎn)任何一枚。觀眾和徒弟一言不發(fā),魔術(shù)師并不知道觀眾是否翻轉(zhuǎn)棋子。
現(xiàn)在魔術(shù)師摘下眼罩,觀察8枚棋子,然后可以說出是否翻轉(zhuǎn)了棋子,識破觀眾的行為。那么魔術(shù)師是如何識破的呢?
校驗和:在磁盤的每個扇區(qū)都有幾個附加位,這個被稱為校驗和。在數(shù)據(jù)讀取的時候,如果校驗和跟數(shù)據(jù)位不符合,那么就判斷讀取錯誤。不過校驗和正確,也有可能數(shù)據(jù)是錯誤的,這個的可能性跟校驗位的長度成負(fù)相關(guān)。校驗位越長,判斷錯誤的概率越小。
校驗和是基于扇區(qū)內(nèi)所有位的奇偶性(parity),如果二進(jìn)制的集合中有奇數(shù)個1,那么數(shù)據(jù)位有奇數(shù)奇偶性并增加值為1的奇偶位。同理,如果有偶數(shù)個1,那么數(shù)據(jù)位有偶數(shù)奇偶性并且增加值為0的奇數(shù)位。
假如我們用一個字節(jié)(8bits)來判斷奇偶,并且檢測出錯誤的可能性為50%,那么出錯的概率為,1/2^8,如果用4個字節(jié)呢,則出錯率為1/2^32。相對于40億次中只有一次錯誤沒被檢測出來。
一般的在數(shù)據(jù)庫中用的是CRC或者是FNV算來進(jìn)行checksum的,在PostgreSQL中用得上 FNV-1a,我們來看看PG是如何實現(xiàn)的。
PostgreSQL-CheckSum
Postgres默認(rèn)是不開啟checksum的,在初始化數(shù)據(jù)庫的時候可以通過選項-k開啟。
- -k, --data-checksums use data page checksums
使用的算法是FNV-1a,下面的地址是這個算法的詳細(xì)介紹:http://www.isthe.com/chongo/tech/comp/fnv/
核心代碼在:src/include/storage/checksum_impl.h
函數(shù)入口:pg_checksum_block
官方描述:
PG的實現(xiàn)跟官方的實現(xiàn)有區(qū)別,官方的實現(xiàn)為:
而PG在乘之前,先右移了17位,然后再xor。
計算出來的結(jié)果都存儲在頁頭PageHeaderData->pd_checksum
數(shù)據(jù)從shared_buffer刷盤以及從disk讀取block都需要計算checksum。如果只是在內(nèi)存中變更數(shù)據(jù)頁,是不需要計算checksum的。
感興趣的可以gdb跟一下看看。
再回到上面的小游戲,魔術(shù)師只要跟徒弟在之前協(xié)定好,有偶數(shù)個白或者黑,問題就解決了,這也是一種奇偶校驗的實現(xiàn)。
介質(zhì)損壞
磁盤也是有使用壽命的,當(dāng)磁盤損壞后,破壞是物理性的,數(shù)據(jù)是不可能完全的恢復(fù)回來。在上面我們介紹了如何來高效的檢測介質(zhì)的故障或讀寫的故障,但是卻沒說明如何解決這個問題。
在磁盤的扇區(qū)損壞的問題上,可以用2塊或者多塊磁盤來存儲數(shù)據(jù)。例如我們寫入內(nèi)容X,通常扇區(qū)是成對的,我們把寫入X的扇區(qū)稱作為(左拷貝)Xl和(右拷貝)Xr。讀函數(shù)可以讀取Xl或者Xr,返回一個結(jié)果w,那么w是X的真實的內(nèi)容。前提是我們通過CheckSum對Xl和Xr扇區(qū)的完整性做了排查。(這段內(nèi)容有點繞口,其實就是寫2份數(shù)據(jù)到不同的磁盤扇區(qū),然后可以任意從其中一個讀取到你想要的內(nèi)容)。
寫的策略也是一樣的,例如寫入Xl,檢查是否返回狀態(tài)為"好",如果不是,就需要不停的寫,到達(dá)若干次后,如果任然未成功,則可表示該扇區(qū)是壞的,則可以用Xr來替換損壞Xl。相反對Xr跟Xl是一樣的邏輯,重復(fù)Xl的動作即可。
讀策略是交互的,交替的讀取Xl和Xr,預(yù)先設(shè)定個很大的數(shù)字,若嘗試超過這個數(shù)據(jù),則可以確定X是無法讀取的。
RAID
上面討論的內(nèi)容其實就是RAID(Redundant Array of Independent Disk,獨立磁盤冗余陣列)。磁盤崩潰發(fā)生率一般由平均失效時間來衡量,假設(shè)磁盤平均失效時間為10年,則每年磁盤中的1/10會發(fā)生故障,為了避免這種故障,就采用了這種策略,當(dāng)一個數(shù)據(jù)盤或者冗余盤發(fā)生崩潰的時候,其他的磁盤可用于恢復(fù)故障磁盤,從而使得沒有任何數(shù)據(jù)會丟失。RAID分了很多等級,我們下面就看看不同等級的計算和使用。
RAID-1
RAID-1方案使用2塊磁盤做扇區(qū)的拷貝,一個做數(shù)據(jù)盤,一個做冗余盤,或者互換。相當(dāng)于有2份磁盤空間來存儲數(shù)據(jù)(空間浪費),任何一份失效,可以使用未損壞的進(jìn)行修復(fù),除非2塊盤同時失效。
看個例子:假設(shè)每個磁盤的使用壽命為10年,那么意味著每年的損壞率為10%。如果磁盤被鏡像,發(fā)生故障的時候我們可以利用另外一塊好的替換他。所以造成錯誤的至少要2個磁盤的數(shù)據(jù)都丟失,才能說是數(shù)據(jù)無法恢復(fù)。假設(shè)替換的時間為3個小時,這是一天中的1/8,或者是1年的1/2920,那么10年的故障率為1/29200,2塊磁盤之一平均5年發(fā)生一次故障,那么丟失數(shù)據(jù)的概率大概為5*29200=14600年。
RAID-4(奇偶塊)
RAID是以磁盤塊為奇偶校驗,假設(shè)有N塊數(shù)據(jù)盤,一個冗余盤,在冗余盤中,第i塊由所有的數(shù)據(jù)盤的第i塊奇偶校驗位組成,也就是說,所有第i塊盤的j位,包括數(shù)據(jù)盤和冗余盤,他們中間必須有偶數(shù)個1,而我們總是選取冗余盤的位讓條件為真。
假設(shè)我們有4塊盤,3個數(shù)據(jù)盤,一個冗余盤。假設(shè)一個塊只有一個字節(jié),8位。
盤1:11110000
盤2:10101010
盤3:00111000
冗余盤
盤4:01100010
在1,2,4,5,7位有兩個1,在3,4有4個1,在6和8有零個1。
利用冗余盤的位讓數(shù)據(jù)盤的奇偶校驗條件為真。
讀:
從任意一個數(shù)據(jù)盤讀取是沒有任何差別。
寫
當(dāng)我們寫一個數(shù)據(jù)盤的一個新塊,我們不僅僅需要改變那個數(shù)據(jù)塊,還需要改變?nèi)哂啾P的相應(yīng)的塊,以保持?jǐn)?shù)據(jù)盤的奇偶性。一個樸素的辦法是讀取N個數(shù)據(jù)盤的相應(yīng)塊,取他們的摸2和,并重寫冗余盤,那么這樣就會是N+1次的磁盤I/O。
還有個更好的辦法,只關(guān)注在被寫盤的i塊的老版本和新版本,操作如下:
1、讀要要被改變的數(shù)據(jù)盤的舊值
2、讀冗余盤的相應(yīng)塊
3、寫新數(shù)據(jù)盤
4、重新計算并寫冗余盤的塊
這樣就只有4次I/O操作
摸2代數(shù)解釋
假設(shè)有如下三個數(shù)據(jù)盤,***個塊如下:
盤1:11110000
盤2:10101010
盤3:00111000
冗余盤:01100010
假設(shè)盤2的塊由10101010改變?yōu)?1001100,我們來求盤2上的舊值和新值的摸2和,得到
01100110,這個結(jié)果告訴我們,必須改變?nèi)哂啾P的***塊的位置,2,3,6,7的值。那么冗余盤為:0000100,如下:
盤1:11110000
盤2:11001100
盤3:00111000
冗余盤:0000100
這樣每列依舊有偶數(shù)個1
故障恢復(fù)
如果是冗余盤壞了,直接換一塊新盤,并且重新計算奇偶。如果是數(shù)據(jù)盤壞了,也是換一個新盤,不起根據(jù)其他盤重新計算它的數(shù)據(jù)。我們來看個例子:
盤1:11110000
盤2:????????
盤3:00111000
盤4:01100010
我們?nèi)∶恳涣械哪?和,可以推導(dǎo)出盤2為:
盤1:11110000
xor
盤2:00111000
=:11001000
xor
01100010
=10101010
RAID-5
在RAID-4的策略中,除非2塊盤同時損壞,否則還是能有效的保護(hù)數(shù)據(jù)。不過有個缺點,就是無論是讀和寫都需要訪問冗余盤。從上面故障恢復(fù)的例子可以發(fā)現(xiàn),恢復(fù)數(shù)據(jù)盤和冗余盤是一樣的策略,都是取其它盤的模2和。這樣,我們就不必要把一個盤做冗余盤,而把其他盤作為數(shù)據(jù)盤,相反,我們可以把每個磁盤作為某寫塊的冗余盤來處理,這種改進(jìn)稱為RAID-5。
假設(shè)我們有4塊盤,0-3。***個盤編號為0,講作為編號為4,8,12等盤的冗余,因為當(dāng)被4除時,余數(shù)為0。編號為1的盤將作為編號為1,5,9等塊的冗余。盤2是2,6,10塊的冗余,盤3是3,7,11等塊的冗余。
如果每個盤讀寫負(fù)荷一樣,如果所有的塊有相同的可能性被寫,那么對于一次寫,每個盤有1/4的機(jī)會,并且還有1/3的機(jī)會作為那個塊的冗余盤。這樣,4個盤的每個涉及寫的機(jī)會是1/4+3/4*1/3=1/2
多個盤的崩潰處理(RAID-6)
前面講的都是一塊盤的崩潰,如果涉及到多個盤的崩潰,還是無法處理的。多個盤的崩潰有一個糾錯碼原理,允許我們處理多個盤的崩潰,前提的我們有足夠多的冗余盤。這個 策略導(dǎo)致了***的RAID級別-RAID-6。這個策略是基于海明碼(Hamming code)進(jìn)行糾錯的。
我們來看個例子,一個7個磁盤的系統(tǒng),磁盤編號為1-7,前面4個是數(shù)據(jù)盤,5-7是冗余盤。數(shù)據(jù)盤和冗余盤組成的一個3*7的矩陣,如下圖:
如圖,請注意:
1、除了全0列之外的,三個0和1的所有可能的列都在這個矩陣中
2、冗余盤有單個1
3、數(shù)據(jù)盤至少各有兩個1
通過這個矩陣可以知道:
1、盤5是盤1、2、3相應(yīng)位的摸2和
2、盤6是盤1、2、4相應(yīng)位的模2和
3、盤7是盤1、3、4相應(yīng)位的模2和
通過這個規(guī)則,我們就能從兩個同時發(fā)生故障的磁盤崩潰中恢復(fù)。
讀
我們從任何一個數(shù)據(jù)盤讀取數(shù)據(jù),不用理睬冗余盤
寫
寫的話就需要考慮冗余盤。為了寫某個塊,需要計算新舊塊的摸2和,這些位以模2和的方式加入到滿足條件的所有冗余盤相應(yīng)塊中。
看個例子:
我們把第二個盤修改為00001111,新值和舊值的模2和為:10100101,因為盤5和6在盤行1和行2都有1
所以我們需要拿他們的***個塊跟剛才算出來的值10100101執(zhí)行摸2和,也就是說我們需要對這2個塊的1,3,6,8位置求反。
盤5:
10100101
xor
01100010
=
11000111
盤6:
10100101
xor
00011011
=
10111110
所以結(jié)果如下:
RAID-6故障恢復(fù)
假設(shè)故障盤為a和b,我們可以從下面這個矩陣中能夠把a(bǔ)和b的列不同的某個行r找到,假設(shè)a是0,而b是1.
然后我們通過取來自b之外所有的行r有1的磁盤相應(yīng)位模2,我們就能計算出b。計算完b,我們必須用所有其他的盤重新計算a。辦法是取該行帶1的那些其他磁盤的位摸2.
看個例子,如果盤2和盤5在同一時間損壞,如下圖:
我們發(fā)現(xiàn)這2個盤在列2不同,盤2是1盤5是0,那么我們用盤1,4,6來恢復(fù)盤2如下圖:
盤2通過計算得到:
盤1:11110000
xor
盤4:01000001
=10110001
Xor
盤6:10111110
=00001111
盤5可以用盤1,2,3來恢復(fù),如下圖:
盤1:11110000
xor
盤2:00001111
=11111111
xor
盤3:00111000
=11000111
這樣盤2和盤5都恢復(fù)了,結(jié)果如下:
關(guān)于RAID-6,磁盤的數(shù)量可以是任意次方-1,例如2^k-1,k是冗余盤,剩下的2^k-k-1是數(shù)據(jù)盤,所有冗余盤差不多是數(shù)據(jù)盤的對數(shù)增長的,并且都可以構(gòu)造成矩陣來表示。
書中只大概介紹了這幾種RAID,不過現(xiàn)在有跟多的RAID,例如:
- RAID-0:Data Stripping
- RAID-1:磁盤鏡像
- RAID-0+1:RAID-0和RAID-1的結(jié)合體
- RAID-2:帶海明嗎校驗
- RAID-3:帶奇偶校驗并行傳送
- RAID-4:帶奇偶校驗獨立磁盤結(jié)構(gòu)
- RAID-5:分布式奇偶校驗
- RAID-6:帶2種分布式存儲奇偶校驗
- RAID-7:優(yōu)化的高速傳輸磁盤結(jié)構(gòu)
- RAID-10:高可靠和高效磁盤結(jié)構(gòu)
- RAID-53:高速數(shù)據(jù)傳輸磁盤結(jié)構(gòu)
總結(jié):主要是講磁盤的損壞和恢復(fù)方面的內(nèi)容,包括如何檢測損壞和如何恢復(fù),checksum是個很好的檢測手段,而磁盤的冗余是在存儲介質(zhì)損壞的時候提供有力的數(shù)據(jù)保護(hù),并且分析了幾種冗余策略。下一章我們來看看數(shù)據(jù)是如何在磁盤組織的,包括定長&變長數(shù)據(jù)的存儲,Tuple的修改(插入,刪除,跟新),跨塊的存儲和列存儲相關(guān)內(nèi)容,并且我們用C語言自己來寫個例子程序。