共計 3917 個字符,預計需要花費 10 分鐘才能閱讀完成。
這篇文章主要介紹“wal 怎么實現日志讀寫”,在日常操作中,相信很多人在 wal 怎么實現日志讀寫問題上存在疑惑,丸趣 TV 小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”wal 怎么實現日志讀寫”的疑惑有所幫助!接下來,請跟著丸趣 TV 小編一起來學習吧!
etcd raft 介紹
etcd raft 是目前使用最廣泛的 raft 庫,etcd raft 在 etcd, Kubernetes, Docker Swarm, Cloud Foundry Diego, CockroachDB, TiDB, Project Calico, Flannel 等分布式系統中都有應用,在生成環境得到了驗證。傳統 raft 庫的實現都是單體設計 (集成了存儲層、消息序列化、網絡層等), etcd raft 繼承了簡約的設計理念,只實現了最核心的 raft 算法, 這樣更加的靈活。etcd 將網絡、日志存儲、快照等功能分開,通過獨立的模塊實現,用戶可以在需要時調用。etcd 自身實現了自己的一套 raft 配套庫:etcd-wal(用于存儲日志),snap(用于存儲快照),MemoryStorage(用于存儲當前日志、快照、狀態等信息以供 raft 核心程序使用)。
etcd wal 介紹
WAL 是 write ahead log 的縮寫,etcd 使用 wal 模塊來完成 raft 日志的持久化存儲,etcd 對 wal 的所有實現都放在 wal 目錄中。
wal 數據結構
type WAL struct {
lg *zap.Logger
dir string // the living directory of the underlay files
// dirFile is a fd for the wal directory for syncing on Rename
dirFile *os.File
metadata []byte // metadata recorded at the head of each WAL
state raftpb.HardState // hardstate recorded at the head of WAL
start walpb.Snapshot // snapshot to start reading
decoder *decoder // decoder to decode records
readClose func() error // closer for decode reader
mu sync.Mutex
enti uint64 // index of the last entry saved to the wal
encoder *encoder // encoder to encode records
locks []*fileutil.LockedFile // the locked files the WAL holds (the name is increasing)
fp *filePipeline
}
上述為 wal 的數據結構,通過用 wal.go 文件中的 Create() 方法來獲取 wal 的實例。wal 首先會創建一個臨時目錄并初始化相關變量,并創建和初始化第一個 wal 文件,等所有的操作都初始化完成后直接更改臨時目錄的名字完成 wal 實例的初始化。
文件組織
wal 的所有日志放在一個指定目錄下,日志的文件名以 .wal 作為結尾,格式為 -.wal,seq 和 index 的格式都為 %016x, 例如:0000000000000001-0000000000000001.wal。index 代表這個文件中第一條 raft 日志的 index,seq 是這個文件的序列號 (依次遞增)。
每個文件的大小默認為 64M,當文件大于 64M 時,wal 會自動生成新的日志文件用于存儲日志。每個日志文件都會使用 flock 鎖定文件,參數為 LOCK_EX,這是一把獨有鎖,同一時間只能有一個進程可以操作這個日志文件,所以當 wal 占有這個文件時,通過進程是無法刪除這個文件的。
日志邏輯組織
wal 日志可以存儲多種類型的數據,具體如下。
crcType 每個新的日志文件的第一條記錄都會是 crcType 類型的記錄,crcType 也只會在每個日志文件的開始時寫入,用于記錄上一個文件最后的 crc 是多少
metadataType 每個新的日志文件中 metadataType 緊跟在 crcType 記錄后面,每個日志文件只會出現一次
stateType 這種日志類型會在兩種情況下加入:
自動切分日志文件時,新的日志文件中,緊跟在 metadataType 后面會存入一條 stateType 的日志
當 raft 核心程序 ready 中返回 hard state 時也會存儲該類型的日志
snapshotType wal 日志中只會存儲 snapshot 的 term 和 index,具體的數據存儲在專門的 snapshot 中,每次存儲快照都會在 wal 日志中存儲一個 wal 的快照。當存儲快照時,會將快照之前 index 的日志文件都釋放掉。wal 中存儲的 snapshot 主要用于檢查快照是否正確。
日志讀寫
wal 通過封裝的 encoder 和 decoder 模塊來實現日志讀寫。
寫日志
encoder 模塊把會增量的計算 crc 和數據一起寫入到 wal 文件中。下面為 encoder 數據結構
type encoder struct {
mu sync.Mutex
bw *ioutil.PageWriter
crc hash.Hash42
buf []byte // 緩存空間,默認為 1M,降低數據分配的壓力
uint64buf []byte
}
wal 通過 encoder 實現寫日志,在這個模塊中會完成 crc 的計算。wal 為了更好的管理數據,日志中的每條數據都會以 8 字節對齊 (wal 會自動對齊字節)。日志寫入的流程如下。
圖中的 crc 是增量計算,以之前的所有日志數據為增量基礎。wal 只關注寫入日志,不會校驗日志的 index 是否重復,但是如果重啟這個 Node 的話,系統會自動過濾掉中間混雜的日志。
日志切分
wal 實現了日志自動切分,當日志數據大于默認的 64M 時就會生成新的文件寫入日志,日志的切分通過 wal.go 文件中的 cut 方法來實現。cut 方法只會在調用 wal 中的 Save 方法才會觸發調用,新文件的第一條記錄就是上一個 wal 文件最后的 crc。
日志 compact
wal 沒有實現日志的自動 compact,系統只提供了 MemoryStorage 的日志 compact 方法(需要用戶主動調用)。
file_pipeline 模塊
wal 新建新的文件時都是先新建一個 tmp 文件,當所有操作都完成后再重命名這個文件。wal 使用 file_pipeline 這個模塊在后臺啟動一個協程時刻準備一個臨時文件以供使用,從而避免臨時創建文件的開銷。
etcd snap 介紹
etcd raft 自帶了 go.etcd.io/etcd/etcdserver/api/snap 模塊來實現快照的存儲。
文件組織
在 snap 模塊中一個快照用一個后綴名為.snap 的文件存儲,文件格式為 -.snap, term 和 index 分別代表快照日志所處的 term 和 index。每個快照具體存儲結構如下圖:
詳細介紹
系統可以有多個快照,snap 模塊使用 Snapshotter 結構統一管理快照。
type Snapshotter struct {
lg *zap.Logger
dir string
}
上面 snapshotter 的結構代碼,snapshotter 主要用于存儲和讀取快照。
快照具體存儲的內容需要用戶來指定,例如在 raft 的官方例子中直接將當時的 kv 數據 Marshal 之后存儲到快照中。
func (s *kvstore) getSnapshot() ([]byte, error) {
s.mu.RLock()
defer s.mu.RUnlock()
return json.Marshal(s.kvStore)
}
何時打快照
在 etcd-raft 中用戶可以選擇何時打快照,在 etcd 的官方案例中打快照的方法是 maybeTriggerSnapshot(),這個方法在節點的 Ready() 方法返回時調用,當前提交的 index 值與上一次大快照的 index 值大于 10000 時會打新的快照。
etcd MemoryStorage 介紹
MemoryStorage 用于存儲 raft 節點臨時的數據,包括 entrys、快照等。用戶將數據存儲到 memoryStorage 中,raft 節點也會使用這些數據。包括 entrys 的傳遞、快照的發送等都是從 memoryStorage 中發送。
// MemoryStorage implements the Storage interface backed by an
// in-memory array.
type MemoryStorage struct {
// Protects access to all fields. Most methods of MemoryStorage are
// run on the raft goroutine, but Append() is run on an application
// goroutine.
sync.Mutex
hardState pb.HardState
snapshot pb.Snapshot
// ents[i] has raft log position i+snapshot.Metadata.Index
ents []pb.Entry
}
memoryStorage 會存儲最新的 entrys(包括哪些沒有 commit)、快照和狀態,用戶在收到其它節點發送的相關數據時需要將數據存儲到 memorystorage 中。
到此,關于“wal 怎么實現日志讀寫”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注丸趣 TV 網站,丸趣 TV 小編會繼續努力為大家帶來更多實用的文章!