本系列文章會總結 QEMU/KVM 和 Ceph 之間的整合:
(1)QEMU-KVM 和 Ceph RBD 的 緩存機制總結
(2)QEMU 的 RBD 塊驅動(block driver)
QEMU-KVM 的緩存機制的概念很多,Linux/KVM I/O 軟件棧的層次也很多,網上介紹其緩存機制的文章很多。邊學習邊總結。本文結合 Ceph 在 QEMU/KVM 虛機中的使用,總結一下兩者結合時緩存的各種選項和原理。
1. QEMU/KVM 緩存機制
先以客戶機(Guest OS) 中的應用寫本地磁盤為例進行介紹。客戶機的本地磁盤,其實是 KVM 主機上的一個鏡像文件虛擬出來的,因此,客戶機中的應用寫其本地磁盤,其實就是寫到KVM主機的本地文件內,這些文件是保存在 KVM 主機本地磁盤上。
先來看看 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 過程
- GUEST OS 中的一個應用發出 read request。
- OS 在 guest page cache 中檢查。如果有(hit),則直接將 data 從 guest page cache 拷貝到 application space。
- 如果沒有(miss),請求被轉到 guest virtual disk。該 request 會被 QEMU 轉化為對 host 上鏡像文件的 read request。
- Host OS 在 HOST Page cache 中檢查。如果 hit,則通過 QEMU 將 data 從 host page cache 傳到 guest page cache,再拷貝到 application space。
- 如果沒有(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 |
|
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 需求客戶機的場景中。 也就是常說的 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 到物理磁盤上。
基本過程:
- 在一個會話中寫入數據
- 發出 barrier request
- 會話中的所有數據被 flush 到物理磁盤
- 繼續下一個會話
看起來和 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 比較坑爹:
因此,你在使用不同版本的 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_DSYNC
或O_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
<作者注:作者對本文中的一些概念還有一些疑惑或者不解,因此,本文內容會持續更新>
參考資料:
- 解析Ceph: RBDCache 背后的世界
- http://docs.ceph.com/docs/hammer/rbd/rbd-config-ref/
- KVM storage performance and cache settings on Red Hat Enterprise Linux 6.2
- http://linux.die.net/man/1/fio
- http://xfs.org/index.php/XFS_FAQ#Write_barrier_support.
- https://libvirt.org/formatdomain.html
- https://www.quora.com/What-is-the-major-difference-between-the-buffer-cache-and-the-page-cache
- https://bugs.launchpad.net/charms/+source/nova-compute/+bug/1412856
- http://duartes.org/gustavo/blog/post/page-cache-the-affair-between-memory-and-files/
- http://www.orczhou.com/index.php/2010/08/time-to-group-commit-1/
- http://www.orczhou.com/index.php/2009/08/innodb_flush_method-file-io/
- http://www.thesubodh.com/2013/07/what-are-exactly-odirect-osync-flags.html
- https://monolight.cc/2011/06/barriers-caches-filesystems/