欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Go存儲(chǔ)基礎(chǔ)使用direct io方法實(shí)例

 更新時(shí)間:2023年12月01日 09:50:14   作者:qiya  
這篇文章主要介紹了Go存儲(chǔ)基礎(chǔ)之如何使用direct io方法實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

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í)例詳解

    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)高性能的分布式日志收集的方法

    本文介紹了在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ù)端

    一文帶你學(xué)會(huì)使用Go語(yǔ)言實(shí)現(xiàn)自己的MCP服務(wù)端

    這篇文章將帶大家速覽MCP的核心概念,并以Go語(yǔ)言為例,介紹如何開發(fā)MCP服務(wù)端和客戶端,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以參考一下
    2025-04-04
  • Go語(yǔ)言中時(shí)間time相關(guān)處理方法詳解

    Go語(yǔ)言中時(shí)間time相關(guān)處理方法詳解

    在Go語(yǔ)言中,time?包是處理時(shí)間和日期的核心,它提供了豐富的函數(shù)和方法,用于顯示、測(cè)量、計(jì)算、格式化、解析時(shí)間等,本文給大家詳細(xì)介紹了Go時(shí)間time相關(guān)處理方法的相關(guān)資料,需要的朋友可以參考下
    2024-10-10
  • go mayfly開源項(xiàng)目代碼結(jié)構(gòu)設(shè)計(jì)

    go 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)原理與使用

    詳解Go語(yǔ)言中泛型的實(shí)現(xiàn)原理與使用

    本文是對(duì)泛型的基本思想及其在?Go?中的實(shí)現(xiàn)的一個(gè)比較容易理解的介紹,同時(shí)也是對(duì)圍繞泛型的各種性能討論的簡(jiǎn)單總結(jié),感興趣的可以學(xué)習(xí)一下
    2022-05-05
  • Go安裝和環(huán)境配置圖文教程

    Go安裝和環(huán)境配置圖文教程

    本文主要介紹了Go安裝和環(huán)境配置圖文教程,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-04-04
  • Go語(yǔ)言調(diào)用SiliconFlow實(shí)現(xiàn)文本轉(zhuǎn)換為MP3格式

    Go語(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
  • 一文詳解Golang中的切片數(shù)據(jù)類型

    一文詳解Golang中的切片數(shù)據(jù)類型

    這篇文章主要介紹了一文詳解Golang中的切片數(shù)據(jù)類型,切片是一個(gè)種特殊的數(shù)組。是對(duì)數(shù)組的一個(gè)連續(xù)片段的引用,所以切片是一個(gè)引用類型
    2022-09-09
  • Golang開發(fā)命令行之flag包的使用方法

    Golang開發(fā)命令行之flag包的使用方法

    這篇文章主要介紹Golang開發(fā)命令行及flag包的使用方法,日常命令行操作,相對(duì)應(yīng)的眾多命令行工具是提高生產(chǎn)力的必備工具,本文圍繞該內(nèi)容展開話題,需要的朋友可以參考一下
    2021-10-10

最新評(píng)論