天天看點

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

本文僅介紹wal的基本處理,如create、open、close、read等操作,從wal目錄中加載snapshot,wal檔案的建立,以及讀取wal目錄中的所有資料(主要是<code>entryType</code>、<code>stateType</code>、<code>metadataType</code>這幾類)和接收到<code>node.Ready()</code>之後的寫操作。

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

wal很多地方用到了crc校驗,基本邏輯是在encoder寫入時會計算crc,在使用新檔案(如<code>create</code>或<code>cut</code>)時會儲存crc。建立檔案時寫入的crc為0,切分檔案(新檔案由<code>WAL.fp</code>提供)時寫入的crc為前一個檔案的crc,一個檔案僅會在開頭儲存一個crc。在讀取WAL檔案時,decoder會在讀取到非<code>crcType</code>的<code>recorder</code>時更新其crc,當讀到<code>crcType</code>的<code>recorder</code>時會使用它計算出的crc與<code>recorder</code>中的crc進行比較,判斷是否存在資料篡改。每個recorder中都會儲存crc,<code>crcType</code>隻是提供了一個執行crc校驗的機會(即隻有遇到crcType類型才會進行crc校驗)。

在看代碼時也給官方提了一些issue:13273、13287、13286

下面是wal的create流程,在建立檔案事先預配置設定檔案大小(64MB),用于提升性能。wal通過<code>encode()</code>函數将編碼後的資料寫入檔案,是以需要在對檔案執行寫操作時加鎖,寫入的資料以record為機關(record首先被寫入緩存,當資料以頁為機關對齊時通過flush寫入檔案)。先計算資料的crc校驗碼,然後計算record的幀資料。寫資料時,先寫入幀資料,再寫入record。在寫入資料(無論是幀資料還是record)時,會以頁為機關将資料寫入檔案,不足一頁的資料會暫存在緩存中。幀資料儲存了實際的資料大小和pad的資料大小,在讀取wal檔案時會用到該資訊。

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

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

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

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

從上面可以看到,wal的encoder用于寫檔案,是以encoder會關聯到目前正在編輯的檔案,記錄了檔案句柄、目前位元組偏移以及緩存等資訊,一般會選擇<code>WAL.locks</code>中的最後一個元素。而decoder用于讀取所有檔案,是以關聯到多個wal檔案,記錄了這些檔案句柄。

下圖是從wal目錄中嘗試讀取所有資訊(如metadata、entries、state)的過程。涉及讀取wal目錄中的檔案資訊,以此建構<code>WAL</code>結構,然後通過生成的decoder來将檔案解碼為不同類型的資料進行處理。最終傳回解碼後的資料。需要注意decoder的檔案是有序的,可以從源碼<code>fileutil.ReadDir</code>看出來,其對檔案名進行了<code>sort.Strings(names)</code>操作。

此外,在讀取檔案時,根據檔案的讀寫模式分别進行了處理。讀模式下隻需讀完所有檔案,關閉檔案并傳回結果即可。寫模式下檔案是加鎖的,在<code>decodeRecord</code>中會讀取<code>lastValidOff</code>(frameSizeBytes + recBytes + padBytes)長度的資料,并将該長度之後的資料歸0,防止檔案中出現被破壞的資料,由于對檔案的修改會改變檔案的crc校驗,但好在新的record不會立即重新整理到檔案中(源碼中的描述如下),更新檔案的encoder,後續通過encoder将資料最終寫入檔案即可。

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

raftexample的<code>serveChannels</code>中當接收到<code>node.Ready()</code>傳來的資料時,會對這些資料進行持久化。如下圖,首先會儲存狀态和entry資訊,如果locks中最後一個檔案(該檔案)的内容大于或等于<code>SegmentSizeBytes</code>時需要切割檔案。

在切分檔案時,将已有的資料同步到檔案中,後面的操作就是建立一個檔案。新檔案來自于<code>WAL.fp</code>是在建立檔案時建立的,fp提供檔案的代碼邏輯如下,可以看到它通過循環建立檔案的方式來為WAL源源不斷地提供日志檔案。

首先在新檔案中記錄目前的crc,然後寫入<code>metadata</code>和<code>state</code>資訊,并重新計算crc,在讀取時可以校驗到此為止的crc。新檔案作為WAL.locks中的最後一個檔案。

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

原圖連結