WAL 概述
WAL 是 write-ahead log 系統(tǒng),其核心思想是將用戶的所有修改操作(插入、刪除)寫入日志,然后再應(yīng)用到系統(tǒng)狀態(tài)。一旦日志寫入成功,就可以通知用戶操作成功。由于日志采用尾部追加方式寫入,耗時較短,因此不會長時間阻塞用戶線程。另外,為防止意外退出導(dǎo)致數(shù)據(jù)丟失,系統(tǒng)重啟時會根據(jù)日志重做用戶操作,保證數(shù)據(jù)可靠性。
WAL – 預(yù)寫日志
WAL 一直是傳統(tǒng) RDBMS 系統(tǒng)中的一個共識,用于幫助保證原子性和持久性(ACID 的 A 和 D)。對表的所有更新首先寫入 WA),然后異步的方式使用。
示例 WAL 和 WALEntry 結(jié)構(gòu):
type WAL struct { dir string // 存放 WAL 文件的目錄。 file *os.File // 引用文件 metadata []byte // 每個 WAL 解碼器頭部記錄的元數(shù)據(jù) *decoder // 解碼器解碼記錄 編碼器 *encoder // 編碼器編碼記錄 mutex sync.Mutex // To確保每個寫入器一次更新 lastIndex uint64 // 保存到 WAL 的最后一個條目的索引} type WALEntry struct { lsn uint64 // 每個日志條目的唯一標識符 data []byte // 實際 WAL 條目(以字節(jié)為單位) crc uint32 // crc for數(shù)據(jù)完整性驗證 type uint32 // wal 記錄的類型 }
為什么需要 WAL
為什么不將更改直接刷新到實際數(shù)據(jù)文件?
它有2個方面——
WAL設(shè)計
WAL 是一個僅附加日志,它將數(shù)據(jù)存儲中的每個狀態(tài)更改存儲為日志。
一個單獨的異步進程可以從 WAL 讀取操作,然后按照正常流程通過不同的緩存將數(shù)據(jù)更新應(yīng)用于磁盤上的實際數(shù)據(jù)文件,有助于提高數(shù)據(jù)存儲的寫入吞吐量。
此外,如果發(fā)生故障,可能會有未應(yīng)用的更新,由于我們在 WAL 文件中存在操作,我們可以從 WAL 重放操作并應(yīng)用它們以使數(shù)據(jù)存儲恢復(fù)到一致狀態(tài)。因此,WAL 幫助我們確保數(shù)據(jù)的完整性和可靠性,同時仍然允許我們的數(shù)據(jù)存儲具有高寫入吞吐量。
注意事項
1. 將 WAL 操作刷新到磁盤
如前所述,對磁盤的寫入可能不會直接刷新,考慮到寫入系統(tǒng)中導(dǎo)致性能的問題,需要進行權(quán)衡刷新頻率或微批處理或兩者來將更改刷新到磁盤,以幫助提高性能。請注意,此處存在數(shù)據(jù)丟失的風(fēng)險。
2. 損壞檢測
需要確保任何刷新到磁盤的操作都不會損壞, WAL 記錄還包含一個 CRC 值,該值可用于驗證何時從 WAL 讀取記錄并確保沒有損壞。
3. 重復(fù)操作
由于 WAL 是一個附加追尾的文件,因此如果客戶端由于通信故障而重試,可能會遇到在 WAL 上寫入重復(fù)操作的情況。因此,每當讀取 WAL 時,要確保忽略重復(fù)項,或者對應(yīng)用數(shù)據(jù)的動作具有冪等性的。
現(xiàn)狀
1)所有數(shù)據(jù)庫,包括像Cassandra這樣的NoSQL數(shù)據(jù)庫都使用WAL來保證持久性。
2) Kafka 使用了與 WAL(Commit Log) 類似的結(jié)構(gòu)。
3) 像 Rocks DB、Level DB 這樣的 KV 存儲和像 Apache Ignite 這樣的分布式緩存也使用 WAL。
概括
總而言之,WAL 提供一下價值
1) 更快的性能和吞吐量,避免了所有更改的數(shù)據(jù)刷新/磁盤寫入。
2) 重啟時的可恢復(fù)性,操作可以從 WAL 應(yīng)用到實際的數(shù)據(jù)存儲。
3)能夠恢復(fù)到時間點快照,我們在 WAL 中存在所有操作。