现场工程师大晒:使用 ImDisk 与 junction 显著提高机械盘阵高并发吞吐性能

近期,协助朋友公司调优了一个奇葩的系统。这个系统是当时公司外包几个计算机毕业生弄出来的,对一堆视频录像文件(AVI)进行处理,执行不同的筛选任务。本来朋友是叫我去指导重构的,看到现状果断放弃。

但作为一个轻易不出刀,一出刀就要劈友的骨灰级现场工程师,临阵脱逃是有损江湖威望的。 最终,通过多管齐下,内存磁盘 ImDisk + NTFS 文件夹链接+现场撸配额控制与强制删除,硬是在1天内恢复环境,随行跟班小广东大呼“大晒”,现场工程师发威,遥想当年洛夫罗。
我是服了

1 初步了解情况:坚决不能动代码

首先被告知,做系统的主要团队已经解散了,只能找到部分成员,代码文档也不完整。朋友说:能够重构是最好的。我仔细研究了1天,惊喜的发现这个系统把小作坊能出的问题都出了,就是反面教材的教科书:

  1. 没有顶层设计。一看就是几个基友QQ沟通攒起来的。每种识别算法都是独立的可执行文件,会自顾自地读文件、产生结果文件。全部依靠定时器扫描文件夹处理,处理完成后改名或者搬移走来触发下一步流程。
  2. 没有数据规范。同类的算法,比如边缘检测和亮斑检测,产生的结果文件格式不同,一个是BMP格式的切片,一个是matlab的.mat, 还有的是TXT文件(行列坐标)且没有文档。
  3. 运行时与开发环境不切割。主干是openCV的C++程序,但还调用matlab引擎和python脚本。VC的程序是Debug版的,直接在Debug文件下启动。
  4. 没有提供完整源码。朋友不懂技术,说是外包前说过要提交源码。源码文件夹只有少量python代码和BAT的批处理脚本,被忽悠了。大量C/C++模块只有Debug文件就和dll和exe, Matlab的源码倒是有,但是不多。
  5. 文件夹配置混乱。关键的数据交换文件夹一定是E:\AVI, 没有配置文件可以修改。一些处理文件夹不能有汉字和空格,但是另一些dll的名字又是 “边缘处理1.dll"。
  6. 配置文件位置不统一。位置扔的乱七八糟。配置文件格式不统一,xml\json\ini\txt都有。
  7. 数据流转混乱。依靠磁盘,共享文件夹、FTP交换数据。路径、用户名口令全部写死,配置文件只能改IP地址。
  8. 数据库设计缺陷。绿色版的mysql,一个文件夹。但是数据库就1个表,和excel没有差别。表里没有索引,数据模型冗余,毫无范式可言。

去重构?这项目我是不可能去重构的。引用下面的图轻松一下!

BUG1BUG2BUG3

2 并发读写-机械磁盘阵列的软肋

这个系统虽然有这么多问题,但当时还是通过了测试。据朋友说,用的是4路网络摄像头采集的视频文件,测试了72小时木有问题。这充分说明,小作坊做的软件,还是堪用的。那为什么又抓狂了呢?有朋友自己的原因,因为该系统原本部署的环境和现场的环境不同。朋友不懂技术,觉得都是摄像头,没有问题。带着尚在公司的剩余成员(布线的),多接了一个客户,但是到现场部署后,才发现摄像头都是闭路高清的,且多了4组。

遇到的主要问题是盘阵罢工。视频来不及写入,程序来不及处理。生产环境的计算机配置不赖。虽然是攒的山寨服务器,却有128G内存,以及一个10TB的RAID-5盘阵。组成盘阵的是西X的7200转。根据初步估算,光纤加持应该是来的及存储的。为什么会发生这样的事情呢?

通过资源监视器,观察到工作状态下,竟然有30多个进程同时访问多个视频文件和中间结果文件。通过现场询问尚能到场的原始团队成员,了解到各类处理进程都是独立读写文件,导致对同一份数据,会读取N次。由于算法无法实时处理,团队也是下了功夫的。他们采用了一个多进程的松散队列,如下图所示:
架构

  1. 视频采集软件每分钟生产8个AVI文件,一个对应一个摄像头。各个功能进程组成多级的处理群,排队处理。
  2. 除了第一组功能只访问原始文件,后续很多功能都需要访问前面生产的结果文件,少量进程是既要原始文件,又要前序结果文件。
  3. 结果文件有的很大、有的很碎片化,结果就是“又大又碎片化”。
  4. 进程之间没有交互,都是靠扫描文件的方式处理。因此,当需要的各层文件不齐全时,会等待几秒再次扫描。
  5. 图上没有画出的是数据库。比起文件的流量,数据库那一点流量和范式问题可以不去理会。

因为是高清视频,为了实时处理,队列中的功能进程比原本配置提高了4倍,共有30多个。

2.1 机械盘阵首先是“机械”

盘阵速度快,是因为各个硬盘分享了整体的数据流量。但如果组成盘阵的成员是机械硬盘,则需要非常注意随机存取的问题!机械磁盘的顺序读写是很快的,但随机读写会导致磁头的寻道时间陡增,延迟和速度会下降。
机械硬盘这个文章详细介绍了机械盘阵的随机存取性能比较,可以参考。

机械硬盘随机IO慢的超乎你的想象

这里引用文中插图:
顺序随机
实际测试现场的盘阵,比上图还要糟糕:随机并发读写下,盘阵缓存无法命中,导致各个磁盘疯狂寻道,效率下降100倍。在现场30多个进程并发读写的场景下,RAID延迟巨大无比,导致文件夹都打不开。

2.2 可行方案-二级架构固态盘阵

一种不去动现有程序的方案,是使用固态盘阵作为介质。稍微经济一些,则考虑采用固态缓存+机械盘阵的二级结构:

  1. 原始数据直接落固态盘阵
  2. 所有的中间处理都在固态盘阵
  3. 最终结果顺序备份到机械盘阵

固态硬盘现在也有M2的TB级别硬盘,1万之内可以搞定。但由于朋友希望现场0元解决这个问题,只能再想办法。

3 现场解决方案

3.1 内存虚拟磁盘ImDisk替代固态盘

由于看到这台工作站是128GB土豪内存,想到现场下载内存虚拟磁盘ImDisk虚拟出一个内存盘来。
二话不说,下载一个:

https://sourceforge.net/projects/imdisk-toolkit/
ImDisk安装后,直接开辟一个64GB的虚拟磁盘,作为A盘(我有软驱怀旧综合症)
注意,

  1. 把临时文件夹选项去掉,我们的盘专门用于处理视频。
  2. 把动态内存分配钩上,这样可以减少内存消耗。

ImDiskSet创建后,我的电脑就看到了A盘。

3.2 NTFS链接工具junction 挂载链接

当安装了A:盘后,惊喜的发现,系统的处理文件就只能放在E:\AVI。已经辞职的程序猿拒绝修改代码,没办法,当时签的野合同,无法通过合同追溯,这太糟糕了。怎么办?!

如果是Linux,直接ln -s就可以了。不过我们在windows下。好在,windows下的NTFS文件夹支持类似的功能。下载工具 junction即可:

https://docs.microsoft.com/en-us/sysinternals/downloads/junction

用法很简单:
删除E:\AVI,而后:

C:\>junction.exe E:\AVI A:\

junction

此时,你会发现E:\AVI又出现了,但是多了一个小的快捷方式图标。

3.3 开发简单配额控制与搬运工具

这个磁盘只有64GB, 只能存储一小会儿。如何保证数据被及时搬走呢?
团队运维现场撸了一个同步程序,主要功能:

  1. 监视文件夹(内存盘),发现新的结果文件后立刻拷贝到目的文件夹(盘阵)
  2. 统计内存盘所有文件大小,按照时戳排序。
  3. 当大小超过配额(比如56GB),则删除最老的文件,直到配额满足要求。

3.3.1 枚举文件

使用Qt 的工具,可以枚举文件夹下的所有文件。
为了配额时删除最旧的文件,采用字典进行自动时刻排序。

std::map<qint64,QMap<QString,QFileInfo> > m_cache_files;
qint64 m_total_size = 0;

m_cache_files这个字典的键是毫秒时刻,值为一个映射,文件绝对路径和文件信息。
同时,设置一个整形m_total_size记录文件夹的总大小。
有了上述数据结构,直接递归枚举所有文件夹。


QFileInfoList DialogFileLoadCtrl::enumFiles(QString dirS)
{
	QStringList lst;
	lst<< "*.*";
	lst<< "*";
	QDir dir(dirS);
	dir.makeAbsolute();
	QFileInfoList linfo = dir.entryInfoList(lst);
	QFileInfoList lstRes;
	foreach(QFileInfo i, linfo)
	{
		if (i.isDir())
		{
			QString dn = i.fileName();
			if (dn=="." ||dn==".." )
				continue;
			QFileInfoList lst_sub = enumFiles(i.absoluteFilePath());
			std::copy(lst_sub.begin(),lst_sub.end(),std::back_inserter(lstRes));
		}
		else
			lstRes << i;
	}
	slot_next_prg(0);
	return lstRes;
}

void DialogFileLoadCtrl::updateMap()
{
	QFileInfoList lst = enumFiles(ui->lineEdit_src_dir->text());
	m_total_size = 0;
  //......
	foreach (QFileInfo i, lst)
	{
		QString fm = i.absoluteFilePath();
		unsigned long long sz = i.size();
		qint64 tm = i.fileTime(QFile::FileBirthTime).toMSecsSinceEpoch();
		//新文件直接拷走一份备份,防止内存断电
		if (!m_cache_files[tm].contains(fm))
		{
			//...
			TransFile(i);
		}
		//记录字典
		m_cache_files[tm][fm] = i;
		m_total_size += sz;
		++m_total_files;
	}
}

3.3.2 搬运文件

搬运文件注意的是要恢复并创建文件夹结构。Qt的QDir基本上解决了一切需求:

void DialogFileLoadCtrl::TransFile(QFileInfo ifile)
{
	QDir dirSrc(ui->lineEdit_src_dir->text());
	//获取相对监视根文件夹,现有文件的相对路径。
	QString srcFile = dirSrc.relativeFilePath(ifile.absoluteFilePath());
	QString relDir = dirSrc.relativeFilePath(ifile.absolutePath());
	//以备份目的根文件夹为起点,恢复目的文件的绝对路径
	QDir dirDst(ui->lineEdit_dst_dir->text());
	QString dstDir = dirDst.absoluteFilePath(relDir);
	QString dstFile = dirDst.absoluteFilePath(srcFile);
	//mkpath 直接创建一串路径
	QFileInfo info(dstDir);
	if (!info.exists())
		dirDst.mkpath(dstDir);
	//拷贝文件
	//...
	QFile::copy(ifile.absoluteFilePath(),dstFile);
}

3.3.3 删除确保配额

定期检查总大小,删除最旧的文件:

void DialogFileLoadCtrl::cleanFile()
{
	while (m_total_size > MAX_SIZE/*16GB in Bytes*/)
	{
		if (!m_cache_files.size())
			break;
		qint64 tmf = m_cache_files.begin()->first;
		QMap<QString,QFileInfo> & mp = m_cache_files[tmf];
		if (!mp.size())
			m_cache_files.erase(tmf);
		else
		{
			QString  fm = mp.begin().key();
			QFileInfo  ifi = mp.begin().value();
			if (QFile::remove(fm))
				m_total_size -= ifi.size();
			mp.remove(fm);
			//.实际情况可调用https://github.com/michaelknigge/forcedel确保强制删除
			if (!mp.size())
				m_cache_files.erase(tmf);
		}
	}
}

3.4 强制删除过期文件

有了上述操作,基本搞定了部署。但是,天杀的发现有一个程序只打开文件,有时不关闭文件,导致其打开的中间结果,在正常删除环节删不掉。怎么办呢?强制删除工具很多,但免费能获取的命令行工具屈指可数。现场搜索了1个小时,找到了神器:使用.net命令行工具
https://github.com/michaelknigge/forcedel
强制删除没有关闭的文件。用法很简单, 暴力删除10次!

for (int itry=0;itry<10 &&QFileInfo::exists(filename);++itry)
{
	QStringList a;
	a<<filename;
	QProcess::execute("ForceDel.exe",a);
	QThread::msleep(200);
}

Windows下调用ForceDel开源工具,要安装.Net3.5.在Windows Server 2012以上,默认是木有的,要把安装光盘插进去,在角色与功能里安装。或者拷贝安装光盘的SxS文件夹到本地,指定备份源位置。

4 总结

经过改造,64个处理器核心占用率80%,基本满足实时处理需求。相关代码见
我的gitcode.net代码仓库
无论如何,上述补救措施都是治标不治本的措施。但这次运维很有意思,告诉我三个事情:

  1. 如果开发团队很差劲,有超级厉害的运维团队,往往也能把项目跑起来。对于小公司来说,追求“堪用”已经很不容易了。不知道有多少线上的光鲜页面背后,是焦头烂额的运维和一堆重启脚本。
  2. 内存映射盘真香,有了这个东西,文件接口的优势就来了!简单啊!一堆fread fwrite就暴力搞定了!
  3. 当老板可以不懂技术,但一定要认识懂技术的自己人。
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
ImDisk Toolkit 中文版是一款适用于 Windows 的免费映像安装程序,可用于安装大量选择的 CD/DVD,硬盘驱动器和软盘映像,如 ISO,MDF,CCD,CUE 等。 所选的映像可以打开为虚拟硬盘分区,虚拟光盘驱动器或虚拟软盘驱动器,具体取决于它们在创建之前的状态,分区,CD/DVD 或软盘驱动器。 虚拟磁盘映像安装工具 ImDisk Toolkit x64 中文特别版 虚拟磁盘映像安装工具 ImDisk Toolkit x64 中文特别版 对于那些不知道的人,RAM 磁盘是由随机存取存储器或系统存储器创建的硬盘驱动器。 这种存储的主要优点是速度非常快,可以以非常高的速度在文件上复制文件。 缺点是在关闭计算机后,数据会丢失,因为这种类型的存储需要始终存在电源以便维护数据。 对于临时存储,您需要在每次重新启动计算机时刷新,这种类型的驱动器是完美的。 ImDisk Toolkit 主要功能包括: 小巧轻便 – 它只有几 MB 大小 – 资源使用率低 允许您创建虚拟分区,CD/DVD 驱动器或软盘 只读模式 – 可以在只读模式下打开图像以防止编辑 可移动驱动器创建器 – 为您提供弹出驱动器的选项 RAM 磁盘创建器 – 从系统内存创建存储分区 ImDisk Toolkit 工具将允许您安装硬盘驱动器,CD-ROM 或软盘的映像文件,并创建一个或多个具有各种参数的 Ramdisk。ImDisk Toolkit 软件包包括 ImDisk Virtual Disk Driver(2.0.10),DiscUtils 库,它扩展了支持的映像文件格式的数量,并添加了几个 GUI 和功能。 系统要求: Vista,7,8,8.1 或 10(64 位)。 某些映像文件格式还要求使用 DiscUtils 库安装 .NET Framework 4(包含在 Windows 8 和更高版本中)。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

丁劲犇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值