分类: LINUX
2014-10-20 16:19:44
原文地址:如何让linux服务器磁盘io性能翻倍 作者:尘世间一迷途小码农
在上一期博客中聊到了linux服务器的磁盘io为什么会这么慢,传送门请戳http://blog.chinaunix.net/uid-29873073-id-4514435.html
简单回顾一下之前的结论:
一 机械磁盘的io的速度主要受“寻道速度”的限制,所以在访问小文件时io性能会极差。如果不在乎成本,可以通过使用固态硬盘来解决这个问题。
二 linux的主流文件系统(如ext4等),在文件系统持续比较满,且需要经常删改文件时,会产生大量文件碎片。在我开发的一款代理服务器中,磁盘长期满负荷运转,运行一个月后文件碎片大约会让io性能降低至只剩20%-30%
这一期我们来看一下有哪些办法可以减少linux下的文件碎片。主要是针对磁盘长期满负荷运转的使用场景(例如http代理服务器);另外有一个小技巧,针对互联网图片服务器,可以将io性能提升数倍。
如果为服务器订制一个专用文件系统,可以完全解决文件碎片的问题,将磁盘io的性能发挥至极限。对于我们的代理服务器,相当于把io性能提升到3-5倍。
linux内核和各个文件系统采用了几个优化方案来提升磁盘访问速度。但这些优化方案需要在我们的服务器设计中进行配合才能得到充分发挥。
linux内核会将大部分空闲内存交给虚拟文件系统,来作为文件缓存,叫做page cache。在内存不足时,这部分内存会采用lru算法进行淘汰。
通过free命令查看内存,显示为cached的部分就是文件缓存了。
如何针对性优化:
lru并不是一个优秀淘汰算法,lru最大的优势是普适性好,在各种使用场景下都能起到一定的效果。
如果能找到当前使用场景下,文件被访问的统计特征,针对性的写一个淘汰算法,可以大幅提升文件缓存的命中率。
对于http正向代理来说,一个好的淘汰算法可以用1GB内存达到lru算法100GB内存的缓存效果。
如果不打算写一个新的淘汰算法,一般不需要在应用层再搭一个文件cache程序来做缓存。
当文件扩大,需要分配磁盘空间时,大部分文件系统不会仅仅只分配当前需要的磁盘空间,而是会多分配一些磁盘空间。这样下次文件扩大时就可以使用已经分配好的空间,而不会频繁的去分配新空间。
例如ext3下,每次分配磁盘空间时,最小是分配8KB。
最小分配的副作用是会浪费一些磁盘空间(分配了但是又没有使用)
如何针对性优化:
我们在reiserfs下将最小分配空间从8KB改大到128K后提升了30%的磁盘io性能。
如果当前使用场景下小文件很多,把预分配改大就会浪费很多磁盘空间,所以这个数值要根据当前使用场景来设定。
似乎要直接改源代码才能生效,不太记得了,09年的时候改的,有兴趣的同学自己google吧。
io访问调度
在同时有多个io访问时,linux内核可以对这些io访问按LBA进行合并和排序,这样磁头在移动时,可以“顺便”读出移动过程中的数据。
2.6内核有四种不同的排序算法,有些侧重于io性能最大化,也有一些侧重于调度的公平性,大致上的原理都类似于电梯排序。
SATA等磁盘甚至在磁盘中内置了io排序来进一步提升性能,一般需要在主板中进行配置才能启动磁盘内置io排序。linux的io排序是根据LBA进行的,但LBA是一个一维线性地址,无法完全反应出二维的圆形磁盘,所以磁盘的内置io排序能达到更好的效果。
关于LBA请参考上一期博客,http://blog.chinaunix.net/uid-29873073-id-4514435.html
如何针对性优化:
io访问调度能大幅提升io性能,前提是应用层同时发起了足够的io访问供linux去调度。
怎样才能从应用层同时向内核发起多个io访问呢?
方案一是用aio_read异步发起多个文件读写请求。
方案二是使用磁盘线程池同时发起多个文件读写请求。
对我们的http正向代理来说,采用16个线程读写磁盘可以将性能提升到2.5倍左右。具体开多少个线程/进程,可以根据具体使用场景来决定。
小提示:
将文件句柄设置为非阻塞时,进程还是会睡眠等待磁盘io,非阻塞对于文件读写是不生效的。在正常情况下,读文件只会引入十几毫秒睡眠,所以不太明显;而在磁盘io极大时,读文件会引起十秒以上的进程睡眠。
详见内核源代码do_generic_file_read会调用lock_page_killable进入睡眠,但是不会判断句柄的非阻塞标志。
linux内核可以预测我们“将来的读请求”并提前将数据读取出来。通过预读取可以减少读io的次数,并且减小读请求的延时。
如何针对性优化:
预读取的预测准确率是有限的,与其依赖预读取,不如我们直接开一个较大的缓冲区,一次性将文件读出来再慢慢处理;尽量不要开一个较小的缓冲区,循环读文件/处理文件。
究竟开多大缓冲区合适,要根据具体使用场景下的内存/磁盘io压力来决定。
虽然说“预读取”和“延迟分配”能起到类似的作用,但是我们自己扩大读写缓冲区效果要更好。
当文件扩大,需要分配磁盘空间时,可以不立即进行分配,而是暂存在内存中,将多次分配磁盘空间的请求聚合在一起后,再进行一次性分配。
延迟分配的目的也是减少分配次数,从而减少文件不连续。
延迟分配的副作用有几个:
1 如果应用程序每次写数据后都通过fsync等接口进行强制刷新,延迟分配将不起作用
2 延迟分配有可能间歇性引入一个较大的磁盘IO延时(因为要一次性向磁盘写入较多数据)
只有少数新文件系统支持这个特性
如何针对性优化:
如果不是对安全性(是否允许丢失)要求极高的数据,可以直接在应用程序里缓存起来,积累到一定大小再写入,效果比文件系统的延迟分配更好。
如果对安全性要求极高,建议经常用fsync强制刷新。
Ext4提供了一款碎片整理工具,叫e4defrag,主要包含三个功能:
1 让每个文件连续存储
2 尽量让每个目录下的文件连续存储
3 通过整理空闲磁盘空间,让接下来的分配更不容易产生碎片
有兴趣的同学可以参考
如何针对性优化:
“让每个目录下的文件连续存储”是一个极有价值的功能。
假设一个网页上有10张图片,这10张图片虽然存在10个文件中,但其实是几乎同时被用户访问的。
如果能让这10张图片存储在连续的磁盘空间中,就能把io性能提升10倍(一次寻道就可以读10个文件了)
传统的做法是通过拼接图片来将这10张图片合并到一张大图中,再由前端将大图切成10张小图。
有了e4defrag后,可以将需连续访问的文件放在同一个文件夹下,再定期使用e4defrag进行磁盘整理。
我们曾经写过一款专用文件系统,针对代理服务器,将磁盘io性能提升到3-5倍。
在大部分服务器上,不需要支持“修改文件”这个功能。一旦文件创建好,就不能再做修改操作,只支持读取和删除。在这个前提下,我们可以消灭所有文件碎片,把磁盘io效率提升到理论极限。
在我们服务器中,每个文件的缓冲区最大值设定为16MB
小于16MB的文件,在服务器准备好整个文件内容后,再创建文件。创建文件时服务器给出文件大小,文件系统保证为文件分配连续的空间。
读写文件时,服务器一次性读写整个文件。
大于16MB的文件,服务器创建文件时告诉文件系统分配16MB磁盘空间。后续每次扩大文件大小时,要么是16MB,要么就是文件终结。不允许在文件未终结的情况下分配非16MB的空间。
读写文件时,每次读写16MB或者直到文件末尾。
在我们的文件系统中,小文件完全无碎片,一次寻道就能搞定一个文件,达到了理论上最佳的性能。
大文件每次磁头定位读写16MB,性能没有达到100%,但已经相当好了。
有一个公式可以衡量磁盘io的效率:
磁盘利用率 = 传输时间/(平均寻道时间+传输时间)
对我们当时采用的磁盘来说(1T 7200转sata),16MB连续读写已经可以达到98%以上的磁盘利用率。