本系列文章会总结 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
先以客户机(Guest OS) 中的应用写本地磁盘为例进行介绍。客户机的本地磁盘,实际上是 KVM 主机上的一个镜像文件虚拟出来的,所以,客户机中的应用写其本地磁盘,其实就是写到KVM主机的本地文件内,这些文件是保存在 KVM 主机本地磁盘上。github
先来看看 I/O 协议栈的层次和各层次上的缓存状况。数据库
熟悉 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。
(来源)
主要组成部分:
Page Cache 是客户机和主机操做系统维护的用来提升存储 I/O 性能的缓存,它是 Linux 虚拟文件系统缓存的一部分,位于操做系统内存中,它是易失性的,所以,在操做系统奔溃或者系统掉电时,这些数据会消失。数据是否写入 Page cache 能够被控制。当会写入 page cache 时,当数据被写入 page cache 后,应用就认为写入完成了,随后的读操做也会从 page cache 中读取数据,这样性能会提升。可使用 fsync 来将数据从 page cache 中拷贝到持久存储。
在 KVM 环境中,host os 和 guest os 都有 page cache,所以,最好是能绕过一个来提升性能。
该缓存的特色是读的时候,操做系统先检查页缓存里面是否有须要的数据,若是没有就从设备读取,返回给用户的同时,加到缓存一份;写的时候,直接写到缓存去,再由后台的进程按期涮到磁盘去。这样的机制看起来很是的好,在实践中也效果很好。
考虑到其易失性,须要考虑它的大小,特别是在 KVM 主机上。如今 KVM 主机的内存能够很大。其内存越大, 那么在 Page cache 中尚未 flush 到磁盘(虚拟或者物理的)的脏数据就越多,其丢失的后果就越严重。默认的话,Linux 2.6.32 在脏数据达到内存的 10% 的时候会自动开始 flush。
这是磁盘的 write cache,它会提升数据到存储的写性能。写到 disk write cache 后,写操做会被认为完成了,即便数据还没真正被写入物理磁盘。这样,若是 disk write cache 没有备份电池的话,断电将致使还没有写入物理磁盘的数据丢失。要强制数据被写入磁盘,应用能够经过操做系统能够发出 fsync 命令。所以,disk write cache 会提到写I/O 性能,可是,须要确保应用和存储栈会将数据写入磁盘中。若是 disk write cache 被关闭,那么写性能将降低,可是断电时数据丢失将会避免。
从该过程能够看出:
QEMU-KVM Linux 支持关闭和开启任一一个 Page cache,也就是说有四种组合模式,分别会带来不一样的效果。在各类I/O的过程当中,最好是绕过一个或者两个 Page cache。
写 I/O 过程比较复杂,本文其他部分会详细阐述。从 1.3 表格总结,基本上
关于 guest os page cache,看起来它主要是做为读缓存,而对于写,没有一种模式是以写入它做为写入结束标志的。
在 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 |
看看性能比较:
基本结论:
上面的基本结论中,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 到物理磁盘上。
基本过程:
看起来和 writethrough 差很少是吧。可是它的效率比 writethrough 高。二者的区别在于,writethrough 是每次 write 都会发 fsync,而 barrier-passing 是在若干个写操做或者一个会话以后发 fdatasync 命令,所以其效率更高。
也能够看到,使用它是有条件的:
也能够看到,应用在须要的时候发出 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 值设得比较大,来模拟一些应用场景。
考虑到 KVM write barrier 的原理和 KVM 各类缓存模式的原理,显而易见,writeback + barrier 的方式下,能够实现 效率最高+数据安全 这种最优效果。
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。
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 达到目的。
从这个栈能够看出来,RBDCache 相似于磁盘的 write cache。它应该有三个功能:
所以,须要注意的是,理论上,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,见本文第一部分的描述。
在 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 秒 |
可见,默认状况下:
也能看出,RBDCache 从空间和时间来方面,在效率和数据有效性之间作平衡。
几个重要的注意事项:
(1)QEMU 和 ceph 配置项的相互覆盖问题
http://ceph.com/docs/master/rbd/qemu-rbd/#qemu-cache-options
优先级: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
有两种类型的 flush:
关于第二种 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); }
由于 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 性能是有很大的区别的:
在实现上,
(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;
各类配置下的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 集群 |
备注:主要内容引用自 2016/05/27 发表于 Linux内核之旅 微信公众号的 系统和进程信息与文件IO缓冲 一文。
忽略文件打开的过程,一般咱们会说“写文件”有两个阶段,一个是调用 write 咱们称为写数据阶段(实际上是受open的参数影响),调用 fsync(或者fdatasync)咱们称为flush阶段。Linux上的块设备的操做能够分为两类:出于速度和效率的考虑。系统 I/O 调用(即内核)和标准 C语言库 I/O 函数,在操做磁盘文件时会对数据进行缓冲。
使用 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,能够设置缓冲的类型,和大小:
(2)咱们也能够经过fflush
来刷新缓冲区。
使用 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。
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 必须是块大小的整数倍。
对于 MySQL 来讲,该数据库系统是基于文件系统的,其性能和设备读写的机制有密切的关系。和数据库性能密切相关的文件I/O操做的三个操做:
MySQL 的 innodb_flush_log_at_trx_commit 参数肯定日志文件什么时候 write 和 flush。innodb_flush_method 则肯定日志及数据文件如何 write、flush。在Linux下,innodb_flush_method能够取以下值:fdatasync, O_DSYNC, 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的实验代表,这将带来五到六倍的性能提高。
一个典型的存储 I/O 路径:
一些组件有本身的cache:
其中:
<做者注:做者对本文中的一些概念还有一些疑惑或者不解,所以,本文内容会持续更新>
参考资料: