Go存儲基礎使用direct io方法實例
Go 存儲編程怎么使用 O_DIRECT 模式?
今天分享一個存儲細節(jié),Go 存儲編程怎么使用 O_DIRECT 模式?
之前提過很多次,操作系統(tǒng)的 IO 過文件系統(tǒng)的時候,默認是會使用到 page cache,并且采用的是 write back 的方式,系統(tǒng)異步刷盤的。由于是異步的,如果在數(shù)據(jù)還未刷盤之前,掉電的話就會導致數(shù)據(jù)丟失。
如果想要明確數(shù)據(jù)寫到磁盤有兩種方式:要么就每次寫完主動 sync 一把,要么就使用 direct io 的方式,指明每一筆 io 數(shù)據(jù)都要寫到磁盤才返回。
那么在 Go 里面怎么使用 direct io 呢?
有同學可能會說,那還不簡單,open 文件的時候 flag 用 O_DIRECT 嘛,然后。。。
是嗎?有這么簡單嗎?
提兩個問題,童鞋們可以先思考下:
- O_DIRECT 這個定義在 Go 標準庫的哪個文件?
- direct io 需要 io 大小和偏移扇區(qū)對齊,且還要滿足內存 buffer 地址的對齊,這個怎么做到?
O_DIRECT 的知識點
在此之前,先回顧 O_DIRECT 相關的知識。direct io 也就是常說的 DIO,是在 Open 的時候通過 flag 來指定 O_DIRECT 參數(shù),之后的數(shù)據(jù)的 write/read 都是繞過 page cache,直接和磁盤操作,從而避免了掉電丟數(shù)據(jù)的尷尬局面,同時也讓應用層可以自己決定內存的使用(避免不必要的 cache 消耗)。
direct io 一般解決兩個問題:
- 數(shù)據(jù)落盤,確保掉電不丟失;
- 減少內核 page cache 的內存使用,業(yè)務層自己控制內存,更加靈活;
direct io 模式需要用戶保證對齊規(guī)則,否則 IO 會報錯,有 3 個需要對齊的規(guī)則:
- IO 的大小必須扇區(qū)大?。?12字節(jié))對齊
- IO 偏移按照扇區(qū)大小對齊;
- 內存 buffer 的地址也必須是扇區(qū)對齊;
思考標題
為什么 Go 的 O_DIRECT 知識點值得一提?
以下按照兩層意思分析思考。
第一層意思:O_DIRECT 平臺不兼容
劃重點:Go 標準庫 os 中的是沒有 O_DIRECT 這個參數(shù)的。
為什么呢?
Go os 庫實現(xiàn)的是各個操作系統(tǒng)兼容的實現(xiàn),direct io 這個在不同的操作系統(tǒng)下實現(xiàn)形態(tài)不一樣。其實 O_DIRECT
這個 Open
flag 參數(shù)本就是只存在于 linux 系統(tǒng)。
以下才是各個平臺兼容的 Open 參數(shù) ( 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. )
發(fā)現(xiàn)了嗎?O_DIRECT 根本不在其中。O_DIRECT 其實是和系統(tǒng)平臺強相關的一個參數(shù)。
問題來了,那么 O_DIRECT 定義在那里?
跟操作系統(tǒng)強相關的自然是定義在 syscall 庫中:
// syscall/zerrors_linux_amd64.go const ( // ... O_DIRECT = 0x4000 )
怎么打開文件呢?
// +build linux // 指明在 linux 平臺系統(tǒng)編譯 fp := os.OpenFile(name, syscall.O_DIRECT|flag, perm)
第二層意思:Go 無法精確控制內存分配地址
標準庫或者內置函數(shù)沒有提供讓你分配對齊內存的函數(shù)。
direct io 必須要滿足 3 種對齊規(guī)則:io 偏移扇區(qū)對齊,長度扇區(qū)對齊,內存 buffer 地址扇區(qū)對齊。前兩個還比較好滿足,但是分配的內存地址作為一個小程序員無法精確控制。
先對比回憶下 c 語言,libc 庫是調用 posix_memalign
直接分配出符合要求的內存塊。go 里面怎么做?
先問個問題:Go 里面怎么分配 buffer 內存?
io 的 buffer 其實就是字節(jié)數(shù)組嘛,很好回答,最常見自然是用 make 來分配,如下:
buffer := make([]byte, 4096)
那這個地址是對齊的嗎?
答案是:不確定。
那怎么才能獲取到對齊的地址呢?
劃重點:方法很簡單,就是先分配一個比預期要大的內存塊,然后在這個內存塊里找對齊位置。 這是一個任何語言皆通用的方法,在 Go 里也是可用的。
什么意思?
比如,我現(xiàn)在需要一個 4096 大小的內存塊,要求地址按照 512 對齊,可以這樣做:
- 先分配要給 4096 + 512 大小的內存塊,假設得到的地址是 p1 ;
- 然后在 [ p1, p1+512 ] 這個地址范圍找,一定能找到 512 對齊的地址(這個能理解嗎?),假設這個地址是 p2 ;
- 返回 p2 這個地址給用戶使用,用戶能正常使用 [ p2, p2 + 4096 ] 這個范圍的內存塊而不越界;
以上就是基本原理了,童鞋理解了不?下面看下代碼怎么寫。
const ( AlignSize = 512 ) // 在 block 這個字節(jié)數(shù)組首地址,往后找,找到符合 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 函數(shù)分配出來的內存一定是 512 地址對齊的。
有啥缺點嗎?
浪費空間嘛。 命名需要 4k 內存,實際分配了 4k+512 。
我太懶了,一行代碼都不愿多寫,有開源的庫嗎?
還真有,推薦個:https://github.com/ncw/directio ,內部實現(xiàn)極其簡單,就是上面的一樣。
使用姿勢很簡單:
步驟一:O_DIRECT 模式打開文件:
// 創(chuàng)建句柄 fp, err := directio.OpenFile(file, os.O_RDONLY, 0666)
封裝關鍵在于:O_DIRECT 是從 syscall 庫獲取的。
步驟二:讀數(shù)據(jù)
// 創(chuàng)建地址按照 4k 對齊的內存塊 buffer := directio.AlignedBlock(directio.BlockSize) // 把文件數(shù)據(jù)讀到內存塊中 _, err := io.ReadFull(fp, buffer)
關鍵在于:buffer 必須是特制的 [ ]byte 數(shù)組,而不能僅僅根據(jù) make([ ]byte, 512 ) 這樣去創(chuàng)建,因為僅僅是 make 無法保證地址對齊。
總結
- direct io 必須滿足 io 大小,偏移,內存 buffer 地址三者都扇區(qū)對齊;
- O_DIRECT 不在 os 庫,而在于操作系統(tǒng)相關的 syscall 庫;
- Go 中無法直接使用 make 來分配對齊內存,一般的做法是分配一塊大一點的內存,然后在里面找到對齊的地址即可;
以上就是Go存儲基礎使用direct io方法實例的詳細內容,更多關于Go存儲direct io 的資料請關注腳本之家其它相關文章!
相關文章
在Go語言開發(fā)中實現(xiàn)高性能的分布式日志收集的方法
本文介紹了在Go語言開發(fā)中實現(xiàn)高性能分布式日志收集的關鍵步驟和考慮因素,包括日志生成與采集、日志傳輸、日志收集器的高性能網(wǎng)絡I/O、日志存儲與分析、監(jiān)控與告警系統(tǒng)、擴展性與可維護性等方面,本文給大家介紹的非常詳細,感興趣的朋友一起看看吧2025-01-01Go語言調用SiliconFlow實現(xiàn)文本轉換為MP3格式
這篇文章主要為大家詳細介紹了Go語言如何調用?SiliconFlow?語音生成?API?的腳本,用于將文本轉換為?MP3?格式的語音文件,感興趣的小伙伴可以了解下2025-02-02