Go 存儲基礎 - 怎麼使用 direct io ?
Go 存儲編程怎麼使用 O_DIRECT 模式?今天就分享這個存儲細節,
之前提過很多次,操作系統的 IO 過文件系統的時候,**默認是會使用到 page cache,並且採用的是 write back 的方式,系統異步刷盤的。**由於是異步的,如果在數據還未刷盤之前,掉電的話就會導致數據丟失。
如果想要明確數據寫到磁盤有兩種方式:要麼就每次寫完主動 sync 一把,要麼就使用 direct io 的方式,指明每一筆 io 數據都要寫到磁盤才返回。
那麼在 Go 裏面怎麼使用 direct io 呢?
有同學可能會說,那還不簡單,open 文件的時候 flag 用 O_DIRECT 嘛,然後。。。
**是嗎?有這麼簡單嗎?**提兩個問題,童鞋們可以先思考下:
-
O_DIRECT 這個定義在 Go 標準庫的哪個文件?
-
direct io 需要 io 大小和偏移扇區對齊,且還要滿足內存 buffer 地址的對齊,這個怎麼做到?
在此之前,先回顧 O_DIRECT 相關的知識。direct io 也就是常說的 DIO,是在 Open 的時候通過 flag 來指定 O_DIRECT 參數,之後的數據的 write/read 都是繞過 page cache,直接和磁盤操作,從而避免了掉電丟數據的尷尬局面,同時也讓應用層可以自己決定內存的使用(避免不必要的 cache 消耗)。
direct io 一般解決兩個問題:
-
數據落盤,確保掉電不丟失;
-
減少內核 page cache 的內存使用,業務層自己控制內存,更加靈活;
direct io 模式需要用戶保證對齊規則,否則 IO 會報錯,有 3 個需要對齊的規則:
-
IO 的大小必須扇區大小(512 字節)對齊
-
IO 偏移按照扇區大小對齊;
-
內存 buffer 的地址也必須是扇區對齊;
direct io 模式卻不對齊會怎樣?
讀寫報錯唄,會拋出 “無效參數” 的錯誤。
思考問題
爲什麼 Go 的 O_DIRECT 知識點值得一提?
以下按照兩層意思分析思考。
1 第一層意思:O_DIRECT 平臺不兼容
劃重點:Go 標準庫 os 中的是沒有 O_DIRECT 這個參數的。
爲什麼呢?
Go os 庫實現的是各個操作系統兼容的實現,direct io 這個在不同的操作系統下實現形態不一樣。其實 O_DIRECT
這個 Open
flag 參數本就是隻存在於 linux 系統。
以下才是各個平臺兼容的 Open 參數 (os/file.go)。
const (
// Exactly one of O_RDONLY, O_WRONLY, or O_RDWR must be specified.
O_RDONLY int = syscall.O_RDONLY // open the file read-only.
O_WRONLY int = syscall.O_WRONLY // open the file write-only.
O_RDWR int = syscall.O_RDWR // open the file read-write.
// The remaining values may be or'ed in to control behavior.
O_APPEND int = syscall.O_APPEND // append data to the file when writing.
O_CREATE int = syscall.O_CREAT // create a new file if none exists.
O_EXCL int = syscall.O_EXCL // used with O_CREATE, file must not exist.
O_SYNC int = syscall.O_SYNC // open for synchronous I/O.
O_TRUNC int = syscall.O_TRUNC // truncate regular writable file when opened.
)
發現了嗎?O_DIRECT 根本不在其中。O_DIRECT 其實是和系統平臺強相關的一個參數。
問題來了,那麼 O_DIRECT 定義在那裏?
跟操作系統強相關的自然是定義在 syscall 庫中:
// syscall/zerrors_linux_amd64.go
const (
// ...
O_DIRECT = 0x4000
)
怎麼打開文件呢?
// +build linux
// 指明在 linux 平臺系統編譯
fp := os.OpenFile(name, syscall.O_DIRECT|flag, perm)
2 第二層意思:Go 無法精確控制內存分配地址
標準庫或者內置函數沒有提供讓你分配對齊內存的函數。
direct io 必須要滿足 3 種對齊規則:io 偏移扇區對齊,長度扇區對齊,內存 buffer 地址扇區對齊。前兩個還比較好滿足,但是分配的內存地址作爲一個小程序員無法精確控制。
先對比回憶下 c 語言,libc 庫是調用 posix_memalign
直接分配出符合要求的內存塊。go 裏面怎麼做?
先問個問題:Go 裏面怎麼分配 buffer 內存?
io 的 buffer 其實就是字節數組嘛,很好回答,最常見自然是用 make 來分配,如下:
buffer := make([]byte, 4096)
那這個地址是對齊的嗎?
答案是:不確定。
那怎麼才能獲取到對齊的地址呢?
劃重點:方法很簡單,就是先分配一個比預期要大的內存塊,然後在這個內存塊裏找對齊位置。 這是一個任何語言皆通用的方法,在 Go 裏也是可用的。
什麼意思?
比如,我現在需要一個 4096 大小的內存塊,要求地址按照 512 對齊,可以這樣做:
-
先分配要給 4096 + 512 大小的內存塊,假設得到的地址是 p1 ;
-
然後在 [p1, p1+512] 這個地址範圍找,一定能找到 512 對齊的地址(這個能理解嗎?),假設這個地址是 p2 ;
-
返回 p2 這個地址給用戶使用,用戶能正常使用 [p2, p2 + 4096] 這個範圍的內存塊而不越界;
以上就是基本原理了,童鞋理解了不?下面看下代碼怎麼寫。
const (
AlignSize = 512
)
// 在 block 這個字節數組首地址,往後找,找到符合 AlignSize 對齊的地址,並返回
// 這裏用到位操作,速度很快;
func alignment(block []byte, AlignSize int) int {
return int(uintptr(unsafe.Pointer(&block[0])) & uintptr(AlignSize-1))
}
// 分配 BlockSize 大小的內存塊
// 地址按照 512 對齊
func AlignedBlock(BlockSize int) []byte {
// 分配一個,分配大小比實際需要的稍大
block := make([]byte, BlockSize+AlignSize)
// 計算這個 block 內存塊往後多少偏移,地址才能對齊到 512
a := alignment(block, AlignSize)
offset := 0
if a != 0 {
offset = AlignSize - a
}
// 偏移指定位置,生成一個新的 block,這個 block 將滿足地址對齊 512;
block = block[offset : offset+BlockSize]
if BlockSize != 0 {
// 最後做一次校驗
a = alignment(block, AlignSize)
if a != 0 {
log.Fatal("Failed to align block")
}
}
return block
}
所以,通過以上 AlignedBlock 函數分配出來的內存一定是 512 地址對齊的。
有啥缺點嗎?
浪費空間嘛。 命名需要 4k 內存,實際分配了 4k+512 。
3 我太懶了,一行代碼都不願多寫,有開源的庫嗎?
還真有,推薦個:https://github.com/ncw/directio ,內部實現極其簡單,就是上面的一樣。
使用姿勢很簡單:
步驟一:O_DIRECT 模式打開文件:
// 創建句柄
fp, err := directio.OpenFile(file, os.O_RDONLY, 0666)
封裝關鍵在於:O_DIRECT 是從 syscall 庫獲取的。
步驟二:讀數據
// 創建地址按照 4k 對齊的內存塊
buffer := directio.AlignedBlock(directio.BlockSize)
// 把文件數據讀到內存塊中
_, err := io.ReadFull(fp, buffer)
關鍵在於:buffer 必須是特製的 []byte 數組,而不能僅僅根據 make([]byte, 512 ) 這樣去創建,因爲僅僅是 make 無法保證地址對齊。
總結
-
direct io 必須滿足 io 大小,偏移,內存 buffer 地址三者都扇區對齊;
-
O_DIRECT 不在 os 庫,而在於操作系統相關的 syscall 庫;
-
Go 中無法直接使用 make 來分配對齊內存,一般的做法是分配一塊大一點的內存,然後在裏面找到對齊的地址即可;
堅持思考,方向比努力更重要。關注我:奇伢雲存儲
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/gW_3JD52rtRdEqXvyg-lJQ