BililiveRecorder icon indicating copy to clipboard operation
BililiveRecorder copied to clipboard

优化多直播间同时录制时的磁盘碎片问题

Open 821938089 opened this issue 5 years ago • 22 comments

多个主播同时上播录制时会产生大量的磁盘碎片。 叠瓦盘特色碎片太多读取会显著变慢,比普通垂直机械硬盘慢得多,我用ffmpeg转封装极端情况下只有几MB的读取速度,而每次录播的文件平均都是7-10GB左右,只有几MB的读取速度简直慢到爆炸。 强烈建议要给录播文件做磁盘空间预分配。

821938089 avatar Jan 10 '21 14:01 821938089

感觉可行性不是很高,首先录播文件大小不能提前知道,而且录播随时都有可能开始没法等一两分钟或更长时间写硬盘。 如果你有具体怎么实现的想法可以说一下。

Genteure avatar Jan 10 '21 14:01 Genteure

我查了下相关资料,我觉得可以这样,简单说就是先占一部分连续的空间,进行直播数据的写入,不够就继续占,这样虽然可能也会产生碎片,但是只要碎片够大,数量少,以减少复杂的寻道操作,对于读取性能的影响会小得多。

具体来说就是先在磁盘空的地方先占1G(或更大)的连续磁盘空间,然后开始写入直播数据,然后写入到达80%~90%的时候,(如果可以就)继续在后面占1G的连续磁盘空间,不行就在附近再找一块连续的磁盘空间进行写入。

我不太清楚我说的东西在实际的技术细节实现上是否可行,你参考下吧。 你也可以参考下开源的qBittorrent是怎么实现的预分配,然后魔改亿下。

821938089 avatar Jan 11 '21 15:01 821938089

是的,这个科技很简单,调用 FileStream.SetLength 就可以实现预先分配。在我的下载器 https://github.com/dotnet-campus/dotnetCampus.FileDownloader 里面就用到这个技术

lindexi avatar Jan 23 '21 05:01 lindexi

@lindexi 据我了解 FileStream.SetLength 并不会预分配硬盘空间。

https://stackoverflow.com/a/41516109

Genteure avatar Jan 23 '21 05:01 Genteure

多谢大佬,软件很好用。

似乎加上了支持随机读写参数之后,是有预先分配的

lindexi avatar Jan 23 '21 05:01 lindexi

我个人还是感觉实现起来会比较费劲。 之后一阵子的重点是重构大部分逻辑,这样的功能改动又大、需求又不很大、又和写入逻辑关系密切,就先搁置吧。。至少等到重构完之后再考虑了。

Genteure avatar Jan 23 '21 05:01 Genteure

期待

lindexi avatar Jan 23 '21 08:01 lindexi

@821938089 你是用什么工具看出来有大量磁盘碎片的?还是猜的?

Genteure avatar Jan 24 '21 03:01 Genteure

测试了一下两个直播间同时录,确实碎得一塌糊涂。

一个可能更可行的方案是,在内存里缓存一下输出内容,每 n MB 写一次硬盘,多个直播间的数据轮流写入而不是同时写入。

缺点

  • 内存使用量会增加
  • 数据写入不及时
  • 如果软件崩溃,还未写入硬盘的数据会丢失

优点

  • 应该能降低碎片数量(效果还需具体测试判断)
  • 不需要提前预分配硬盘空间
    • ...因此对录到 Network Share 或 OneDrive 同步盘等位置的用户比较友好
    • ...也不需要写 0 占位
    • ...如果软件崩溃,不会留下一个全是 0 的大文件
    • 结束录制时不需要清除文件结尾未使用的空间

Genteure avatar Jan 24 '21 05:01 Genteure

我使用的检查 fragmentation 的工具:

contig diskview

之后重构 1.3.0 的时候会记着这个需求预留好接口,然后到 1.3.1 或者之后的版本再实现。

Genteure avatar Jan 24 '21 06:01 Genteure

@821938089 你是用什么工具看出来有大量磁盘碎片的?还是猜的?

用磁盘碎片整理工具UDefrag可以以可视化的方式查看磁盘上的文件碎片分布情况,我看了下,一个几GB的录播能碎成几千块碎片。

其实从任务管理器的磁盘活动时间也可以看的出,读取碎片化文件非常占磁盘活动时间,大多数应该都是浪费在寻道时间上了。

821938089 avatar Jan 24 '21 07:01 821938089

@821938089 没记错的话 qBittorrent 使用的是 SetFileValidData,这需要预先知道文件大小,且在没有特殊权限的情况下,会将硬盘那部分区域写 0,性能并不好。

@Genteure 手动进行缓存不如直接使用系统的缓存功能,看上去 VMware 暂停的时候能直接把虚拟机的内存数据变成“已修改”,系统会在后台自动写进硬盘,此时关闭 VMware 并不影响。估计是有相关接口可用的。

ysc3839 avatar Feb 23 '21 16:02 ysc3839

@ysc3839 不需要预先知道文件大小,可以先分配1G做写入,快满了再申请1G空间。 除非这个函数只能在创建文件的时候只能用一次,但是我觉得应该不太可能。 特殊权限可以申请的嘛,设计一个设置让用户选择就好了。

821938089 avatar Feb 26 '21 09:02 821938089

看上去 VMware 暂停的时候能直接把虚拟机的内存数据变成“已修改”,系统会在后台自动写进硬盘,此时关闭 VMware 并不影响。

@ysc3839 这段话是什么意思?没理解

Genteure avatar Feb 26 '21 12:02 Genteure

写了一半的录播姬 1.3 已经 push 到了 dev-1.3,核心功能已经完成,WPF UI 还没改完但主要功能能用

如果有人有兴趣帮忙实现这个功能的话,实现这个功能需要:

  • 新建一个 class 实现 BililiveRecorder.Flv.IFlvProcessingContextWriter 或者 继承 BililiveRecorder.Flv.Writer.FlvProcessingContextWriter,在这个新的 class 里实现对磁盘碎片问题的优化
  • 修改 BililiveRecorder.Core\Config\V2\build_config.data.js 添加一个控制写入模式的配置项
    • 运行 node build_config.js 生成新的 C# 代码、然后运行一下格式化
  • 修改 BililiveRecorder.Core.Recording.RecordTask 根据配置项使用不同的 Writer 进行写入

Genteure avatar Feb 26 '21 14:02 Genteure

@Genteure 任务管理器的内存中有一段是 Modified,当 VMware 暂停虚拟机时,可以看到原本是 In use 的那块可以直接转变为 Modified,然后 VMware 就可以退出了,这部分数据会由系统慢慢写入硬盘。

ysc3839 avatar Feb 28 '21 15:02 ysc3839

@ysc3839 看你描述感觉应该是 Memory-mapped files,我感觉不太适合用来实现这个功能?

@821938089 可以试一下 1.3 ,以前是每个 Tag 写一次,现在是每个 GOP 写一次,如果 1.3 录出来的硬盘碎片数量比之前版本的少,就说明自己实现缓存、定期一次性写入的思路是可行的。

Genteure avatar Feb 28 '21 16:02 Genteure

实在不行先写入到临时文件,临时文件到一定大小了追加到正式文件里 一个直播间占几百兆临时文件无所谓吧。 考虑问题: 多个直播间同时从临时文件读取追加是否会造成碎片 整体读入临时文件是否会大量占用内存

优点: 不用提前分配空间 如果软件崩溃,可从临时文件恢复

缺点: 双倍磁盘读写

liushengqi000 avatar Apr 20 '21 02:04 liushengqi000

碎片在录制时主要影响就是要在数据和文件表之间来回寻道。

只有内存(或ssd)缓存可以解决这个问题。

对磁盘做什么操作并没有什么用。
预分配不合逻辑:如果空文件(快)用完了,这时候磁盘必须专门写0,不能同时录像,否则这部分录像和0文件就一起碎了。这时候录像只能在内存里,那么最终还是个内存缓存。虽然确实解决了碎片问题,但软件复杂度和崩溃的风险一点都不会少

解决碎片问题,就是用内存缓存(池)。弄一个可以设置大小的缓存,每个直播间都申请这么大的内存,网络数据先缓存起来,哪个满了就申请新缓存,然后把满的写入并回收。

我一般不考虑程序崩溃的问题。。。录像断了1秒钟和1分钟的结果是一样的。。。重要的录像当然是多人多点同时录。。。

至于内存占用,条件差的设置个32MB,这么大的碎片基本上已经不影响转封装时读取了,按机械硬盘100M/s写入、每个直播间5M/s算,50个直播间跑满也才1.6G内存,全部双缓存也就不到4G,谁真有这么高需求,肯定也不差这么点内存了。。

GongT avatar Jun 21 '21 17:06 GongT

目前 1.3.x 的标准录制模式是每个 GOP 写入一次硬盘,每个 GOP 根据推流侧的设置一般在 1 秒到 10 秒左右,大小大约 0.12 到 12 Mibytes,和单独设置个内存缓存有点类似。

不过目前还是多次调用 file.Write(...) 写入的,不知道会不会有什么影响。具体还是得靠实际测试来决定。

如果测试的话, @lindexi 上面提出的 file.SetLength 也应该试一下,我后来又研究了一下, 好像 是有效的?

如果有人有时间有兴趣的话能帮忙测试一下就最好了( 在测试得出结论之后,目前的项目代码结构要实现一个缓存层相对还是比较轻松的。

Genteure avatar Jun 22 '21 04:06 Genteure

我直接用PrimoCache套了个写缓冲,每5分钟才写一次磁盘,碎片,不存在的

YuhaoTang avatar Aug 12 '21 00:08 YuhaoTang

在 .NET 6 有一个新的 API 也许可以很好在这里用上

FileStream file preallocation performance · Issue #45946 · dotnet/runtime

lindexi avatar Sep 03 '21 11:09 lindexi