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