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

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没有差别。表里没有索引,数据模型冗余,毫无范式可言。

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


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/
安装后,直接开辟一个64GB的虚拟磁盘,作为A盘(我有软驱怀旧综合症)
注意,

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

创建后,我的电脑就看到了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));}elselstRes << 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. 当老板可以不懂技术,但一定要认识懂技术的自己人。

机械盘阵高并发——使用ImDisk 与 junction显著提高整体吞吐性能相关推荐

  1. oracle rac 高并发性能_高并发业务下 JVM 涉及的垃圾回收与性能问题分析与定位...

    最近好多 Java 的朋友问:"高并发业务场景下,JVM涉及的性能问题好难搞呀--".看来是大家的技术经验相对少了些,拿不准该从哪些地方上手,其实,每个技术人要该懂得怎样更好打造自 ...

  2. 刘知远团队提出:如何通过扩大高质量指导性对话数据集,来提高模型的性能和效率...

    深度学习自然语言处理 原创 作者 | 刘嘉玲 随着开源语言大模型(LLM)的百花齐放,模型的性能和效率关乎到产品的成本和服务体验的均衡.那么,有没有办法让语言大模型变得更高效.更优秀呢? 为了进一步提 ...

  3. 优化杭州某著名电子商务网站高并发千万级大型数据库经验之- 磁盘I/O性能

    好久没写博客了,一方面是日常工作繁忙,另外一方面是想更多的时间陪陪家里人,享受春天的美好时光,同时还在写一本<程序员,你伤不起>的一本书要由人民邮电出版社出版:公司有2台超级强大的服务器, ...

  4. 高性能高并发系统的稳定性保障

    高性能高并发系统的稳定性保障 原创 2016-12-21 肖飞 开涛的博客 作者:肖飞,于2011年8月份加入京东,曾亲身参与到京东的应用性能监控.统一日志.流式计算.内存缓存.四层防攻击等一些基础技 ...

  5. 千万别说做过高并发!

    懂高并发性能调优,一定是你在技术进阶赛道变得牛逼的加分项.不论,你是开发,架构还是管理岗,亦或者是其他互联网相关岗位. 因为毫不夸张的说,在现在动辄过千万级的并发流量环境下,懂得并发压测.性能瓶颈诊断 ...

  6. 读数据库遇到空就进行不下去_如何解决高并发场景下缓存+数据库双写不一致问题?...

    推荐阅读: 一只Tom猫:手撕分布式技术:限流.通讯.缓存,全部一锅端走送给你!​zhuanlan.zhihu.com 一只Tom猫:MySQL复习:20道常见面试题(含答案)+21条MySQL性能调 ...

  7. 深入浅出!阿里运维专家三种方法教你如何应对高并发“海啸”场景

    作者:田杰,阿里云数据库高级运维专家 在数据库的日常使用中,来自应用的高并发场景并不罕见,其标志性的表现为 高新连接创建速率(CPS,比如 PHP 短连接).发送大量请求到 DB 数据库层. 如同 海 ...

  8. java rabbitmq 并发_RabbitMQ消息中间件 高级篇二 高并发情况下保障消息投递可靠性...

    RabbitMQ消息中间件技术精讲9 高级篇二 高并发场景下,消息的延迟投递做二次确认进行回调检查来保障生产者消息投递成功的可靠性 在上一篇文章中,我们介绍了BAT大厂中一种方式保障生成者消息投递可靠 ...

  9. Java高并发程序设计入门

    转自:http://blog.csdn.net/johnstrive/article/details/50667557 说在前面 本文绝大部分参考<JAVA高并发程序设计>,类似读书笔记和 ...

  10. Golang高并发安全(一)

    一.场景 很多时候,我们希望加载配置时只加载一次,因为如连接数据库时较慢且不必加载多次:我们希望关闭通道时只关闭一次,因为关闭多次会报错:我们希望访问一个公共资源时,该资源是完整可靠的. 二.介绍 G ...

最新文章

  1. 在vscode中使用pylint-django插件解决pylint的一些不必要的错误提示【转】
  2. 二值化每个特征,微软用1350亿参数稀疏神经网络改进搜索结果
  3. mybatis报错invalid types () or values ()解决方法
  4. Android下ListView的分页(9.6)
  5. 新闻发布项目——分页公共类(PageUitl )
  6. html 为元素附空值,HTML空(void)元素有哪些?
  7. 软件测试(原书第2版中文)PDF版
  8. 出现这6种评估违法状况,征收补偿决定可能被撤销!
  9. Python进阶(十二)常用数据处理模块
  10. WayOs内置智能重启:自动计算UTC时间为本地时间,可以调整为几时重启
  11. 【入门指南】使用阿里云Elasticsearch搭建ELK日志系统
  12. Android 判定手机是否root
  13. 收到客户反馈的BUG,主管应该怎么办
  14. 从数据库、代码和服务器对PHP网站Mysql做性能优化
  15. python csv wordpress xmlrpc_wordpress_xmlrpc模块批量自动发布文章到wordpress - 老牛博客...
  16. 威联通建php邮件服务器_威联通 ※ 群晖 虚拟机性能对比 我可能要碰瓷 eSir
  17. Linux Mint 20.3更改源及软件安装
  18. 世嘉MD游戏开发进阶篇【二】:C语言实现有限状态机
  19. 山大商院java课程_山大商院必读书目推荐——《社会科学方法论》
  20. Linux系统CentOS 7中安装配置JDK

热门文章

  1. 4.3.2 信道编码 ——卷积码
  2. 天大18春在线考试计算机答案,天津大学期末考核18春学期《画法几何及工程制图》在线作业一全套习题答案...
  3. C语言实验源程序保存,c语言实验1程序开发环境.doc
  4. 使用百度地图时,经纬度坐标正确,但位置只显示北京
  5. 富士施乐Fuji Xerox DocuPrint CP215 w 驱动
  6. python判断是否有弹出窗口_使用Python爬取弹出窗口信息的实例
  7. java读取zip文件损坏_java zip压缩文件损坏
  8. AI之语音转写项目实践
  9. 卸载adobe系列产品工具
  10. 从硅谷“神秘”孵化器,看中美医疗创新差别在哪里