频繁的磁盘IO操作可能会给程序或者系统带来意想不到的问题。工作中我们可能会碰到在压缩或者写大体积文件时,另一个同学连接上终端后敲命令会很卡,这种现象非常明显。但如果程序中频繁使用磁盘IO,产生的问题就不一定能这么明显的定位到了。
背景
个人负责的一个组件其中有针对监听指定事件,并在获取到事件时进行截屏然后回传服务端的需求。由于运行环境特殊的原因,程序是以system角色跑在windows中,无法直接获取当前用户的桌面信息,只能写一个截屏程序,并基于CreateProcessAsUser系统接口来调用,而此接口返回值只有一个error类型,无法返回其他类型。
之前初始版本实现时是在调截屏程序时传递一个uuid,截屏程序生成截屏文件,以uuid为文件名直接落盘,服务端在获取接口返回后等待截屏文件落地后读取截屏内容后再发送出去,由于图片写入磁盘需要消耗一定时间,服务端只能等待一段时间后再判断截屏文件是否生成。
实际应用后经测试发现,win2008、win7上多次触发截屏,都能获取完整渲染的图像文件,而win10中总有一定记录生成的图片显示的不全,经过多方排查后一直没找到确切原因。
在一次优化另一个模块的磁盘io占用后,不经意间发现,新版本的程序在win10出现截屏不全的几率已经降低到1/15左右,但偶尔还会出现。那么这么一看,原因基本可以确定跟频繁磁盘IO有关了(还有一方面因素,在相同资源配置下,win2008和win7系统的流畅度比win10要好的多)。截屏时要落盘,而获取截屏数据时又要重新读取,在事件产生的频率高时,因截屏产生的磁盘IO消耗就大了,极可能会出现图片未生成完毕就被读取的情况。
那么验证思路就有了,尽量避免磁盘IO!经过重新设计,新的实现方案是:服务端单独启动一个rpc server,用于单独接收截屏程序上报的截屏数据请求,然后正常下发截屏的调用。截屏程序在收到指令后进行截屏,截屏数据不写入文件,而是申请一个buffer,并直接写入buffer,然后通过本地rpc调用服务端的方法回传截屏数据。服务端在启动rpc server时单独初始化一个带过期的缓存,并用uuid为键存储接收的截屏数据。下发截屏调用的模块在发出截屏指令后直接去缓存中进行查询,查询到后再根据业务逻辑需求发送出去。
新的截屏方案避免的截屏文件写入磁盘及从磁盘读取两步IO操作,经多次测试,win10环境下截屏渲染不全的现象未再出现。
引用一个很经典的对比图