数据一致性和io类型

版权声明:本文由高剑林原创文章,转载请注明出处: 
文章原文连接:https://www.qcloud.com/community/article/106node

来源:腾云阁 https://www.qcloud.com/communitylinux

 

对linux内核来讲,读写要通过层层路径,才能真正读写硬盘。从io路径来讲,io要通过page cache,io调度队列,dispatch队列,ncq队列和硬盘cache,才能真正到达硬盘。算法

Page cache:page cache是linux内核提供的缓存接口,page cache的名字就说明内核是经过page单元(一般4K大小)来管理cache。读操做首先在page cache查找,若是找到,就复制page cache的内容返回,找不到,才真正调用下层处理。写操做,buffer io 写到page cache就返回,真正的磁盘写,是由内核的pdflush内核线程负责缓存

IO调度队列:服务器

Linux内核提供了四种io调度算法,as,deadline,cfq,noop。每种调度算法都实现了一个调度队列,io首先在队列中排序(noop最简单,不排序),而后根据条件,决定是否到dispatch队列。从调度队列下发,涉及一个unplug的概念。也就是说,调度队列一般处于阻塞(plug)状态,当执行unplug操做时,io离开调度队列,开始下发。unplug是个循环动做,将调度队列的全部io都尝试下发,直到不能下发为止。
总结一下,执行unplug有下列条件:并发

  • 第一个io启动了三毫秒的定时器,定时器到了,会unplug,开始下发async

  • io请求超过设定的限制(缺省是4),执行unplug,开始下发oop

  • Sync标志的io,当即unplug,开始下发。性能

  • Barrier标志的io,清空调度队列后,执行unplug,开始下发spa

  • 一个io执行完毕,也要unplug队列。

dispatch队列:dispatch队列对应用关系不大。可是内核层对日志文件系统的joural数据,提供了一种barrier io,这个主要在dispatch队列实现。

Ncq队列:
NCQ是sata硬盘自身的队列。(sas硬盘的队列叫TCQ)。NCQ队列是由操做系统建立的,可是加入到NCQ队列的io,是由硬盘来决定执行顺序。为了实现这个,NCQ队列建立在内核的DMA内存中,而后通知硬盘,至于硬盘选择那个io执行,是硬盘自身选择的结果。

硬盘cache:
硬盘cache是硬盘内部的cache。若是打开硬盘cache的话,写硬盘的io,首先是到硬盘cache,而非直接落到硬盘。

一. Pdflush的回写逻辑

Pdflush提供了四个参数来控制回写。在内核实现中,pdflush的回写策略控制还比较复杂。

可是简单一点说,内核缺省状况下,每5秒钟扫描脏页,若是脏页生存时间超过30秒(缺省数值),就刷脏页到磁盘。

详细的可参考本人写的《linux内核回写机制和调整》一文。

二. 数据下盘和一致性分析

从上文的分析,一般的io写,到page cache层就结束返回了,并没真正写到硬盘。这样机器掉电或者故障的时候,就有丢失数据的风险。为了尽快下io,系统又提供了一些措施解决这个问题。

O_SYNC:打开文件的时候,能够设置O_SYNC标志,在page cache的写完成后,若是文件有O_SYNC标志,当即开始将io下发,进入调度队列。随后将文件系统的meta data数据也下发,而后开始循环执行unplug操做,直到全部的写io完成。和回写机制比较,O_SYNC没有等脏页生存30秒,就尝试当即下发到硬盘。

O_SYNC本质就是io下发,而后执行unplug操做。O_SYNC的几个问题是:

  • 写page cache时候要把io拆成4k的单元。回写也是每次写4K的页面,若是是大io,就须要内核的调度层把4k的io从新再合并起来。这是冗余的过程

  • 每一个io都要当即unplug,这样就不能实现io的排序和合并。O_SYNC的性能至关低。

  • 若是多个进程并发写,不能保证写操做的顺序。Ncq队列根据硬盘磁头的位置和磁盘旋转位置肯定执行的顺序。通常是meta data数据一块儿写,这样存在不一样步的风险。

  • 若是硬盘cache打开了,那么写只到硬盘cache就返回了。存在丢数据的风险。一般存储厂商都要求硬盘cache关闭。不过腾讯的服务器都是打开硬盘cache的。

O_DIRECT:打开文件的时候,可设置O_DIRECT标志。O_DIRECT不使用内核提供的page cache。这样读操做,就不会到page cache中检查是否有须要数据存在。而写操做,也不会将数据写入page cache,而是送入调度队列。

O_DIRECT执行写io的时候,会置WRITE_SYNC标志。这个标志在io进入调度队列后,会执行一次unplug操做。而不是像O_SYNC那样,循环执行unplug操做。

为了不O_SYNC每一个写io都要阻塞等待的问题,系统提供了fsync和fdatasync系统调用,可让应用层本身控制同步的时机。

Fsync:fsync将文件范围内,全部的脏页面都下发到硬盘。而后也要将脏的元数据写到 硬盘。若是文件的inode自己有变化,一样须要写到硬盘。

Fdatasync:fdatasync和fsync的区别其实很轻微。好比ext2文件系统,若是文件的inode只有轻微的变化,fdatasync此时不更新inode。典型的轻微变化是文件atime的变化。而在ext3文件系统,fsync和fdatasync是彻底同样的。不论是否轻微变化,都要回写inode。

Fsync和fdatasync都是对整个文件的操做,若是应用只想刷新文件的指定位置,这两个系统调用就失效了。因此新的内核还提供了sync_file_range来指定范围写。不过要注意,sync_file_range是不回写文件的meta data。必须应用层保证meta data没有更新。

三. Pdflush的回写逻辑

Pdflush提供了四个参数来控制回写。在内核实现中,pdflush的回写策略控制还比较复杂。

可是简单一点说,内核缺省状况下,每5秒钟扫描脏页,若是脏页生存时间超过30秒(缺省数值),就刷脏页到磁盘。

详细的可参考本人写的<<linux内核回写机制和调整》一文。

四. 内核的barrier io

从上文的分析看出,内核没有为用户态提供保证顺序的,肯定写到硬盘的系统调用。可是对于内核文件系统来讲,必须提供这样的接口。好比日志文件系统,必需要数据落到硬盘后,才能修改元数据的日志。不然,出错状况下就可能形成文件系统崩溃。为此,内核专门提供了一个barrier方式实现日志的准确写到硬盘。

文件系统的barrier io,意味着,这个barrier io以前的写io必须完成。同时,在barrier io完成以前(是真正写到硬盘,不是写到cache就返回),也不能有别的写io再执行。为此,上文分析的dispatch 队列完成了这个功能。

当写io从调度队列进入dispatch队列的时候,要检查是不是一个barrier io。若是是barrier io,dispatch首先在队列中插入一个SCSI命令SYNCHRONIZE_CACHE,这个命令指示硬盘cache刷全部的写io到硬盘。而后再下发barrier io,以后再插入一个SYNCHRONIZE_CACHE命令,指示硬盘将刚才的barrier io真正写到硬盘(还有一种方式,是经过命令携带FUA标志实现不通过cache直接下盘)。

五. 总结

对于单一的存储系统来讲,数据一致性,性能和可靠性是几个矛盾的指标。标准的linux内核在这方面也有些左右为难。好比内核在io失败的状况下,通常会重试(内核设置了5次的重试)。这样重试时间可能超过1分钟,这对互联网系统的业务来讲是不可承受的。如何找到合适点,平衡几个指标的关系,从操做系统最底层提高产品的可靠性和性能,是一项长期的任务。

相关文章
相关标签/搜索