🧸为什么unRAID下载/拷贝文件时CPU占用高:详解FUSE-SHFS、I/O问题和解决办法
00 分钟
2023-7-19
2024-4-17
type
status
date
slug
summary
tags
category
icon
password
本文会涉及到 unRAID 的存储挂载目录,如果你还不是很了解,你可以参考博主的另一篇文章 《新手教程:unRAID 存储目录结构说明》

1、为什么下载或写入文件时 CPU 的负载高?

除了常见的 BT/PT 下载会造成 CPU “负载高”的情况之外,拷贝文件到 unRAID 阵列有时候也会造成此类情况。
大多数情况下,实际上并不是 CPU 负载高,而是因 I/O wait (I/O 等待)过高而导致了 CPU 看起来处于“高负荷”状态。
不过需要说明的是,拷贝文件到 unRAID 上是需要消耗 CPU 算力的,因此如果你的 CPU 性能比较弱,那么拷贝文件的确会让 CPU 负载高。但是对于现今来说,除非你用的是古董级别的 CPU,否则只是拷贝文件不会给 CPU 造成什么太大压力。
那么什么是 I/O wait ?
I/O wait 是指系统中的进程在等待输入/输出(I/O)操作完成时所花费的时间。当进程需要从磁盘、网络或其他外部设备读取或写入数据时,它会发起一个 I/O 请求并等待操作完成。在这个等待过程中,进程会被标记为处于“ I/O wait 状态”,并且无法执行其他任务。
I/O wait 通常是由于以下情况引起的:
  1. 磁盘访问延迟:当进程需要从磁盘读取数据时,如果磁盘响应较慢或者存在磁盘访问冲突,进程就会进入 I/O wait 状态。
  1. 网络延迟:当进程需要通过网络传输数据时,如果网络延迟较高或带宽不足,进程就会在等待数据传输完成时进入 I/O wait 状态。
  1. 外部设备延迟:当进程需要与外部设备进行交互时,如果设备响应较慢或存在设备访问冲突,进程就会进入 I/O wait 状态。
在系统中,一定程度的 I/O wait 是正常的,因为许多应用程序需要与外部设备进行交互。然而,过多的 I/O wait 等待可能会导致系统性能下降,因为进程无法有效利用 CPU 资源。
在 unRAID 中我们可以通过 top 命令来查看 I/O wait 的数值:
notion image
notion image
如果 top 命令查看到 wa 的数值比较大,那么此时在 unRAID 的状态面板中就会看到 CPU 处于“高负载”状态,如:
图片源自 unraid 官坛
图片源自 unraid 官坛
在上面的图片中,可以看到 wa 的数值比较大(67.4 wa),表明此时 CPU 因进程的 I/O 任务时间过长,CPU 资源无法得到有效利用,就造成了 CPU 看起来负载很高的情况。
这就有点类似于公路上有几台龟速形式的汽车导致了道路拥堵的情况,所以即使道路再宽、车道再多也得不到有效的利用。

2、为什么会出现这种问题?

2.1、首先需要先了解 unRAID 上的 FUSESHFS 这两个概念

在解释原因之前,大家需要先花点时间了解一下 FUSEFilesystem in Userspace)和 SHFS(Share Filesystem)这两个东西。

(1)什么是 FUSEFilesystem in Userspace

当我们使用计算机时,我们通常会与文件系统进行交互,例如打开文件、读取数据、写入文件等。传统的文件系统是由操作系统内核提供和管理的。但是,有时我们希望创建一种特殊的文件系统,而不是仅仅依赖于操作系统提供的标准文件系统
这就是 FUSE(Filesystem in Userspace)发挥作用的地方。FUSE 是一种允许开发者在用户空间创建自定义文件系统的技术。与传统的文件系统不同,FUSE 将文件系统的实现从内核空间转移到用户空间,这使得开发者能够更加灵活地实现新的文件系统类型。
总的来说,FUSE是一项技术,它允许开发者创建自定义文件系统,并以用户空间的方式与操作系统进行交互。
更多关于 FUSE 的资料

(2)什么是 SHFS(Share Filesystem)

unRAID 阵列(Array)所使用的文件系统叫做 SHFS(Share Filesystem),是由官方基于 FUSE 所创建出来的、unRAID 独有的文件系统,这个文件系统的挂载点之一是 /mnt/user ,也就是 unRAID 中共享文件夹所存储的地方。
如果你在 unRAID 的命令行窗口执行以下命令:
那么你会看到有以下两个基于 FUSE 所创建出来并挂载了的 SHFS 文件系统:
notion image
是不是看到了很熟悉的一个路径 /mnt/user ?没错,这就是 unRAID 阵列中共享文件夹的存储位置:
notion image

(3)unRAID 中的 SHFS 的作用是什么

我们来看下官方文档中是如何介绍 SHFS 的:
User Shares are implemented by using Linux Fuse file system support. What they do is provide an aggregated view of all top level folders of the same name across the cache and the array drives. The name of this top level folder is used as the share name. From a user perspective this gives a view that can span multiple drives when viewed at the network level. Note that no individual file will span multiple drives - it is just the directory level that is given a unified view.
用户共享(User Shares)通过使用 Linux 的 Fuse 文件系统支持来实现。它们的作用是提供一个聚合视图,跨缓存和阵列驱动器的所有具有相同名称的顶级文件夹。该顶级文件夹的名称被用作共享名称。从用户的角度来看,在网络级别上,这提供了一个可以跨多个驱动器的视图。请注意,没有任何单个文件会跨越多个驱动器-只有目录级别获得了统一的视图。
When viewed at the Linux level then User Shares will appear under the path /mnt/user. This includes the files on the main array and also any for the share on any pool. It is important to note that a User Share is just a logical view imposed on top of the underlying physical file system so you can see the same files if you look at the physical level (as described below for Disk Shares).
在 Linux 级别上查看时,用户共享将显示在路径 /mnt/user 下。这包括主阵列上的文件以及任何池中共享的文件。重要的是要注意,用户共享只是强加在底层物理文件系统之上的逻辑视图,因此如果您在物理级别查看,则可以看到相同的文件。
官方文档的示意图
官方文档的示意图
作者的另一个视图
作者的另一个视图
官方的说明不好理解,我站在用户的角度对上面的内容进行整理:
  • 用户共享(User Shares)是基于 SHFS 来实现的,呈现给用户最直观的就是 WebUI 界面中的“共享”(SHARES)。
    • notion image
  • 用户共享的作用是提供一个“聚合视图”:这里所指的聚合视图指的是逻辑上的概念。我们知道 unRAID 的阵列(Array)和缓存池(user-definded pool)可以由多个物理存储设备组成,每一个物理存储设备中可能存放着各种零零散散的文件,那对于 unRAID 来说该如何将这些不同存储设备中的文件都通过一个“出口”去呈现给用户呢,总不能让用户到一个个硬盘里面去找文件吧?所以就有了用户共享这个东西。
  • 在“聚合视图”下,用户看到的只有“具有相同名称的顶级文件夹”:也就是说用户在共享所看到的一个个文件夹都是每一个物理存储设备根目录下的文件夹。比如说如果我在硬盘 3 的根目录下创建了一个叫做 hello 的文件夹,那么这个文件夹也会在“共享”(SHARES)下展示出来,这就是“聚合视图”的效果 —— 将每一个存储设备的根目录下的所有最顶层文件夹都聚合在用户共享中。
    • 需要注意的是,聚合的只能是文件夹,如果你在存储设备根目录下创建的是一个文件,那么是不会展示在用户共享中的。
  • 聚合视图下的文件并不会跨盘存在。前面说了“聚合视图”只是逻辑上的概念,并不代表每一个存储设备中都存在有对应的文件夹或者文件。比如说你在“共享”(SHARES)中看到有一个叫做 movie 的文件夹,并不代表这个movie 文件夹同时存在于所有的存储设备中(跨盘存在),有可能只是这个文件夹只存放在硬盘 1 中,而其他的硬盘并没有这个文件夹。
💡
unRAID 之所以借助 FUSE 开发了一套属于自己的文件系统 SHFS ,是为了尽可能让用户更方便的去管理自己的文件,而不需要关心文件应该存放在哪里、上哪里去找文件的问题。
我们拿 Windows 系统来做个对比你就知道为什么 unRAID 这么设计了:
notion image
在 Windows 系统中去找一个文件时,我们第一个反应是到哪个分区(C 盘、D 盘、E盘…)去找,而不是去哪个文件夹去找,因为 Windows 并没有类似 unRAID “聚合视图”的逻辑。
用户必须明确的知道文件存放在哪个分区上才能找得到对应的文件,如果硬要给 Windows 套上“聚合视图”这个概念的话,那么这个概念下的视图单位就是分区。
这也造成了 Windows 不存在通过添加其他存储设备来扩展分区存储空间的可能,因为分区并不能跨盘而存在,比如说 C 盘只能是存在于 Disk 1,并不存在从 Disk 2 划分一部分空间来扩展 C 盘空间的可能。
而 unRAID 并不存在上述的情况,这也是为什么 unRAID 扩展存储空间是一件很简单的事情,这归功于 unRAID 的 SHFS 文件系统。

2.2、那 SHFS 跟 CPU 负载高(I/O wait)有什么关系?

SHFS 文件系统的优点是显而易见的,同时缺点也一样明显 —— 那就是 SHFS 会增加额外的 I/O wait 。
unRAID 上有一个叫做 shfs 的进程,这个进程的作用就是监视 SHFS 文件系统的写入和读取操作,决定进程的写入或读取对象应该是指向哪一个存储设备。
我们前面讲过,SHFS 文件系统提供了一个聚合的视图,用户只需要与用户共享文件夹来打交道,而不需要关心文件需要写入到硬盘 1 还是硬盘 2 亦或者是写入到缓存盘,那么负责实现这一效果的就是 shfs 这个进程。
举个例子,比如说当你往 /mnt/user/movie 拷贝文件时,这时候 shfs 这个进程就会去判断写入的文件应该是写入到阵列里的硬盘 1 或者是硬盘 2,又或者是写入到缓存盘,然后再告诉进程应该往哪里写入。
之所以要判断文件应该往哪里写入,是因为 shfs 需要按照共享文件夹设置的文件分配方法(如 High Warter)和拆分级别进行计算,最终决定文件去向。
notion image
你可以尝试在拷贝文件的过程中使用 top 命令去观察到 shfs 进程:
https://forums.unraid.net/bug-reports/stable-releases/683-692-huge-cpu-load-and-io-wait-when-copying-to-array-r1467/
在上图中,我通过 Windows 的 SMB 共享往 /mnt/user/doamin 目录下拷贝文件,此时 shfs 进程检测到有进程往 SHFS 文件系统(也就是 /mnt/user 这个目录下的文件系统)执行写入操作,shfs 就会像前面所说的去判断文件应该往哪里写入,因此 shfs 就会占用相应的 CPU 资源去执行他的任务,所以你也看到了 shfs 的资源占用来到了进程第三位,并且也造成了一定的 I/O wait( 3.7 wa )。
但如果说有大量的 I/O 操作往 /mnt/user 目录下执行,那么 shfs 进程的任务量就比较大了。而存在大量 I/O 操作的常见场景之一就是 P2P下载(也就是 BT / PT 下载),这也就是为什么很多用户在使用 qBttorrent 或者 Transmission 下载时出现了 CPU 负载高的情况(此现象在官坛中常称为 shfs overhead ):
  • 一方面,大量往 /mnt/user 目录下执行的 I/O 任务会让 shfs 进程消耗更多的 CPU 算力。因此如果你的 CPU 性能比较弱,那么就会造成 CPU 负载高的情况,但对于现在来说这种情况很罕见(毕竟现在大家动不动都用 12、13 代 CPU 组 NAS,性能溢出的反倒是普遍情况)。
  • 另一方面,因为 shfs 造成了较高的 I/O wait。CPU 资源得不到有效利用,所以有时候虽然看起来 CPU “很忙”,但实际上 CPU 的负载并不是真的很大。

3、如何避免?

各位读者看到这里,相信你已经明白了为什么下载时 CPU “负载高” 背后的逻辑,在你掌握了相应的一些关于 unRAID 文件系统的知识之后,本章的一些方法和技巧理解起来就会比较简单了。

3.1、方法一:增加内存

增加内存是提高 unRAID 读写速度最有效的方法,因为内存越大,可用于读写的缓存内存(Cached memory)越大,读写速度也越高。
对于现在内存白菜价的情况,这是最简单有效的方法,属于真正的大力出奇迹 —— 两根内存不够?那就四根!64G 不够?那就128G!
前面说过因为 shfs 造成了较高的 I/O wait,因此就算 CPU 性能再高也得不到有效的利用。但如果这时候有了缓存内存,进程可以先把数据存储到读写更快的内存中,等待任务执行完之后再将缓存内存中的数据慢慢写回硬盘,这样就很好的解决了 I/O wait 问题,因为 CPU 再也不用在那干等着了。
关于 Cache memory

3.2、方法二:使用 SSD 硬盘作为缓存盘

前面说过, I/O wait 除了系统原因之外,还包括物理存储设备自身的原因。我们知道机械硬盘的读写速度远远比不上 SSD 硬盘,所以我一直给身边的朋友建议去添加一个 SSD 硬盘来作为缓存盘配合 unRAID 使用,包括官方也是这么建议的。所以有条件的我都建议买一个缓存盘去使用,250G 或者更高的都可以,根据你自身的实际情况去选择就行(SATA 或者 M2 固态都行)。
SSD 硬盘即使不是用在 NAS 系统上,在普通的 PC 电脑上也基本上属于标配了,白菜的价格带来良好的使用体验。
固态硬盘的作用是从物理层面去减少 I/O wait ,当然也有可能加上了固态硬盘之后 CPU 还是会因 I/O wait 而出现高负载的现象(可能没原先那么高),所以请继续往下看解决办法,

3.3、方法三:绕过 /mnt/user 路径

前面说过,unRAID 的 SHFS 文件系统是挂载在 /mnt/user 目录下的,所以如果我们绕过了 /mnt/user 路径去执行 I/O 操作,那么就可以避免因 shfs 进程而出现的高 I/O wait 情况。
比如说,如果你原先希望往 /mnt/user/Downloads 这个文件夹下面去写入影音文件,那么你可以通过查看这个 Downloads 文件夹具体存在哪个硬盘或者缓存盘,然后直接拷贝到对应的硬盘上即可,比如说:
notion image
从图片中可以看到,Downloads 这个文件夹里面的文件存在于 disk1 和名为 cache 的缓存盘中,此时你就可以直接往 /mnt/disk1/Downloads 或者 /mnt/cache/Downloads 这两个路径下去写入文件从而绕过 SHFS 文件系统。
那么要怎么实现直接写入 /mnt/diskX 或者 /mnt/缓存池名称
你可以通过在 unRAID 的全局共享(Global share Settings)中开启磁盘共享功能来将阵列或者缓存池中的硬盘直接暴露出来:
💡
注意! 当你开启 disk shares 之后,请不要直接将数据从存储设备(如 /mnt/disk1/mnt/cache 等硬盘设备挂载目录)复制到用户共享( /mnt/user ),这可能导致数据丢失或者系统不稳定,因为这会与上面提到的“聚合视图”功能产生冲突。
停止阵列,然后按照下图的执行去开启:
notion image
notion image
应用并开启阵列之后,就会在 SHARES 下看到对应的 Dsik Shares 板块:
notion image
然后你就可以开启对应 disk 或者缓存的 SMB 共享(或者 NFS 共享)了:
notion image
notion image
通过此方法可以有效的避免 I/O wait 问题,并且在 SMB 传输速度上可以提高将近 50% 或者更高的传输速度。
在论坛的一个帖子中,一位网友测试了往同一个共享文件夹写入文件时,使用不同路径的传输速度对比:
此网友的共享文件夹使用了缓存策略(缓存用的 Samsung 970 EVO),并且内网下是万兆传输
  • /mnt/user/xxx 路径写入文件的传输速度平均只有 280 MB/s
    • notion image
 
  • /mnt/cache/xxx 路径写入文件的传输速度达到了万兆 1 GB/s
    • notion image
可以看到,即使是同一个共享文件夹,但是由于避开了 /mnt/user 路径而选择直接访问实际的存储路径 /mnt/cache 之后,传输速度得到了显著的提高。
这里还有一个国外 Youtube 博主的视频《Boost Your Unraid NAS Performance by 50%: Fix Disk IO Wait》同样解释了关于 unRAID 上的 FUSE、SHFS 等问题。

3.4、方法四:借助 6.12 版本下的 exclusive share 功能

在 2023 年 6 月份发布的 6.12 版本中,除了支持 ZFS 文件系统之外,我个人觉得比较大的改动之一就是 exclusive share (独占共享)。
简单来说,如果你的某一个共享文件夹的文件只存在于某一个缓存池中(user-defined pool),那么在开启了 exclusive share 的情况下,unRAID 会返回一个指向该缓存池共享文件夹的 synmlink(Symbolic link),也就是常说的“软连接”。
notion image
notion image
举个例子,假如你有一个共享文件夹叫做 media,你把这个文件夹放到你的缓存池 cache 里面 ( /mnt/cache ),并且这个 media 文件夹不存在与阵列里的任何硬盘上,那么此时 media 文件夹就满足了实现 exclusive share 的条件,你会在共享文件夹的属性中看到如下的提示:
“Yes” 即表示满足了实现 exclusive share 的条件
“Yes” 即表示满足了实现 exclusive share 的条件
也就是说,exclusive share 功能允许你在访问 /mnt/user 的情况下也能够避免因 SHFS 文件系统所造成的 I/O 问题。

(1)exclusive share 原理

/mnt/user 下的某一个共享文件夹,比如说 media,如果只存在于缓存池中,那么当你通过 /mnt/user/media 进行访问时,unRAID 会返回一个指向实际存储位置 —— /mnt/cache/media 的软连接。因此虽然你访问的是 /mnt/user/media ,但是借助 exclusive share 功能实际上访问的是 /mnt/cache/media ,那么也就避开了 SHFS 文件系统所造成的 I/O 问题 。
不满足 exclusive share 的文件夹访问的依然是 /mnt/user 目录
不满足 exclusive share 的文件夹访问的依然是 /mnt/user 目录

(2)实现条件

共享文件夹要实现 Exclusive access 需要保证以下四个条件:
notion image
notion image
  1. 主存储空间(Primary storage)不能选择阵列,只能选择缓存池;
  1. 辅助存储(Secondary storage)选择“无”(none)
  1. 不能开启 NFS 共享;
  1. 此文件夹及所有内容都只存在于缓存池(user-definded pool)中。
实现了 Exclusive access 的文件夹,在 /mnt/user 目录下是以一个软连接(symlink)的形式存在,比如说下图中的 appdata 文件夹是一个软连接,右侧的箭头指向的是真正的存储位置 /mnt/zpool/appdatazpool 是我的 zfs 缓存池)。
notion image

4、如何应用于 Docker ?

好了,基本上看到这里你已经知道整个过程的前因后果了,那么回到最初的问题 —— 我怎么让 QB 或者 Tr 等 Docker 下载工具在下载时避免出现 CPU “负载高”的情况呢?
这里分两种情况:
  • 如果你的 unRAID 版本 < 6.12 :在设置 Docker 路径映射时,避开 /mnt/user ,例如在给 QB docker 配置路径映射时:
    • notion image
  • 如果你的 unRAID 版本 ≥ 6.12
    • 在全局共享中开启 exclusive shares 配置;
    • 要映射给 Docker 容器的共享文件夹要满足 Exclusive access 条件;
    • 映射路径时正常选择 /mnt/user/xxxx 即可

5、常见问题

Q:为什么我的共享文件夹已经存放在缓存池了,文件夹属性中的 Exclusive access 依然提示的 No ?

请确保你的这个共享文件夹不存在于除了缓存池之外的存储设备中。比如说如果你的这个文件夹还存在于 Disk3 或者 Disk2,即使是空的文件夹,那么 Exclusive access 也会提示为 No 。
所以你要做的就是去检查每一个盘下面是否存在着相同名称的共享文件夹,如果存在的话需要删掉。
例如,上图中  aliyun  这个文件夹可以看到存储地址只在 cache 中
例如,上图中 aliyun 这个文件夹可以看到存储地址只在 cache 中
还要保证前面所说的条件:
  1. 主存储空间(Primary storage)不能选择阵列,只能选择缓存池;
  1. 辅助存储(Secondary storage)选择“无”(none);
  1. 不能开启 NFS 共享;
notion image
当你满足上述条件之后,一般情况下就可以开启共享文件夹的 Exclusive access 了。

评论