理解 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

 

<做者注:做者对本文中的一些概念还有一些疑惑或者不解,所以,本文内容会持续更新>

 

参考资料:

相关文章
相关标签/搜索