etcd raft 處理流程圖系列3-wal的讀寫


本文僅介紹wal的基本處理,如create、open、close、read等操作,從wal目錄中加載snapshot,wal文件的創建,以及讀取wal目錄中的所有數據(主要是entryTypestateTypemetadataType這幾類)和接收到node.Ready()之后的寫操作。

WAL的處理還是比較復雜的可以借鑒的地方也很多。WAL在編碼以及flush時使用緩存來提升效率。flush的單位為分頁,每頁又分為8個section,section的作用是用來檢測寫入的數據是否被破壞,檢測邏輯為:如果某個section中的所有字節都為0,則說明數據遭到破壞,反之則認為數據正常。在isTornEntry中,主要通過section機制來檢測WAL文件中最后一個record是否因為數據破壞而導致json解析或crc校驗失敗。

wal很多地方用到了crc校驗,基本邏輯是在encoder寫入時會計算crc,在使用新文件(如createcut)時會保存crc。創建文件時寫入的crc為0,切分文件(新文件由WAL.fp提供)時寫入的crc為前一個文件的crc,一個文件僅會在開頭保存一個crc。在讀取WAL文件時,decoder會在讀取到非crcTyperecorder時更新其crc,當讀到crcTyperecorder時會使用它計算出的crc與recorder中的crc進行比較,判斷是否存在數據篡改。每個recorder中都會保存crc,crcType只是提供了一個執行crc校驗的機會(即只有遇到crcType類型才會進行crc校驗)。

在看代碼時也給官方提了一些issue:132731328713286

創建

下面是wal的create流程,在創建文件事先預分配文件大小(64MB),用於提升性能。wal通過encode()函數將編碼后的數據寫入文件,因此需要在對文件執行寫操作時加鎖,寫入的數據以record為單位(record首先被寫入緩存,當數據以頁為單位對齊時通過flush寫入文件)。先計算數據的crc校驗碼,然后計算record的幀數據。寫數據時,先寫入幀數據,再寫入record。在寫入數據(無論是幀數據還是record)時,會以頁為單位將數據寫入文件,不足一頁的數據會暫存在緩存中。幀數據保存了實際的數據大小和pad的數據大小,在讀取wal文件時會用到該信息。

wal的文件名由兩部分構成:seq和index,前者應該順序遞增的,以保證日志文件的連續性(isValidSeq會根據seq校驗日志文件的連續性)。

加載snapshot

下面是在wal目錄中加載snapshot的操作,該操作中用到了上面的幀數據。wal使用decode()函數進行解碼,首先取出在幀數據中解析出record的大小和padBytes的小,然后根據record的大小解碼數據,最后根據record的類型采集並返回所有snapshot。

從上面可以看到,wal的encoder用於寫文件,因此encoder會關聯到當前正在編輯的文件,記錄了文件句柄、當前字節偏移以及緩存等信息,一般會選擇WAL.locks中的最后一個元素。而decoder用於讀取所有文件,因此關聯到多個wal文件,記錄了這些文件句柄。

讀取所有數據

下圖是從wal目錄中嘗試讀取所有信息(如metadata、entries、state)的過程。涉及讀取wal目錄中的文件信息,以此構建WAL結構,然后通過生成的decoder來將文件解碼為不同類型的數據進行處理。最終返回解碼后的數據。需要注意decoder的文件是有序的,可以從源碼fileutil.ReadDir看出來,其對文件名進行了sort.Strings(names)操作。

此外,在讀取文件時,根據文件的讀寫模式分別進行了處理。讀模式下只需讀完所有文件,關閉文件並返回結果即可。寫模式下文件是加鎖的,在decodeRecord中會讀取lastValidOff(frameSizeBytes + recBytes + padBytes)長度的數據,並將該長度之后的數據歸0,防止文件中出現被破壞的數據,由於對文件的修改會改變文件的crc校驗,但好在新的record不會立即刷新到文件中(源碼中的描述如下),更新文件的encoder,后續通過encoder將數據最終寫入文件即可。

		// decodeRecord() will return io.EOF if it detects a zero record,
		// but this zero record may be followed by non-zero records from
		// a torn write. Overwriting some of these non-zero records, but
		// not all, will cause CRC errors on WAL open. Since the records
		// were never fully synced to disk in the first place, it's safe
		// to zero them out to avoid any CRC errors from new writes.

WAL的保存

raftexample的serveChannels中當接收到node.Ready()傳來的數據時,會對這些數據進行持久化。如下圖,首先會保存狀態和entry信息,如果locks中最后一個文件(該文件)的內容大於或等於SegmentSizeBytes時需要切割文件。

在切分文件時,將已有的數據同步到文件中,后面的操作就是新建一個文件。新文件來自於WAL.fp是在創建文件時創建的,fp提供文件的代碼邏輯如下,可以看到它通過循環創建文件的方式來為WAL源源不斷地提供日志文件。

	for {
		f, err := fp.alloc()
		if err != nil {
			fp.errc <- err
			return
		}
		select {
		case fp.filec <- f:
		case <-fp.donec:
			os.Remove(f.Name())
			f.Close()
			return
		}
	}

首先在新文件中記錄當前的crc,然后寫入metadatastate信息,並重新計算crc,在讀取時可以校驗到此為止的crc。新文件作為WAL.locks中的最后一個文件。

原圖鏈接


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM