IO Path
App IO
App
主动发起IO
读、写;
大、小;
连续、随机;
顺序、并发;
-
同步调用
同步调用windows,windows也会同步调用存储设备;
同步调用linux,linux会根据mount文件系统时的参数来,同步mount同步调用存储设备,异步mount异步调用存储设备。
-
异步调用
Network IO
-
CIFS
在不使用FS cache的情况下,应用层的IO请求被network io模块透传给CIFS SERVER。
在使用FS cache的情况下,应用层的IO请求会先在本地FS cache中命中,若未命中,走page fault流程,最后由OS内核发起读page的请求发送给CIFS SERVER。CIFS客户端缓存不会预读数据。
使用FS cache,异步变同步的问题依然存在,同时还存在读惩罚,应用层发起的读IO会映射为读page,page size为4K,会读取多余的数据,若IO横跨两个页面地址,则会读取两个page(当然也可认为是在预读...)。应该在应用层将IO大小优化为页面大小避免读惩罚,这样即使在不使用FS cache的情况下,也能避免IO透传到CIFS server上,在server的cache中未命中时产生的读惩罚。
-
使用FS cache
连续小IO读写;
-
IO探讨
-
读行为
-
Buffered Read
读IO在CIFS Client命中则直接返回结果,未命中产生Page Fault,再将Page IO传到CIFS Server,在Server Cache中会再命中一次,最后返回Page大小的IO包。
-
-
-
大话存储上一个读IO请求立马对应一个Response,异步变同步,但笔记本测试中还是异步IO。
- No Buffered Read
程序IO大小被透传到CIFS Server,CIFS Server返回同样大小的IO包。
IO在CIFS Client不会命中缓存或产生Page Fault,但IO在CIFS Server上可能命中或产生Page Fault导致读惩罚。
- 写行为
- Buffered Write
- WT
写IO写入Client Cache,再产生一个Page IO传到Server,Server刷盘,回Client写IO成功。
无论在Client还是Server,IO写入Cache前都可能产生一个Page读,NFS不会?。
- WB
IO写入Client Cache就返回应用程序IO写入成功,Client根据算法定时将Cache刷到Server。
- No Buffered Write
写IO透传到CIFS Server,IO写入Server Cache,若Cache中没有对应页面,先读入cache,写入数据,返回客户端IO写成功,不用刷盘。
- 总结
使用CIFS时,使用No Buffered模式避免读写惩罚,同时保证iosize为4KB的整数倍,杜绝从Client到Server端整个IO路径中的惩罚现象。
考虑使用Buffered IO情况:小块连续读、小块连续写。
WT模式不考虑使用。
-
NFS
-
IO行为
总的来讲
CIFS
1.会产生page fault,因而产生一些惩罚读
2.异步IO会强制被变为同步NFS
会预读和合并写-
默认挂载
async 延缓执行写IO,积累一定时间以合并IO提交
rsize=wsize=65536 预读和合并写-
DD连续读
内核会预读rsize大小的数据,网络抓包发现传输的数据包大小是65536字节。
-
连续覆盖写
内核合并wsize大小(有时间限制,可能小于wsize)的数据块
-
随机覆盖写
内核无法合并IO,IO会被透传到Server,Server端可能再合并,不会像CIFS那样在写页面不在cache中时有惩罚读,发起一个page IO
-
-
默认带Sync
-
DD读
网络抓包发现传输数据块大小为65536,因为sync读会有cache hit操作,未命中时由内核向Server发起预读,预读大小为rsize
-
写
网络抓包发现写IO大小被透传给Server,因为同步写需要上一个IO返回,下一个IO才会发起
NFS写IO不会像CIFS写入本地cache就返回。
sync写=透写?
IO要下盘到Server硬盘才返回sync写要经过cache,透写pass掉cache
-
-
指定rsize\wsize
rsize\wsize只影响底层传输的数据包数量和大小,对NFS的IO逻辑没有影响
-
-
OS IO
File System
-
Fs Cache
-
异步变同步
默认使用Fs Cache加速IO;
大多数情况下,异步调用会转换为同步调用:
1.预读增加IO命中,尤其是连续小IO,命中几乎100%,将异步转为同步节省异步带来的系统开销。
2.FS cache缓存下层返回的乱序结果以应用层IO发起顺序返回。
3.FS cache一般使用Memory Mapping将数据映射到缓存中一定数量的page中,当目标数据不再缓存中的page时(page fault),OS用维护的线程池分配一个线程向下层发起同步读,在应用层IO并发量大,page fault频繁产生时,线程池的线程耗尽,后续page fault的IO必须等待可用线程,异步被转为同步了。-
优化
不使用FS Cache,可以得到较高的IO并发,但IO无法在FS cache中命中,IO慢,可以外接一个NAS存储设备,使得主机端IO并发高,并且能在存储设备的cache中命中数据,是最优方案。
-
-
-
FS上部
-
对用户的表示层
如 /root/a.txt
D:\a.txt -
访问接口层
直接接受上层IO,同时提供不同参数,如:
FILE_FLAG_SEQUENTIAL_SCAN
FILE_FLAG_RANDOM_ACCESS
应对不同的IO类型。
-
-
FS中部
-
FS cache
提供预读、回写、Page Fault、IO合并优化等
-
-
FS下部
-
块映射
文件系统感知底层RAID和磁盘类型等因素,为不同上层业务制定合理分布策略,以优化性能。
-
Flush机制
-
日志记录
-
FSCK
-
底层卷接口
卷IO策略,处于文件系统最底层,
负责将数据从卷中读出或写入。这个层次需要用最少的IO做最多的事情,
保证卷IO Queue随时充满、异步操作等,
以榨取底层存储系统性能。
-
Volume
-
块设备
一般使用Memory Mapping的方式映射到内存地址空间,访问块设备同样是Page Fault方式,可接受非扇区对齐的IO,在Page被读入内存后,再返回IO大小的数据。
应用程序对块设备发起读IO,由块设备转换成对应的Page,再转发给底层设备。
块设备向底层设备发起的IO总是恒定的,Page大小,同一个线程发起的读IO只能顺序执行,多个线程可以并发,一个线程的256KB读,会被块设备切开成64个4KB的读,非常浪费。
写IO会被块设备合并、覆盖、重排等优化。
-
字符设备(裸设备)
没有缓存,所以不能将非扇区整数倍的IO转换再传递给底层设备。
好处,可透传上层应用程序的任意扇区整数倍IO给底层设备,不用像块设备那样切割,性能很高。
字符设备时一个设备最底层的抽象,其本质等于物理设备本身。
-
裸设备 vs 文件系统
数据库类应用程序,自身具备——扇区映射、预读缓存、写缓存、读写优化等功能。
再使用文件系统,会造成缓存重复,且层层复制。
不使用FS好处:避免缓存层层复制且冗余,同时避免FS缓存带来的读写惩罚。
使用FS的好处:文件管理方便,可以直接复制数据文件备份、做文件系统快照等。
-
裸设备 vs DIO
(参见 IO Manager DIO IRP)
Windows DIO会锁定用户程序缓存,将其指针传递给底层设备驱动直接操作,底层设备驱动只能操作扇区整数被IO,所以用户程序缓存及IO size只能为扇区整数倍。若下层使用了类似CIFS协议,IO传递到CIFS Server时,由于CIFS Server存在cache,Page Fault会将IO划整,这时用户程序缓存及IO size不用为扇区整数倍。
DIO会消除文件系统缓存带来的读写惩罚,同时还能享受文件系统管理文件的便利,但是由于文件系统还有通过加锁互斥实现数据一致性的功能,在使用数据库类应用程序时,数据库自身已经有完备的加锁实现,会相比使用裸设备降低一些性能。
-
CIO
CIO是在DIO的基础上,将保证数据一致性的功能完全交给应用程序,自身完全不加锁,以此提升应用程序的并发性能。
Windows提供了API参数(Share Mode)以在访问文件时实现CIO功能。
-
数据库方案
使用AIO+裸设备性能最佳。
若需要享受文件系统管理文件的便利,则
AIO+CIO最佳。
IO Manager
-
Windows 系统IO路径简图
-
IRP流动过程
IO Request Packet
IRP包含上层请求的所有信息及完成这个请求所要涉及的底层所有驱动链(FS Driver、Device
Driver,etc)。IOM将IRP顶层IOSL填好发送给顶层驱动,一般是FSD,FSD收到后读取本层IOSL,执行完,若还需要下层继续执行(如cache hit失败),将下层IOSL填好后将IRP传递给下层,这样直到底层驱动。
底层驱动收到IRP执行完后,将IRP返给IOM,IOM会清空底层IOSL,IOM会从下往上以此将IRP发送给上层驱动检查执行结果,上层驱动根据不同执行结果触发相应操作。
-
Buffered IO IRP
1)用户程序自身数据缓存
虚拟地址空间逻辑连续->映射到非连续物理内存2)用户程序向OS发起IO请求时,将自身缓存地址通知给OS
3)OS在内核内存空间池中分配一个与对应用户程序缓存大小相同的连续SystemBuffer
4)IOM向下发起IRP,IRP中包含SystemBuffer指针
5)For Read:
底层驱动层层拷贝数据到该层buffer,直到SystemBuffer,若某层命中,则直接将数据拷贝到上层buffer,IRP不继续向下传递。
For wirte :
IOM将用户缓存中数据复制到SystemBuffer即返回用户程序完成信号,之后IOM向下发起IRP,逐步将SystemBuffer中的数据写入底层设备,下层Buffer会优化写IO,比如FS cache的合并。6)IO完成后,OS释放SystemBuffer,每次IO均要分配对应的SystemBuffer。
-
DIO IRP
1)用户程序自身数据缓存
虚拟地址空间逻辑连续->映射到非连续物理内存2)用户程序向OS发起IO请求时,将自身缓存地址通知给OS
3)IOM将程序对应缓存锁定,直到整个IO请求完成
4)IOM发起IRP
5)底层设备将从介质读取的数据直接送至应用程序缓存,若底层加入虚拟化效果驱动,如RAID,卷镜像,则需要最底层驱动读取数据后,转发给上层(RAID层)转化数据,在拷贝到用户程序缓存。
-
- note
Buffered IO IRP,用户程序Buffer会被Swap机制复制到硬盘,以腾出物理内存空间供其他程序使用。
DIO模式用户程序Buffer被锁定。
猜测是Buffered IO模式,层层都会耗费内存,所以采用了SWAP机制节省内存。
-
Page Cache
内核将文件或者块设备上对应的块映射到Page Cache。
Page Cache向下映射文件块或设备块,向上映射到用户程序缓存,形成Memory Mapping。多个用户程序访问同一个底层块时,Page Cache中只有一份Page,同时map到各个程序自身缓存,各个程序任何时刻看到的都是相同内容且最新的Page。
Device Driver
-
Queue
对下发到磁盘的IO做优化,磁盘臂优化算法。
-
Windows
-
设备驱动层次和类型
-
驱动链加载
系统总线驱动通过总线控制器发现所有PCI总线,
为每个PCI总线创建PDO(Physical Device Object),
同时再创建FDO(Function Device Object);
-
总线驱动通过总线FDO扫描总线感知到PCI设备,
为感知到的PCI设备创建PDO,由于总线驱动不知道如何使用PCI设备,不能为其创建FDO;
如PCI设备是SCSI适配卡:
内核将SCSI Port/Miniport驱动(SCSI控制器驱动)加载到其PDO上,并为其创建FDO,之后便可以感知到SCSI卡后端SCSI总线连接的SCSI Target设备了,并为SCSI Target创建PDO;
内核再加载Storage Class Dirver
- Storage Class Driver
1.为每个具体设备生成FDO对象,如果设备存在分区,则为每个分区生成PDO和FDO。
2.获取每个设备属性信息,比如是否支持Write Cache及其模式、最大传输单位等。
3.处理上层下发的IRP,将IRP映射翻译成包含CDB的SRB下发。
4.维护每个请求的超时机制。
5.根据底层反馈的所能接受的每个请求的最大传输单元,分割上层IRP请求。
- SCSI PassThrough
用户程序可以直接生成CDB发向底层设备,Class Driver收到CDB后会做格式检查和IO Size分割,然后将CDB封装到SRB中发送给Port Driver。
- Command Queue
Class Driver没必要对上层发来的IRP做Queue处理,Queue处理由下层的Port Driver负责,Class Driver通过设置下发给Port Driver的标记,控制Port Driver对该请求的Queue处理,如放到队首、队尾等。
- 获取设备属性信息
1.SCSI/IDE控制器的最大传输单元,即每次IO的最大可读写扇区长度。
2.是否SCSI/IDE控制器在DMA时可以读写不连续的物理内存Page,数量是多少。
3.SCSI/IDE控制器对Buffer边界对其的要求信息。
4.SCSI/IDE控制器是否支持SCSI的TCQ以及是否支持基于每个LUN的TCQ。
5.SCSI/IDE控制器是否支持WriteBack模式的写缓存,具体类型是什么,比如是否有电池保护等。
- 请求重试
大部分的IO请求重试由Port Driver来做,比如设备繁忙,总线RESET,上层Class Driver不知道也不关心,如果Port Driver重试多次依然失败,需要将错误上报给Class Driver,此时Class Driver不应该再次发起重试。
但如果是上层逻辑错误,如Check Conditions等,需要上层Class Driver解决逻辑问题并重试。
- Storage Miniclass Driver
用来针对特殊设备实现特殊功能,有厂家自行开发。
- Storage Port Driver
驱动外部总线控制器,如SCSI卡、FC卡、RAID卡。
为Class Driver屏蔽下层的LUN到适配卡的物理链路,不管LUN是通过FC网络、IP网络或是SCSI总线到达。
可以认为,Port Driver是适配卡的驱动,Class Driver是具体磁盘或者LUN的驱动。
- SCSI Port Driver
1.提供驱动的适配器属性给上层Class Driver,如最大传输单元的限制以及Write Cache属性信息等。
2.确保适配卡所连接的所有设备处于正常状态,未上电设备不会被上层使用。
3.为Class Driver提交的IRP异步请求维护队列,发生底层设备错误或设备繁忙时重试。
4.提供给接口给上层来控制自身的Queue。
- SCSI Miniport Driver
1.发现适配器并探寻其各种属性。
2.初始化适配器。
3.执行数据IO过程。
4.处理中断、总线Reset等事件。
5.维护超时计时器。
-
Linux
-
SCSI设备驱动链
-
IO Scheduler
-
NOOP
只对IO进行Merge操作,不重拍序以适应磁头臂寻道,适合不虚寻道的SSD盘。
-
Deadline
对于读IO请求,维护按读IO的LBA排序的读IO队列,以及一个按读IO进入顺序排列的表,常规情况下,将重排后的IO下发给下层驱动执行,但会引起IO被冷落的情况,所以同时维护一个计时器,对于超过一定时间(500ms)的IO,优先执行。
写IO也一样,超时时间为5s。
-
Anticipatory
对读IO进行预测,判断后续读IO的起始地址是否会和队列中的某个IO相近或相邻,如果是,则等待一段时间,等相应IO到来后重排或merge。
预测是通过计算接收读IO的平均间隔时间,和下一个读IO起始地址和上一个读IO结束地址的距离平均值,再做一定计算。
写IO不做预测。
-
CFQ
Completely Fair Queue
为不同用户的不同进程分别维护队列,在这些队列间保持公平。 -
Suggestion
在外部存储使用智能阵列时,使用CFQ保证进程IO公平调度即可,因为阵列无法感知主机IO属于哪个进程。其他优化完全可以交给阵列做,避免浪费主机资源。
-
副作用
IO被重排后,无可避免会产生主机针对同一个LBA的读和写被打乱顺序的情况,造成数据不一致。
对于存在问题的情况,由应用程序保证时序,比如DIO下的先读后写,先读到数据再下发写IO。
-
-
-
Queue Depth\Length
从应用层到设备驱动层,存在各种各样、各种层次的Queue,当底层设备IO处理不过来时,会导致Queue出现积压,从底层Queue向应用层Queue层层溢出。
Queue中只要有存货,就证明对应的下层处理存在瓶颈,Queue Length越大,瓶颈越大。从主机应用层到底层设备驱动层,再到底层存储设备内部,任意两个模块间的Queue都遵循此原则。
上层多个模块共同访问下层模块,如多个主机通过一个端口共同访问一台存储设备,各个主机的适配器Queue Depth之和要等于存储设备端口的Queue Depth,以保证不会饿死任何一台主机。
对于SSD,由于SSD可并发执行IO,那么适配卡的Queue Depth要大于等于该并发度。
- 文件系统建立在块设备之上?777
Storage IO
Disk
Disk organization
后端磁盘控制器/适配器层
RAID管理层
LUN管理层
-
LUN基于RAID的分布方式
-
纵向非条带化LUN分布方式
每个LUN的逻辑地址是纵向分布的,但是底层RAID组还是以Stripe方式来做逻辑分割并且计算校验值,这种方式下的LUN与条带是垂直的。
-
横向条带化LUN分布方式
LUN与Stripe平行,图中的Parity对于RAID5来讲是分布式的。
-
类文件系统LUN分布方式
以类似文件系统的思想,将整个RAID组作为存储资源,格式化为inode、block等,映射一个可能很大的文件作为一个LUN。
-
基于分布式RAID的LUN分布方式
一个RAID组无须独占某几块硬盘,可以与其他不同类型的RAID共享同一批物理硬盘,另外,同一块磁盘可以承载任何一种RAID类型的一部分。
-
全打散式LUN分布方式
-
-
LUN分布方式对阵列高级功能的影响
Snapshot、RAID组扩容、Thin Provision、DST等
前端接口设备及驱动层
缓存管理层
数据前处理和后处理层
IO诊断
1
症状:存储系统每秒接收到的IO数远未达到系统标称值,链路带宽也远未达到,前段接口的Queue Length远小于Queue Depth,并没有严重挤压现象,存储系统后端磁盘繁忙程度很低。主机端程序的IO延迟很低,但是吞吐量以及IOPS并未满足要求。
可能病因:IO源头并发度不够,程序使用了单线程同步IO。
治疗:修改程序使用异步IO或多线程设计。
2
症状:
存储系统美妙接收到的IO数远未达到系统标称值,链路带宽也远未达到,前段接口的Queue Length之接近Queue Depth,显示有挤压的IO,存储后端磁盘繁忙程度很低。主机程序的IO延迟很低,但是吞吐量以及IOPS并未满足要求。
可能病因:
主机端适配卡驱动或存储系统前段接口适配器驱动Queue Depth不够。
治疗:
调节以上两处Queue Depth。
3
症状:
存储系统检测到Cache Hit率极低,磁盘几乎百分百繁忙,而前端IOPS和带宽很低,业务所要求的响应时间无法满足。
可能症状:
IO随机读过大。
治疗:
治标,使用更高速高规格的磁盘或SSD,或缓存LUN等技术;
治本,改善程序的IO设计降低IO随机读。
4
症状:
从主机端或者存储系统端监测到的主机内核发出的IO从数量和容量上都表现为较高的值,通过计算理论上可以满足程序的需求,但是程序层却表现的很迟缓,IO延迟、IOPS、带宽等皆达不到从存储端监测到的数据所体现的指标。
可能病因:
主机端发生读写惩罚现象。(详见789)
治疗:
在使用内核缓存的情况下,是程序发起的IO Size为Page Size的整数倍且和Page偏移对齐,避免使用Write Through加重惩罚;
使用DIO越过缓存避免惩罚。
5
症状:
主机端并未发出大量的IO请求,但是存储系统却忙得不可开交,同时存储系统上并没有诸如Deduplication等Data Cooker的后台操作,主机端IO延迟偏高。
可能病因:
存储端发生惩罚现象。主机端IO Size未Page对齐、LUN不对齐。
6
症状:
存储系统单个RAID组中硬盘很多,转速也很快,存储系统控制器处理能力也很强大,这个RAID组中有多个LUN分配给了多个主机使用。就是不知道为什么,当一台主机访问其上的LUN时,性能很好,但是一旦有另一台主机访问这个LUN,或者访问这个RAID组上其他LUN的时候,性能骤降,两台主机获得的性能之和还不如之前单台主机所获得的性能。
可能病因:
多LUN共享同一RAID组IO冲突。LUN在RAID组中的横、纵、全打散、类文件系统等不同的分布方式,决定了对不同LUN访问时IO冲突的大小。
网友评论