如果我们曾经在IO密集型应用中碰到过性能问题,那么我们可能都知道系统的性能会随磁盘压力增加而降低。这个因素是众所周知的,但是其背后的原因可能就不那么清晰了。本文将尝试解释这个过程背后所发生的事情。
一个典型的场景是当数据被写入一个文件时,它首先会被写入内存区域保留页面缓存;该缓存页保存还没有被写入的数据,这些数据被称为脏数据;经过一段事件后,根据IO策略,系统内核将这些脏数据刷新到持久化到硬盘的设备队列中。一旦数据到达队列,剩下的就是机器操作:设备驱动读取IO请求、接着是旋转,寻找和写入到文件所在的物理磁盘块位置。其中首先是写日志文件,然后才将数据写入实际文件中。
在最近和一些其他工程师的讨论中,提出了一个想法,通过禁用日志记录文件系统来减少磁盘写入延迟。虽然这种想法是正确的,因为这种做法使得每次磁盘写入过程中减少了一次磁盘操作,但是写日志所花费的时间可以忽略不计,原因是日志未年检和被写入的文件在同一个块中。而使用日志文件从磁盘崩溃中恢复的好处也远大于只是稍微减少的延迟的写操作。
更重要的是,IO密集型应用的瓶颈是在系统将脏数据页刷新到磁盘时而不是在写日志这一步。而刷新的吞吐量受限于设备的带宽。一个典型的转速为15K的硬盘在最好的情况下顺序存取速度可以达到120MB/秒,而在随机IO时实际带宽甚至更小。为了更好说明该问题,假设系统使用Redhat Linux系统默认的30秒刷新策略,并且应用以每秒20M的速度写入。那么30秒以后系统就会累计600M的咱数据需要刷新至磁盘(假设在此之前没有跨越脏数据页)。在Linux中,刷新操作时通过pdflush守护进程来完成的。在最优的顺序写的情况下,那么它将使用所有的pdflush线程(默认为8个线程)并且需要整整5秒将脏数据从缓存页刷新到设备队列中。这5秒的副作用是双重的:缓存页区域一直处于繁忙状态,磁盘带宽被耗尽。一种监控该情况的方式是检查磁盘队列的长度。在Linux中,主要是/proc/meminfo中的“Writeback”值或者是sar(System
Activity Reporter系统活动情况报告)“avgqu-sz”值。
当JVM的垃圾回收器正在清理堆内存而且陷入内核忙于刷新(kernel-busily-flushing)时刻时,该场景就变得更加复杂。而一些GC事件是stop-the-world(STW)的事件,这要求应用中所有线程都暂停来达到安全状态,这样堆内存中的对象就可以被移出。如果某个应用的线程在内核忙于刷新时试图写入数据,那么该线程将会阻塞在刷新人物之后,而且不能响应请求去暂停。这将导致连锁反应:繁忙的磁盘阻塞了写线程,该写线程延长了垃圾回收(GC)的暂停时间,而暂停又使应用不响应。该问题可以通过垃圾回收(GC)日志看出,其中有长时间的STW暂停,并伴随有小段CPU时间花费在于磁盘活动有关的“usr”和“sys”上。
而当系统内存使用达到上限时情况会变得更糟。默认情况下,在Linux中,通过设置“swappiness”为60可以打开内存交换功能。通过该设置,当系统内存压力大以及IO繁忙时,系统会从运行中的进程中积极的将“idle”内存页置换到磁盘(是的,更多磁盘IO)来为缓存页腾出更多空间。被置换出的进程还需要再次交换,因为当进程再次活跃时,它需要首先被交换回内存页。相反的,如果swappiness被禁用(将/proc/sys/vm/swappiness设置为0)来进程的内存页尽可能多的存放在内存中,接着没有选择,内核将更频繁的刷新脏数据到磁盘中来释放内存页。过早的和频繁的刷新增加了每秒IO的压力、降低了磁盘读写性能,这使得原有问题更加糟糕。
所以有什么缓解策略?
在系统层面,针对我们特定的IO负载通常是检查内核的刷新策略,通常要调整至最优性能需要好几轮的测试。Linux下推荐的调整开关为:
- /proc/sys/vm/dirty_expire_centiseconds
- /proc/sys/vm/dirty_background_ratio
在设备配置层面,通过将频繁访问的文件存放在不同的设备中也有助于避免单一设备队列阻塞问题。或者,如果使用的话使用多套RAID1将产生更多的设备队列,这也比使用只有一个设备队列的单磁盘要好。
例如,下图展示了有4块硬盘,2 x RAID1设置(左侧)提供了两个设备队列访问系统。相反,另外的4个设备RAID10设置(右侧)则只提供一个设备队列。
如果成本允许,可以考虑升级为SSD,它的带宽是旋转型磁盘带宽的6到7倍。对于SSD的建议是,SSD偶尔需要做数据压缩。而数据压缩对性能的影响很不好,关于这方面的讨论最好新开一篇文章讨论。同时还需要注意的是逻辑卷管理(LVM)。相对于SAS磁盘访问速度来说,使用LVM会引入额外的小延迟,但可以忽略不计;但是在使用SSD硬盘时该延迟就会变得非常值得注意,因为SSD速度本身就比比SAS快很多。
在应用层面,检查任何配置以避免双重缓冲脏数据页。例如,在Mysql中我们可以开启DIRECT_IO功能来使脏数据只是缓存在Mysql的内存中,而不是在系统缓存页。
最后但很重要,尽量避免不必要的磁盘访问。
- 本文由程序员学架构翻译
- 本文译自
http://architects.dzone.com/articles/performance-impact-io?mz=110215-high-perf - 转载请务必注明本文出自:程序员学架构(微信号:archleaner
) - 更多文章请扫码: