理解 QEMU/KVM 和 Ceph(1):QEMU-KVM 和 Ceph RBD 的 緩存機制總結

本系列文章會總結 QEMU/KVM 和 Ceph 之間的整合:php

(1)QEMU-KVM 和 Ceph RBD 的 緩存機制總結html

(2)QEMU 的 RBD 塊驅動(block driver)node

(3)存儲卷掛接和設備名稱linux

 

  QEMU-KVM 的緩存機制的概念不少,Linux/KVM I/O 軟件棧的層次也不少,網上介紹其緩存機制的文章不少。邊學習邊總結。本文結合 Ceph 在 QEMU/KVM 虛機中的使用,總結一下二者結合時緩存的各類選項和原理。git

1. QEMU/KVM 緩存機制

   先以客戶機(Guest OS) 中的應用寫本地磁盤爲例進行介紹。客戶機的本地磁盤,實際上是 KVM 主機上的一個鏡像文件虛擬出來的,所以,客戶機中的應用寫其本地磁盤,其實就是寫到KVM主機的本地文件內,這些文件是保存在 KVM 主機本地磁盤上。github

  先來看看 I/O 協議棧的層次和各層次上的緩存狀況。數據庫

1.1 Linux 內核中的緩存

  熟悉 Linux Kernel 的人都知道在內核的存儲體系中主要有兩種緩存,一是 Page Cache,二是 Buffer Cache。Page Cache 是在 Linux IO 棧中爲文件系統服務的緩存,而 Buffer Cache 是處於更下層的 Block Device 層,因爲應用大部分使用的存儲數據都是基於文件系統,所以 Buffer Cache 實際上只是引用了 Page Cache 的數據,而只有在直接使用塊設備跳過文件系統時,Buffer Cache 才真正掌握緩存。關於 Page Cache 和 Buffer Cache 更多的討論參加 What is the major difference between the buffer cache and the page cache?後端

  這些 Cache 都由內核中專門的數據回寫線程負責來刷新到塊設備中,應用可使用如 fsync, fdatasync(見下面第三部分說明)之類的系統調用來完成強制執行對某個文件數據的回寫。像數據一致性要求高的應用如 MySQL 這類數據庫服務一般有本身的日誌用來保證事務的原子性,日誌的數據被要求每次事務完成前經過 fsync 這類系統調用強制寫到塊設備上,不然可能在系統崩潰後形成數據的不一致。而 fsync  的實現取決於文件系統,文件系統會將要求數據從緩存中強制寫到持久設備中。緩存

 仔細地看一下 fsync 函數(來源): 安全

 fsync() transfers ("flushes") all modified in-core data of (i.e., modified buffer cache pages for) the file referred to by the file
       descriptor fd to the disk device (or other permanent storage device) so that all changed information can be retrieved even after the  system crashed or was rebooted.  This includes writing through or flushing a disk cache if present.  The call blocks until the device
       reports that the transfer has completed.  It also flushes metadata information associated with the file (see stat(2)).

 它會 flush 系統中全部的 cache,包括 Page cache 和 Disk write cache 以及 RBDCache,將數據放入持久存儲。也就說說,操做系統在 flush Page cache 的時候, RBDCache 也會被 flush。 

1.2 I/O 協議棧層次及緩存

1.2.1 組成

來源

主要組成部分:

  • Guest OS 和 HOST OS Page Cache

Page Cache 是客戶機和主機操做系統維護的用來提升存儲 I/O 性能的緩存,它是 Linux 虛擬文件系統緩存的一部分,位於操做系統內存中,它是易失性的,所以,在操做系統奔潰或者系統掉電時,這些數據會消失。數據是否寫入 Page cache 能夠被控制。當會寫入 page cache 時,當數據被寫入 page cache 後,應用就認爲寫入完成了,隨後的讀操做也會從 page cache 中讀取數據,這樣性能會提升。可使用 fsync 來將數據從 page cache 中拷貝到持久存儲。

在 KVM 環境中,host os 和 guest os 都有 page cache,所以,最好是能繞過一個來提升性能。

    • 若是 guest os 中的應用使用 direct I/O 方式,guest os 中 page cache 會被繞過。
    • 若是 guest os 使用 no cache 方式,host os 的 page cache 會被繞過。

該緩存的特色是讀的時候,操做系統先檢查頁緩存裏面是否有須要的數據,若是沒有就從設備讀取,返回給用戶的同時,加到緩存一份;寫的時候,直接寫到緩存去,再由後臺的進程按期涮到磁盤去。這樣的機制看起來很是的好,在實踐中也效果很好。

考慮到其易失性,須要考慮它的大小,特別是在 KVM 主機上。如今 KVM 主機的內存能夠很大。其內存越大, 那麼在 Page cache 中尚未 flush 到磁盤(虛擬或者物理的)的髒數據就越多,其丟失的後果就越嚴重。默認的話,Linux 2.6.32 在髒數據達到內存的 10% 的時候會自動開始 flush。

  • Guest Disk (virtual disk device):客戶機虛機磁盤設備
  • QEMU image:QEMU 鏡像文件
  • Physical disk cache

這是磁盤的 write cache,它會提升數據到存儲的寫性能。寫到 disk write cache 後,寫操做會被認爲完成了,即便數據還沒真正被寫入物理磁盤。這樣,若是 disk write cache 沒有備份電池的話,斷電將致使還沒有寫入物理磁盤的數據丟失。要強制數據被寫入磁盤,應用能夠經過操做系統能夠發出 fsync 命令。所以,disk write cache 會提到寫I/O 性能,可是,須要確保應用和存儲棧會將數據寫入磁盤中。若是 disk write cache 被關閉,那麼寫性能將降低,可是斷電時數據丟失將會避免。

  • Physical disk platter:物理磁盤

1.2.2 GUEST 應用讀 I/O 過程

  1. GUEST OS 中的一個應用發出 read request。
  2. OS 在 guest page cache 中檢查。若是有(hit),則直接將 data 從 guest page cache 拷貝到 application space。
  3. 若是沒有(miss),請求被轉到 guest virtual disk。該 request 會被 QEMU 轉化爲對 host 上鏡像文件的 read request。
  4. Host OS 在 HOST Page cache 中檢查。若是 hit,則經過 QEMU 將 data 從 host page cache 傳到 guest page cache,再拷貝到 application space。
  5. 若是沒有(miss),則啓動 disk (或者 network)I/O 請求去從實際文件系統中讀取數據,讀到後再寫入 host page cache,在寫入 guest page cache,再到 GUEST OS application space。

從該過程能夠看出:

  • 兩重 page cache 會對數據重複保存,這會帶來內存浪費
  • 兩重 page cache 也會提升 hit ratio,由於每每 guest page cache 比 host page cache 會小不少

QEMU-KVM Linux 支持關閉和開啓任一一個 Page cache,也就是說有四種組合模式,分別會帶來不一樣的效果。在各類I/O的過程當中,最好是繞過一個或者兩個 Page cache。

1.2.3 Guest 應用 寫 I/O 過程

寫 I/O 過程比較複雜,本文其他部分會詳細闡述。從 1.3 表格總結,基本上

  • writeback/unsafe:app ----qemu write----> host page cache  --- os flush ---> disk cache --- hw flush ---> disk
  • none:                   app  --- qemu write----> disk write cache  ---- hw flush ---> disk
  • writethrough:        app --- qemu write----> host page cache, disk
  • directsync:            app --- qemu write ---> disk

關於 guest os page cache,看起來它主要是做爲讀緩存,而對於寫,沒有一種模式是以寫入它做爲寫入結束標誌的。

1.3 客戶機磁盤(drive)的緩存模式

在 libvirt xml 中使用 'cache' 參數來指定driver的緩存模式,好比:

<disk type='file' device='disk'>  #對於 type,'file' 表示是 host 上的文件,'network' 表示經過網絡訪問,好比Ceph
          <driver name='qemu' type='raw' cache='writeback'/> 

QEMU/KVM 支持以下這些緩存模式做爲 ‘cache’ 的可選值:

緩存模式 說明 GUEST OS Page cache Host OS Page cache Disk write cache 被認爲數據寫入成功     數據安全性
cache = unsafe 跟 writeback 相似,只是會忽略 GUEST OS 的 flush 操做,徹底由 HOST OS 控制 flush
Bypass(?不肯定)

E E Host page cache 最不安全,只有在特定的場合纔會使用
Cache=writeback I/O 寫到 HOST OS Page cache 就算成功,支持 GUEST OS flush 操做。
效率最快,可是也最不安全
Bypass?不肯定 E E Host Page Cache 不安全. (only for temporary data where potential data loss is not a concern  )
Cache=none

客戶機的I/O 不會被緩存到 page cache,而是會放在 disk write cache。

這種模式對寫效率比較好,由於是寫到 disk cache,可是讀效率不高,由於沒有放到 page cache。所以,能夠在大 I/O 寫需求時使用這種模式。


綜合考慮下,基本上這是最優模式,並且是支持實時遷移的惟一模式

也就是常說的 O_DIRECT I/O 模式

 Bypass  Bypass  E Disk write cache 不安全. 若是要保證安全的話,須要disk cache備份電池或者電容,或者使用 fync
Cache=writethrough

I/O 數據會被同步寫入 Host Page cache 和 disk。至關於每寫一次,就會 flush 一次,將 page cache 中的數據寫入持久存儲。

這種模式,會將數據放入 Page cache,所以便於未來的讀;而繞過 disk write cache,會致使寫效率較低。所以,這是較慢的模式,適合於寫I/O不大,可是讀I/O相對較大的狀況,最好是用在小規模的有低 I/O 需求客戶機的場景中。
當不須要支持實時遷移時,若是不支持writeback 則可用。

也就是常說的 O_SYNC I/O 模式

E E Bypass disk 安全
Cache=DirectSync

 跟 writethrough 相似,只是不寫入 HOST OS Page cache

也就是常說的 O_DIRECT & O_SYNC 模式

Bypass Bypass Bypass disk 同 」O_SYNC「,對一些數據庫應用來講,每每會直接使用這種模式,直接將數據寫到數據盤
cache=default

使用各類driver 類型的默認cache 模式

qcow2:默認 writeback

         
  • 性能:  writeback = unsafe > none > writethrough = directsync
  • 安全性: writethrough = directsync > none > writeback > unsafe

看看性能比較:

基本結論:

  • 各類模式的性能差異很是大
  • 對於數據庫這樣的應用,使用 directsync 模式,數據直接寫入物理磁盤纔算成功
  • 對於重要的數據或者小 I/O 的場景,使用 writethrough
  • 對於通常的應用,或者大 I/O 場景,使用 none。這個能夠說是大部分狀況下的最優選項。
  • 對於丟失了也無所謂的數據,可使用 writeback

1.4 KVM Write barrier

1.4.1 什麼是 KVM write barrier

  上面的基本結論中,writethrough 是最安全的,可是效率也是最低的。它將數據放在 HOST Page Cache 中,一方面來支持讀緩存,另外一方面,在每個 write 操做後,都執行 fsync,確保數據被寫入物理存儲。只有在數據被寫入磁盤後,寫操做纔會標記爲成功。這種模式下,客戶機的 virtual storage adapter 會被通知不會使用 writeback 模式,所以,它不會主動發送 fsync 命令,由於它是重複的,不須要的。

  那還有沒有什麼辦法使它在保持數據可靠性的同時,使它的效率提升一些呢?答案是 KVM Write barrier 功能。新的 KVM 版本中,啓用了 「barrier-passing」 功能,它能保證在無論是用什麼緩存模式下,將客戶機上應用寫入的數據 100% 寫入持久存儲。

  好吧,這真是個神器。。那它是如何實現的呢?以 fio 工具爲例,在支持 write barrier 的客戶機操做系統上,在使用 direct 和 sync 參數的狀況下,會使用這種模式。它在寫入部分數據之後,會使得操做系統發出一個 fdatasync 命令,這樣 QEMU-KVM 就會將緩存中的數據 flush 到物理磁盤上。

基本過程:

  1. 在一個會話中寫入數據
  2. 發出 barrier request
  3. 會話中的全部數據被 flush 到物理磁盤
  4. 繼續下一個會話

 看起來和 writethrough 差很少是吧。可是它的效率比 writethrough 高。二者的區別在於,writethrough 是每次 write 都會發 fsync,而 barrier-passing 是在若干個寫操做或者一個會話以後發 fdatasync 命令,所以其效率更高。

  也能夠看到,使用它是有條件的:

  • KVM 版本較新
  • 客戶機操做系統支持:在較新的 Linux 發行版中都會支持
  • 客戶機中的文件系統支持 barrier (ext4 支持並默認開啓;ext3 支持但默認不開啓),並且整個 I/O 協議棧中的各個層次都支持 flush 操做
  • 應用須要在須要的時候發出 flush 指令。

  也能夠看到,應用在須要的時候發出 flush 指令是關鍵。一方面,Cache 都由內核中專門的數據回寫線程負責來刷新到塊設備中;另外一方面,應用可使用如 fsync(2), fdatasync(2) 之類的系統調用來完成強制執行對某個文件數據的回寫。像數據一致性要求高的應用如 MySQL 這類數據庫服務一般有本身的日誌用來保證事務的原子性,日誌的數據被要求每次事務完成前經過 fsync(2) 這類系統調用強制寫到塊設備上,不然可能在系統崩潰後形成數據的不一致。而 fsync(2) 的實現取決於文件系統,文件系統會將要求數據從緩存中強制寫到持久設備中。相似地,支持 librbd 的QEMU 在適當的時候也會發出 flush 指令。

  以 fio 爲例,設置有兩個參數時,會有 flush 指令發出:

fsync=int
How many I/Os to perform before issuing an fsync(2) of dirty data. If 0, don't sync. Default: 0.
fdatasync=int
Like fsync, but uses fdatasync(2) instead to only sync the data parts of the file. Default: 0.

須要注意的是,頻繁的發送(int 值設置的比較小),會影響 IOPS 的值。爲了測得最大的IOPS,能夠在測試準備階段發一個sync,而後再收集階段就不發sync,徹底由 RBDCache 本身的機制去 flush;或者須要的話,把 int 值設得比較大,來模擬一些應用場景。

1.4.2 KVM write barrier 和 KVM 緩存模式的結合

考慮到 KVM write barrier 的原理和 KVM 各類緩存模式的原理,顯而易見,writeback + barrier 的方式下,能夠實現 效率最高+數據安全 這種最優效果。

1.5 小結

  • 在客戶機能夠啓用 write barrier 時,使用 write-back or nocache + barrier,而後應用會在合適的時候發出 flush 指令。
  • 在客戶機不支持 write barrier 時,若是對讀敏感應用,使用 write-back (可使用 pagecache);對須要同步數據的應用,使用 noncache;最安全的狀況下,使用 writethrough。
  • 對於一些能過備用電池或者別的技術(好比設備上有電容等)保證了在掉電狀況下數據也不會丟失的狀況下,barrier 最好被禁止。好比企業存儲的Adatper,或者 SSD。
    • 」If the device does not need cache flushes it should not report requiring flushes, in which case nobarrier will be a noop.「
    • 」With a RAID controller with battery backed controller cache and cache in write back mode, you should turn off barriers - they are unnecessary in this case, and if the controller honors the cache flushes, it will be harmful to performance. But then you *must* disable the individual hard disk write cache in order to ensure to keep the filesystem intact after a power failure.「。
    • 一個例子是,Ceph OSD 節點上的 SSD 分區,通常都使用 」nobarrier「參數 來禁用 barrier。 

1.6 Linux page cache 補充

Linux 系統中被用於 Page cache 的主內存能夠經過 free -m 命令來查看:

root@controller:~# free -m
             total       used       free     shared    buffers     cached
Mem:          4867       3586       1280          0        193        557
-/+ buffers/cache:       2834       2032
Swap:         2022          0       2022

寫 Page cache:

(1)當數據被寫時,一般狀況下,它首先會被寫入 page cache,被看成一個  dirty page 來管理。’dirty‘ 的意思是,數據還保存在 page cache 中,還須要被寫入底下的持久存儲。dirty pages 中的內容會被系統週期性地、以及使用諸如 sync 或者 fsync 的系統調用來寫入持久存儲。

root@controller:~# cat /proc/meminfo | grep Dirty
Dirty:               148 kB
root@controller:~# sync
root@controller:~# cat /proc/meminfo | grep Dirty
Dirty:                 0 kB
sync writes any data buffered in memory out to disk. This can include (but is not limited to) modified superblocks, modified inodes, and delayed reads and writes. 

(2)當數據被從持久性存儲讀出時,它也會被寫入 page cache。所以,當連續兩次讀時,第二次會比第一次快,由於第一次讀後把數據寫入了 page cache,第二次就直接從這裏讀了。完整過程以下:

 

須要注意的是,須要結合應用的須要,來決定是否在寫數據時一併寫入 page cache。

2. QEMU+RBDCache

  QEMU 可以支持將主機的一個塊設備映射給客戶機,可是從 0.15 版本開始,就再也不須要先將一個 Ceph volume 映射到主機再給客戶機了。如今,QEMU 能夠直接經過 librbd 來象 virtual block device 同樣訪問 Ceph image。這既提升了性能,也使得可使用 RBDCache 了。

  RBDCache 是 Ceph 的塊存儲接口實現庫 Librbd 用來在客戶端側緩存數據的目的,它主要提供了讀數據緩存,寫數據匯聚寫回的目的,用來提升順序讀寫的性能。須要說明的是,Ceph 既支持之內核模塊的方式來實現對 Linux 動態增長塊設備,也支持以 QEMU Block Driver 的形式給使用 QEMU 虛擬機增長虛擬塊設備,並且二者使用不一樣的庫,前者是內核模塊的形式,後者是普通的用戶態庫,本文討論的 RBDCache 針對後者,前者使用內核的 Page Cache 達到目的。

2.1 librbd I/O 協議棧

  從這個棧能夠看出來,RBDCache 相似於磁盤的 write cache。它應該有三個功能:

  • 寫緩存:開啓時,librdb 將數據寫入 RBDCache,而後在被 flush 到 Ceph 集羣,其效果就是多個寫操做被合併,可是有必定的時間延遲。
  • 讀緩存:數據會在緩存中被保留一段時間,這期間的 librbd 讀數據的話,會直接從緩存中讀取,提升讀效率。
  • 合併寫操做:對同一個 OSD 上的多個寫操做,應該會合併爲一個大的寫操做,提升寫入效率。 」Due to several objects map to the same physical disks, the original logical sequential IO streams mix together (green, orange, blue and read blocks).  來源

  所以,須要注意的是,理論上,RBDCache 對順序寫的效率提高應該很是有幫助,而對隨機寫的效率提高應該沒那麼大,其緣由應該是後者合併寫操做的效率沒前者高(也就是可以合併的寫操做的百分比比較少)。具體效果待測試。

  在使用 QEMU 實現的 VM 來使用 RBD 塊設備,那麼 Linux Kernel 中的塊設備驅動是 virtio_blk,它會對塊設備各類請求封裝成一個消息經過 virtio 框架提供的隊列發送到 QEMU 的 IO 線程,QEMU 收到請求後會轉給相應的 QEMU Block Driver 來完成請求。當 QEMU Block Driver 是 RBD 時,緩存就會交給 Librbd 自身去維護,也就是一直所說的 RBDCache;用戶在使用本地文件或者 Host 提供的 LVM 分區時,跟 RBDCache 一樣性質的緩存包括了 Guest Cache 和 Host Page Cache,見本文第一部分的描述。

2.2 RBDCache 的原理

2.2.1 RBDCache 的配置

  在 ceph.conf 中,設置 rbd cache = true 便可以啓用 RBDCache。它有如下幾個主要的配置參數:

配置項 含義 默認值
rbd cache 是否啓用 RBDCache

0.87 版本開始:true,啓用

0.87 版本以前:false,禁用

rbd_cache_size Librbd 能使用的最大緩存大小 32 MiB
rbd_cache_max_dirty

緩存中容許髒數據的最大值,用來控制回寫大小,不能超過 rbd_cache_size。超過的話,應用的寫入應該會被阻塞,

這時候IOPS就會降低

24 MiB
rbd_cache_target_dirty 開始執行回寫過程的髒數據大小,不能超過 rbd_cache_max_dirty 16MiB
rbd_cache_max_dirty_age 緩存中單個髒數據最大的存在時間,避免可能的髒數據由於遲遲未達到開始回寫的要求而長時間存在 1 秒

  可見,默認狀況下:

  • 在主機操做系統內內存內會分配 32MiB 的空間用於 RBD 作緩存使用
  • 容許最大的髒數據大小爲 24MiB,超過的話,可能會阻止繼續寫入(須要確認)
  • 在髒數據總共有 16MiB 時,開始回寫過程,將數據寫入Ceph集羣
  • 在單個髒數據(目前在 Librbd 用戶態庫中主要以 Object Buffer Extent 爲基本單位進行緩存,這裏的粒度應該是 Object Buffer Extent)存在超過 1 秒時,對它啓用回寫

  也能看出,RBDCache 從空間和時間來方面,在效率和數據有效性之間作平衡。

幾個重要的注意事項:

(1)QEMU 和 ceph 配置項的相互覆蓋問題

http://ceph.com/docs/master/rbd/qemu-rbd/#qemu-cache-options

  • 在沒有在 Ceph 配置文件中顯式配置 RBD Cache 的參數(儘管Ceph 支持配置項的默認值,可是,看起來,是否在Ceph配置文件中寫仍是不寫,會有不一樣的效果。。真繞啊。。)時,QEMU 的 cache 配置會覆蓋 Ceph 的默認配置。
    • qemu driver 'writeback' 至關於 rbd_cache = true
    • qemu driver ‘writethrough’ 至關於 ‘rbd_cache = true,rbd_cache_max_dirty = 0’
    • qemu driver ‘none’ 至關於 rbd_cache = false
    • 一個典型場景是,在 nova.conf 中配置了 」cache=writeback」,而沒有在客戶端節點上配置 Ceph 配置文件,這時候將直接打開 RBDCache 並使用 writeback 模式,而不是先 writethrough 後 writeback。
  • 在在 Ceph 配置文件中顯式配置了緩存模式的時候,Ceph 的 cache 配置會覆蓋 QEMU 的 cache 配置。
  • 若是在 QEMU 的命令行中使用了 cache 配置,則它會覆蓋 Ceph 配置文件中的配置。

優先級:QEMU 命令行中的配置 > Ceph 文件中的顯式配置 > QEMU 配置 > Ceph 默認配置

(2)在啓用 RBDCache 時,必須在 QEMU 中配置 」cache=writeback」,不然可能會致使數據丟失。在使用文件系統的狀況下,這可能會致使文件系統損壞。

Important 

If you set rbd_cache=true, you must set cache=writeback or risk data loss. Without cache=writeback, QEMU will not send flush requests to librbd. If QEMU exits uncleanly in this configuration, filesystems on top of rbd can be corrupted.

http://ceph.com/docs/master/rbd/qemu-rbd/#running-qemu-with-rbd

(3)使用 raw 格式的 Ceph 卷設備 「 <driver name='qemu' type='raw' cache='writeback'/>「

http://ceph.com/docs/master/rbd/qemu-rbd/#creating-images-with-qemu

理論上,你可使用其餘 QEMU 支持的格式好比 qcow2 或者 vmdk,可是它們會帶來 overhead
The raw data format is really the only sensible format option to use with RBD. Technically, you could use other QEMU-supported formats (such as qcow2 or vmdk), but doing so would add additional overhead, and would also render the volume unsafe for virtual machine live migration when caching (see below) is enabled.

(4)在新版本的 Ceph 中(未來的版本,尚不知版本號),Ceph 配置項 rbd cache 將會被刪除,RBDCache 是否開啓將由 QEMU 配置項決定。

也就是說,若是 QEMU 中設置 cache 爲 ‘none’ 的話, RBDCache 將不會被使用;設置爲 ‘writeback’ 的話,RBDCache 將會被啓用。參考連接:ceph : [client] rbd cache = true override qemu cache=none|writeback

(5)對 Nova 來講,不設置 disk_cachemode 值的話,默認的 driver 的 cache 模式是 ‘none’。可是,在不支持 ‘none’ 模式的存儲系統上,會改成使用 ‘writethrough’ 模式。(來源

def disk_cachemode(self):
        if self._disk_cachemode is None:
            # We prefer 'none' for consistent performance, host crash
            # safety & migration correctness by avoiding host page cache.
            # Some filesystems (eg GlusterFS via FUSE) don't support
            # O_DIRECT though. For those we fallback to 'writethrough'
            # which gives host crash safety, and is safe for migration
            # provided the filesystem is cache coherant (cluster filesystems
            # typically are, but things like NFS are not).
            self._disk_cachemode = "none"
            if not self._supports_direct_io(FLAGS.instances_path):
                self._disk_cachemode = "writethrough"
        return self._disk_cachemode

 

2.2.2 緩存中的數據被 flush 到 Ceph cluster

  有兩種類型的 flush:

  • RBD 主動的,在 RBDCache 規定的空間或者數據保存時間達到閾值以後,會觸發回寫
  • RBD 被動的,librbd 的 flush 接口被調用,所有緩存中的數據也會被回寫。又能夠細分爲兩種類型:
    • QEMU 在合適的時候會自動發出 flush:QEMU 做爲最終使用 Librbd 中 RBDCache 的用戶,它在 VM 關閉、QEMU 支持的熱遷移操做或者 RBD 塊設備卸載時都會調用 QEMU Block Driver 的 Flush 接口,確保數據不會被丟失。所以,此時,須要用戶在使用了開啓 RBDCache 的 RBD 塊設備 VM 時須要給 QEMU 傳入 「cache=writeback」 確保 QEMU 知曉有緩存的存在,否則 QEMU 會認爲後端並無緩存而選擇將 Flush Request 忽略。
    • 應用發出 flush,好比 fio,能夠設置 fdatasync 爲一個大於零的整數,從而在若干次寫操做後執行fdatasync。( fdatasync=int Like fsync, but uses fdatasync(2) instead to only sync the data parts of the file. Default: 0「

  關於第二種 flush,這裏的一個問題是,何時會有這種主動 flush 指定發出。有文章說,」QEMU 做爲最終使用 Librbd 中 RBDCache 的用戶,它在 VM 關閉、QEMU 支持的熱遷移操做或者 RBD 塊設備卸載時都會調用 QEMU Block Driver 的 Flush 接口「。同時,一些對數據的安全性敏感的應用也能夠經過操做系統在須要的時候發出 flush 指定,好比一些數據庫系統。你可使用 fio 工具的 fdatasync 參數在指定的寫入操做後發出 fdatasync 指令。具體效果還待測試。

  librados 的 flush API:

CEPH_RADOS_API int rados_aio_flush(rados_ioctx_t io)
Block until all pending writes in an io context are safe

This is not equivalent to calling rados_aio_wait_for_safe() on all write completions, since this waits for the associated callbacks to complete as well.

Parameters
io -
the context to flush

  下面是一個 Linux 系統上文件操做的僞代碼(來源)。可見,該程序知道只有在 fdatasync 執行成功後,數據纔算寫入成功。

#include   "stdlib.h"           /* for exit */
#include   "unistd.h"           /* for write fdatasync*/
#include    "fcntl.h"           /* for open  */
int main(void){
    int fd;
    if((fd=open("/home/zzx/test.file",O_WRONLY|O_APPEND|O_DSYNC))<0){
        exit(1);
        }
    char buff[]="abcdef";
    if(write(fd,buff,6)!= 6){
        exit(2);
    }
    if(fdatasync(fd)==-1){
        exit(3);
    }
    exit(0);
}

2.2.3 RBDCache 中數據的易失性和 librbd rbd_cache_writethrough_until_flush 配置項

  由於 RBDCache 是利用內存來緩存數據,所以數據也是易失性的。那麼,最安全的是,設置 rbd_cache_max_dirty = 0,就是不緩存數據,至關於 writethrough 的效果。很明顯,這沒有實現 RBDCache 的目的。 

  另外,Ceph 還提供 rbd_cache_writethrough_until_flush 選項,它使得 RBDCache 在收到第一個 flush 指令以前,使用 writethrough 模式,透傳數據,避免數據丟失;在收到第一個 flush 指令後,開始 writeback 模式,經過 KVM barrier 功能來保證數據的可靠性。   

  該選項的含義:

This option enables the cache for reads but does writethrough until we observe a FLUSH command come through, which implies that the guest OS is issuing barriers.  This doesn't guarantee they are doing it properly, of course, but it means they are at least trying.  Once we see a flush, we infer that writeback is safe.

  該選項的默認值究竟是 true 仍是 false 比較坑爹:

  • ceph/librbd 在 0.80 版本中添加該選項,默認值是 false (代碼
  • ceph/librbd 在 0.87 版本中將默認值修改成 true (代碼

  所以,你在使用不一樣版本的 librbd 狀況下使用默認配置時,其 IOPS 性能是有很大的區別的:

  • 0.80 版本中,一直是 writeback,IOPS 會從頭就很好;
  • 0.87 版本中,開始是 writethrough,在收到第一個操做系統發來的 flush 後,轉爲 writeback,所以,IOPS 是先差後好。

  在實現上,

(1)收到第一個flush 以前,至關於 rbd_cache_max_dirty 被設置爲0 了:

uint64_t init_max_dirty = cct->_conf->rbd_cache_max_dirty;
      if (cct->_conf->rbd_cache_writethrough_until_flush)
    init_max_dirty = 0;

(2)收到第一個 flush 以後,就轉爲 writeback 了

if (object_cacher && cct->_conf->rbd_cache_writethrough_until_flush) {
      md_lock.get_read();
      bool flushed_before = flush_encountered;
      md_lock.put_read();
      uint64_t max_dirty = cct->_conf->rbd_cache_max_dirty;

2.3 小結

  各類配置下的Ceph RBD 緩存效果:

配置 rbd_cache_writethrough_until_flush 的值 緩存效果
rbd cache = false N/A 沒有讀寫緩存,等同於 directsync

rbd cache = true

rbd_cache_max_dirty = 0           

N/A   只有讀緩存,沒有寫緩存,等同於 writethrough

rbd cache = true

rbd_cache_max_dirty > 0

「cache=writeback」 

True

在收到 QEMU 發出的第一個 flush 前,

使用 writethrough 模式;收到後,使用 writeback 模式

rbd cache = true

rbd_cache_max_dirty > 0

「cache=writeback」 

False 一直使用 writeback 模式,QEMU 會在特定時候發出 flush,可能會致使數據丟失

rbd cache = true

rbd_cache_max_dirty > 0

「cache=none」 

True 一直使用 writethrough 模式,沒有寫緩存,只有讀緩存

rbd cache = true

rbd_cache_max_dirty > 0

「cache=writeback」 

False 一直使用 writeback 模式,QEMU 會發出 flush 使緩存數據寫入Ceph 集羣

 3. Linux 系統 I/O <補記於 2016/05/28>

    備註:主要內容引用自 2016/05/27 發表於 Linux內核之旅 微信公衆號的 系統和進程信息與文件IO緩衝 一文。

    忽略文件打開的過程,一般咱們會說「寫文件」有兩個階段,一個是調用 write 咱們稱爲寫數據階段(實際上是受open的參數影響),調用 fsync(或者fdatasync)咱們稱爲flush階段。Linux上的塊設備的操做能夠分爲兩類:出於速度和效率的考慮。系統 I/O 調用(即內核)和標準 C語言庫 I/O 函數,在操做磁盤文件時會對數據進行緩衝。

3.1 stdio 緩衝的類型和設置

使用 C 標準庫中的 fopen/fread/fwrite 系列的函數,咱們能夠稱其爲 buffered I/O。具體的I/O path以下:

Application <-> Library Buffer <-> Operation System Cache <-> File System/Volume Manager <-> Device

其中,library buffer 是標準庫提供的用戶空間的 buffer,能夠經過 setvbuf 改變其大小。

設置方法以下:

(1)經過 setvbuf 函數,結合不一樣的mode,能夠設置緩衝的類型,和大小:

  • 不緩衝:不對 I/O 進行緩衝,等同於write或read忽略其buf和size參數,分別指定爲NULL和0。stderr默認屬於這個類型。
  • 行緩衝:對於輸出流,在輸出一個換行符前(除非緩衝區滿了)將緩衝數據,對於輸入流,每次讀取一行數據。
  • 全緩衝:對於輸出流,會一直緩衝數據知道緩衝區滿爲止。

(2)咱們也能夠經過fflush來刷新緩衝區。

3.2 內核緩衝

使用 Linux 的系統調用的 open/read/write 系列的函數,咱們能夠稱其爲 non-buffered I/O。其I/O 路徑爲:

Application<-> Operation System Cache <-> File System/Volume Manager <->Device

OS Cache 即內核緩衝的緩衝區,它是在內核態的,沒辦法經過像 stdio 庫的 setvbuf 那樣給它指定一個用戶態的緩衝區,只能控制緩衝區的刷新策略等。

  • fsync 用於將描述符 fd 相關的全部元數據都刷新到磁盤上,至關於強制使文件處於同步I/O文件完整性。 
  • fdatasync 的做用相似於 fsync,只是強制文件處於數據完整性,確保數據已經傳遞到磁盤上。
  • sync 僅在全部數據(數據塊,元數據)已傳遞到磁盤上時才返回。

這些函數都只能刷新一次緩衝,此後每次發生的I/O操做都須要再次調用上面的三個系統調用來刷新緩衝,爲此能夠經過設置描述符的屬性來保證每次IO的刷新緩衝。

  • 打開文件的時候設置 O_SYNC,至關於每次發生 IO 操做後都調用 fsync 和 fdatasync 系統調用。[寫操做只有在數據和元數據好比修改時間等被寫入磁盤後才返回]
  • 使用 O_DSYNC 標誌則要求寫操做是同步 I/O 數據完整性的也就是至關於調用 fdatasync。[寫操做必須等到數據被寫入磁盤後才返回,此時元數據能夠沒有被寫入磁盤,好比文件的 inode 信息還沒有被寫入時]
  • O_RSYNC 標誌須要結合 O_DSYNC 和 O_SYNC 標誌一塊兒使用。

將這些標誌的寫操做做用結合到讀操做中,也就說在讀操做以前,會先按照O_DSYNCO_SYNC對寫操做的要求,完成全部待處理的寫操做後纔開始讀。

更詳細信息,能夠參考 http://man7.org/linux/man-pages/man2/open.2.html

3.3 Direct I/O(O_DIRECT 標誌)

   Linux 容許應用程序在執行磁盤 I/O 時繞過緩衝區高速緩存(Host OS Page Cache),從用戶空間直接將數據傳遞到文件或磁盤設備上(實際上是 Disk cache 中),這咱們稱之爲直接 I/O,或者裸 I/O。直接 I/O 只適用於特定 I/O 需求的應用,例如:數據庫系統,其高速緩存和I/O優化機制自成一體,無需內核消耗CPU時間和內存去完成相同任務。經過在打開文件的時候指定 O_DIRECT 標誌設置文件讀寫爲直接IO的方式。使用直接 IO 存在一個問題,就是I/O得對齊限制,由於直接 I/O 涉及對磁盤的直接訪問,因此在執行I/O時,必須遵照一些限制:

  • 用於傳遞數據的緩衝區,其內存邊界必須對齊爲塊大小的整數倍。

  • 數據傳輸的開始點,亦即文件和設備的偏移量,必須是塊大小的整數倍。

  • 待傳遞數據的 buffer 必須是塊大小的整數倍。

3.4 總結

3.4.1 I/O 棧

3.4.2 MySQL 中的 I/O 模式

對於 MySQL 來講,該數據庫系統是基於文件系統的,其性能和設備讀寫的機制有密切的關係。和數據庫性能密切相關的文件I/O操做的三個操做:

  • open 打開文件:好比,open("test.file",O_WRONLY|O_APPDENT|O_SYNC))
  • write 寫文件:好比,write(fd,buf,6)
  • fdatasync flush操做(將文件緩存刷到磁盤上):好比,fdatasync(fd) == -1,表示 write操做後,咱們調用了 fdatasync 來確保文件數據 flush 到了 disk上。fdatasync 返回成功後,那麼能夠認爲數據已經寫到了磁盤上。像這樣的flush的函數還有fsync、sync。

MySQL 的 innodb_flush_log_at_trx_commit 參數肯定日誌文件什麼時候 write 和 flush。innodb_flush_method 則肯定日誌及數據文件如何 write、flush。在Linux下,innodb_flush_method能夠取以下值:fdatasync, O_DSYNC, O_DIRECT,那這三個值分別是如何影響文件寫入的?

 

  • fdatasync 被認爲是安全的,由於在 MySQL 總會調用 fsync 來 flush 數據。
  • 使用 O_DSYNC 是有些風險的,有些 OS 會忽略該參數 O_SYNC。
  • 咱們看到 O_DIRECT 和 fdatasync和 很相似,可是它會使用 O_DIRECT 來打開數據文件。有數據代表,若是是大量隨機寫入操做,O_DIRECT 會提高效率。可是順序寫入和讀取效率都會下降。因此使用O_DIRECT須要謹慎。

簡單的,InnoDB 在每次提交事務時,爲了保證數據已經持久化到磁盤(Durable),須要調用一次 fsync(或者是 fdatasync、或者使用 O_DIRECT 選項等)來告知文件系統將可能在緩存中的數據刷新到磁盤(更多關於fsync)。而 fsync 操做自己是很是「昂貴」的(關於「昂貴」:消耗較多的IO資源,響應較慢):傳統硬盤(10K轉/分鐘)大約每秒支撐150個fsync操做,SSD(Intel X25-M)大約每秒支撐1200個fsync操做。因此,若是每次事務提交都單獨作fsync操做,那麼這裏將是系統TPS的一個瓶頸。因此就有了Group Commit的概念。

當多個事務併發時,咱們讓多個都在等待 fsync 的事務一塊兒合併爲僅調用一次 fsync 操做。這樣的一個簡單的優化將大大提升系統的吞吐量,Yoshinori Matsunobu的實驗代表,這將帶來五到六倍的性能提高。

4. I/O Cache 與 Linux write barrier

4.1 I/O Cache

一個典型的存儲 I/O 路徑:

一些組件有本身的cache:

其中:

  • page cache 是 VFS cache 的一部分,buffer cache 是內存中,二者都是易失性的。可是,buffer cache 的容量每每很大。
  • storage controller write cache 存在於絕大多數中高端存儲控制器中。
  • Write cache on physical media 存在於磁盤中,絕大多數也是易失性的。一些SSD含有備份電容。現代磁盤每每帶有 16 - 64 MB cache。

4.2 Linux write barrier

 

<做者注:做者對本文中的一些概念還有一些疑惑或者不解,所以,本文內容會持續更新>

 

參考資料:

相關文章
相關標籤/搜索