1. 为什么嵌入式文件系统会盯上 RAID5
先给不熟悉存储这块的朋友说下背景。传统印象里,嵌入式设备跟 RAID 这种"企业级"技术八竿子打不着。以前我们做嵌入式,存储介质基本就是 SD 卡、eMMC、Nor Flash 轮着用,文件系统选个 FAT、ext4 或者 JFFS2 这种带掉电保护的,数据可靠性主要靠单盘折腾。但是这几年情况明显变了,工业控制、边缘计算网关、车载记录仪、NAS 小主机,甚至一些科研采集设备,跑的数据量越来越大,单块存储介质根本扛不住容量和可靠性两方面的压力。
我这次做的事,是在一个嵌入式文件系统里加入 RAID5 支持。说直白点,就是让跑在 ARM 或者低功耗 x86 平台上的文件系统,自己去管理多块硬盘或者 eMMC,把数据像 RAID5 那样条带化分布,同时分布式地存一份校验信息。任何一块盘挂了,文件系统照常工作,数据不丢,换上新盘还能重建回来。这条能力在 PC 服务器上见怪不怪,但在嵌入式环境里落地,踩的坑完全不一样。
如果你手头也在做类似的事情——比如想把自研的嵌入式存储方案做得更皮实,或者在评估文件系统层面直接做冗余的可行性——这篇就聊聊我这次的完整设计思路、实现过程中遇到的坑,以及最后实测下来的性能表现。顺带也会聊一个嵌入式场景下避不开的话题:RAID5 和 RAID10 到底该怎么选。
1.1 什么样的嵌入式场景真的需要 RAID5
很多人一听到"嵌入式 + RAID5"第一反应是:有必要吗?我一开始也这么想,后来被实际需求打脸了。
拿我们这次的目标场景举例:一台边缘计算网关,里面要塞 4 块 2.5 寸 SATA SSD,跑的是工业现场的实时数据库和历史趋势归档。现场环境是 7x24 小时不间断运行,设备放在户外机柜里,夏天温度能到 50 度,震动、断电、电压波动全都是家常便饭。这种环境下,单块 SSD 的故障率并没有想象中那么低,而一旦系统盘挂了恢复不了,现场工程师得跑一趟现场重新部署,成本可能比设备本身还贵。
另一个典型场景是车载和轨交记录仪。法规要求黑匣子数据必须完整保留,但存储介质本身可能随时遭遇物理冲击。RAID5 能扛单盘故障,两块盘同时挂的概率在概率学上已经足够低,对于这种场景是性价比最高的选择。
还有一类是小型 NAS 和私有云盒子。不少用户把家庭照片、工作文档都存在里面的多块盘上,希望容量利用率高一点,又能接受一块盘故障不丢数据。RAID5 对这类用户是远比 RAID1 更实用的方案。
所以核心判断标准就三条:多块独立存储介质、数据需要持续写入不能中断、单盘故障会造成不可接受的数据丢失或业务中断。满足这三条的嵌入式产品,都是 RAID5 的合理应用对象。
1.2 传统 RAID 实现为什么不适合直接搬过来
不少做嵌入式的同事第一反应是:RAID 不是现成的吗?Linux 内核有 md 模块,硬件也有 RAID 卡,直接叠上去不就行了?
说实话,在 x86 工控机上这么干确实没毛病,但放到嵌入式环境里,问题一个接一个:
- 硬件 RAID 卡功耗高、体积大、贵。嵌入式主板的 PCB 面积和功耗预算卡得死死的,加一块 RAID 卡可能整机功耗直接超标。
- 软件 RAID(Linux md)是内核态的独立层,位于块设备和文件系统之间。它对嵌入式场景的适配并不友好:要么依赖完整 Linux 内核,要么你得自己裁剪移植,而且 md 层的缓冲区管理、线程调度策略都不是为小内存、低 CPU 主频的嵌入式环境设计的。
- 最重要的是,很多嵌入式文件系统并不是 ext4/xfs,而是厂商自研的、带日志或掉电保护的专有格式。它们往往直接管理裸设备,无法在中间塞一层标准 RAID 驱动。
所以这次项目的技术决策很明确:不做独立 RAID 层,而是把 RAID5 的逻辑直接融入文件系统自身,让文件系统成为"懂冗余"的存储管理者。这样既可以统一管理 IO 调度和掉电恢复,又能精准控制内存占用和行为特性,是嵌入式环境下更务实的一条路。
2. 整体设计:文件系统里的 RAID5 到底怎么长出来
确定了大方向——在嵌入式文件系统内部实现 RAID5 语义,接下来的问题就是:怎么设计这个结构才不把文件系统本身搞乱。
2.1 文件系统与 RAID 之间的"责任边界"
先理清一层关系。传统架构里,文件系统的职责是把数据组织成文件和目录,RAID 的职责是把底层多块盘拼成一个可靠的块设备。两者是上下层关系,互相不关心对方内部逻辑。
把 RAID5 融进文件系统之后,边界其实变了。文件系统不只是文件管理器,它还要充当"条带化调度器"和"校验计算器"。但有一点必须保持清楚:文件系统的语义不能乱。应用层读写文件的行为,跟它是不是 RAID5 无关,打开文件、写入数据、读取数据这些接口必须和以前完全一致。
我的设计是引入一层"虚拟卷抽象"。文件系统内部多了一个虚拟卷的概念,每个虚拟卷背后绑定一组物理盘(一般 3 到 8 块),虚拟卷内有独立的块分配器,负责把文件的逻辑块映射到物理盘的物理块上,这个映射关系就携带 RAID5 的条带和校验策略。文件系统原来的目录树、inode 管理、日志系统,完全构建在虚拟卷之上,感知不到底层盘的变化。
2.2 条带化布局与分块大小怎么定
RAID5 最核心的是条带化布局。简单说就是把连续的数据切成一格一格的块,轮流写到不同盘上。设磁盘数量为 N,每次写 N-1 个数据块,剩下 1 个写校验块。校验块的位置在每条条带内部做轮转,避免某一块盘总是承担校验写入,导致寿命不均。
分块(chunk/stripe unit)大小是我最先要定的参数。嵌入式设备常见值有 64KB、128KB、256KB。选小了对小文件友好,但校验计算频率高、IO 次数多;选大了顺序大文件吞吐好,但小文件随机写放大明显,而且单块盘故障后重建时需要读的跨度大。
我这次均衡下来选了 128KB,兼顾了顺序读写和小文件性能。如果读者做的产品是高清视频写入为主,可以调大到 256KB;如果是 IoT 设备大量小日志,64KB 更合适。这个参数在虚拟卷创建时固定,运行期改不了,所以前期测试要充分。
2.3 校验块的分布算法
校验块的轮转公式不复杂。设条带内块序号从 0 到 N-1,校验块在条带号 p 中的位置是 (N-1 - (p % N))。数据块的位置就是除了这个位置以外的其它 N-1 个槽位。这样每个条带的校验块分散到不同盘上,每块盘承担的校验写压力宏观上一样。
用 N=4 的磁盘组举例,条带 0 的校验在第 3 块盘,条带 1 的校验在第 2 块盘,条带 2 的校验在第 1 块盘,条带 3 的校验在第 0 块盘,条带 4 又回到第 3 块。整个组里,数据量跟校验量的比例始终是 3:1,可用容量是总容量的 75%。
2.4 关键模块:块分配器、日志、IO 调度器
整个实现分为三个核心模块,"块分配器"负责选盘和选偏移。每次要写一个逻辑块,分配器会查当前条带游标,决定去哪块盘、写哪个偏移,同时记住该条带内的校验盘位置。这个逻辑要尽量快,不能每条 IO 都做复杂查表,所以我把条带映射表放在内存里,用位图加速。
"日志子系统"要做扩展,因为 RAID5 的"写前日志"比普通文件系统复杂。如果中途掉电,会存在部分条带已经写了新数据但校验没更新的情况。这时候必须能检测出来并回滚或重放。我的方案是给每个条带加一个世代号(generation),写数据前先把条带世代号记入日志,完成写入后再提交。恢复时发现某个条带的世代号不连续,就认为该条带处于不一致状态,需要重建恢复。
"IO 调度器"负责把逻辑 IO 合并成物理 IO。RAID5 最大的麻烦是"读-改-写"序列。应用只写了 4KB,但 RAID5 需要读旧数据、读旧校验、计算新校验、写新数据、写新校验,一个逻辑写变成五次物理访问。调度器会把同一磁道的写请求合并,等条带凑齐了再一起写,尽量把"读-改-写"变成"整条带写"。
3. 实操落地:从奇偶校验算法到掉电安全
3.1 奇偶校验计算:别小看 XOR
RAID5 校验算法本质上就是异或(XOR)运算。新校验 = 旧数据 XOR 新数据 XOR 旧校验。这个等式在任何一个数据块更新时都成立。
嵌入式 CPU 算 XOR 很便宜,ARM Cortex-A 系列一条指令就能处理 64 位数据,但数据量大时不能纯靠 CPU 硬算。我做了两层优化:
第一层,如果写入的数据刚好覆盖整个条带(所有数据块都是新的),就不需要读旧数据旧校验,直接对 N-1 个数据块做 XOR 得到新校验,一次写满整个条带。这是 RAID5 的最高效路径。
第二层,如果只写了部分块,适合走"读-改-写",只需要读被修改块和对应校验块,算出新校验。这个流程涉及两次读、两次写,加上计算,开销确实不小。
实测下来,Cortex-A53 1.5GHz 主频上,1MB 数据的 XOR 计算大概耗时 3 到 5 毫秒,相对磁盘 IO 时间(十几毫秒)不算瓶颈。但要注意,主控芯片如果还要跑业务,CPU 占用会明显涨。所以我做了个简单的"校验计算线程池",利用多核 CPU 分散计算负载,避免 IO 路径被校验计算堵住。
3.2 写路径的条带组装逻辑
写路径是实现中最容易出 bug 的地方。核心逻辑是"条带缓存 + 延迟提交":
- 应用写入数据,文件系统按逻辑块地址定位到某个条带。
- 数据先写入条带缓存(strip cache),缓存里记录哪些块是"脏"的。
- 当脏块数量达到条带满员条件,或者达到刷新超时阈值,就触发一次条带提交。
- 提交前检查脏块数:
- 满 N-1 块:直接全部 XOR 算校验,一次写完。
- 不满 N-1 块:读旧数据、读旧校验,走读改写流程。
- 校验计算完成后,把数据块和校验块按目标盘分别下发。
有个细节必须提醒,条带缓存不能无限大。内存紧张的嵌入式设备,条带缓存一般设 4 到 8 个条带就够了,超过阈值强制刷出。缓存淘汰策略我用的是"最久未提交优先",避免某个条带长期占用缓存。
3.3 掉电一致性:重建流程与坏盘标记
嵌入式环境最怕掉电。RAID5 如果掉电时条带处于半写状态,重启后数据可能是旧的,也可能是新的,校验跟数据对不上。
我的方案是前面提过的"世代号 + 日志"组合。具体流程:
- 每个条带元数据里存一个 32 位世代号。
- 写条带前,先把目标世代号写入日志区(日志区放在所有盘之外,可以是一块小容量的 SPI NOR Flash 或磁盘预留区)。
- 写完数据块和校验块后,再把条带元数据里的世代号更新。
- 重启扫描日志,如果发现某个条带的日志世代号比元数据里的新,说明写操作没完成,就把这个条带标记为"待重建"。
待重建条带的数据恢复有两种做法。如果损坏区域恰好是数据块,可以直接用其它数据块和校验块 XOR 反解;如果损坏的是校验块,直接重新计算追平即可。整个恢复过程也就是 RAID5 的重建流程。
3.4 成员盘状态管理
文件系统跑起来之后,需要实时监控每块盘的健康状态。嵌入式环境下没有企业级硬盘的 SMART 通道那么完善,我就用"IO 错误计数 + 响应超时"双指标来判定盘是否失效。
IO 错误计数连续超过阈值(比如 10 次),或者某块盘响应超时达到 30 秒,就把这张盘标记为"失效"。失效盘上的数据立即从条带中剥离,系统进入降级模式,所有读请求通过其余盘 XOR 计算恢复,所有写请求跳过这块盘继续写,同时把校验关系临时调整。
这里有个坑:降级模式下,如果又有一块盘失效,RAID5 就真的救不回来了。所以失效盘一定要立刻报警,建议在系统里留一个 GPIO 或网络告警出口。
4. RAID5 与 RAID10 的嵌入式选型对比
既然是做嵌入式文件系统的 RAID5,就绕不开"RAID5 还是 RAID10"这个经典选择题。我直接把我这次的实测对比数据放出来,结合嵌入式场景的约束来聊。
4.1 容量利用率与成本对比
先看最直观的容量账。设总共有 N 块盘,单盘容量 C:
- RAID5 可用容量 = (N-1) * C。4 块 1TB 盘的可用容量是 3TB,利用率 75%。
- RAID10 可用容量 = (N/2) * C。4 块 1TB 盘的可用容量是 2TB,利用率 50%。
嵌入式产品做硬件选型时,每块盘都是实打实的成本。同样 4 盘位,RAID5 能多出 1TB 可用空间,这在产品定价和竞争力上是很明显的优势。如果产品经理告诉你"预算只能上 4 块盘,可用容量不能低于 3TB",那基本没得选,只能 RAID5。
4.2 写性能与读性能差异
对比条件:4 块 SATA SSD 组 RAID5,条带块 128KB,RAID5 带延迟提交;RAID10 组用同样的盘,走镜像 + 条带。测试工具用 fio,队列深度 32,块大小分布覆盖 4KB 到 1MB。
顺序读性能:两者几乎一致,都能到单盘读速率的 N 倍(受总线瓶颈限制),RAID5 略高一点因为数据块分布更均匀。
顺序写性能:RAID5 因为校验计算和条带提交,会损失一部分带宽。在 1MB 大块顺序写场景,RAID5 大概是 RAID10 的 85% 到 90%,差距能接受。
随机写 4KB 场景,差距就拉开了。RAID10 的写惩罚是 2(写镜像两份),RAID5 的写惩罚是 4(读旧数据、读旧校验、写新数据、写新校验)。实测下来,RAID5 的 4KB 随机写 IOPS 只有 RAID10 的 50% 左右。如果业务里有大量小随机写,RAID5 会很难看。
嵌入式设备很多都是日志类、采集类顺序写为主,这个差距可以接受。但如果你的应用是数据库这类随机写密集场景,RAID10 显然更稳。
4.3 故障恢复与重建风险
这是我最想强调的一个点。RAID5 单盘故障后进入降级模式,读性能会明显下降,因为每次读都要做 XOR 反解。而且重建过程需要读所有剩余盘的全部数据,重建时间跟盘容量和 IO 速度直接相关。
举个例子,4 块 1TB 的盘,重建大概需要 6 到 10 个小时(取决于接口速度和 CPU 计算能力)。这期间如果再来一块盘故障,数据就永久丢失了。RAID10 的重建就快得多,只需要从镜像盘完整拷贝数据,不涉及 XOR 计算。
所以嵌入式长期无人值守的场景,如果只有 3 块盘,我用 RAID5 会有顾虑;如果有 4 块以上盘,RAID5 的经济性优势就压过了重建风险,可以接受。
4.4 嵌入式专项:内存、CPU、功耗
RAID5 需要条带缓存,需要校验计算线程,掉电恢复时需要重建,这些都会增加内存和 CPU 开销。我实测在 512MB 内存、双核 A53 的平台上,RAID5 模块运行稳定态占用内存约 80MB(其中条带缓存 64MB,元数据 16MB),校验计算会让 CPU 占用增加 10%-15%。
RAID10 的内存开销就小很多,只需要做镜像写,几乎不占额外 CPU。如果嵌入式设备本身内存很紧,比如 256MB 的旧平台,RAID10 可能更现实。
最终我这次的结论很明确:嵌入式场景,盘位 4 个以上、写模式以顺序为主、容量敏感的产品,选 RAID5;盘位 2 个、随机写敏感、内存紧张的产品,选 RAID10。两者不是替代关系,是不同场景下的不同正确答案。
5. 性能调优与实测数据
5.1 关键参数调节:条带缓存大小与刷新阈值
条带缓存大小直接决定了写聚合效果。缓存太小,条带凑不齐就强制刷,校验计算效率低;缓存太大,内存吃紧,还会让掉电丢数据的风险窗口变大。
我调试了三个典型值:64MB、128MB、256MB,测试写性能如下表:
| 缓存大小 | 顺序写 1MB(MB/s) | 随机写 4KB(IOPS) | 峰值内存占用 |
|---|---|---|---|
| 64MB | 408 | 3120 | 96MB |
| 128MB | 452 | 3280 | 160MB |
| 256MB | 461 | 3310 | 288MB |
128MB 到 256MB 的性能提升已经很小,但内存翻倍。我最后用 128MB,对 512MB 内存平台来说是合理平衡。
刷新阈值我设为两种模式。默认"条带满员立即刷新",追求最低延迟;再提供"定时器模式",比如每 100ms 刷新一次,适合需要稳定写入节奏的流媒体场景。定时器模式会让平均延迟略高,但抖动小。
5.2 fio 基准测试与结果解读
用 fio 走一组标准流程,建议读者复现时也按这个套路来,方便横向对比:
- 顺序读 1MB,队列深度 32
- 顺序写 1MB,队列深度 32
- 随机读 4KB,队列深度 32
- 随机写 4KB,队列深度 32
实测 4 盘 RAID5 结果:
| 测试项 | 数值 |
|---|---|
| 顺序读 | 823 MB/s |
| 顺序写 | 452 MB/s |
| 随机读 4KB | 18,400 IOPS |
| 随机写 4KB | 3,280 IOPS |
顺序读能到 823 MB/s,是因为 4 块盘同时读,接口速率叠加后接近 SATA 控制器极限。随机读 IOPS 比单盘高不少,因为读请求分散在不同盘上并行处理。随机写 IOPS 是最弱的一环,前面解释过原因,这就是 RAID5 的物理天花板,只能靠缓存优化尽量补。
5.3 降级模式下的性能变化
模拟一块盘拔出,系统进入降级模式后,我再跑同一组 fio:
| 测试项 | 正常模式 | 降级模式 | 变化 |
|---|---|---|---|
| 顺序读 | 823 MB/s | 465 MB/s | -43% |
| 顺序写 | 452 MB/s | 398 MB/s | -12% |
| 随机读 4KB | 18,400 IOPS | 9,700 IOPS | -47% |
| 随机写 4KB | 3,280 IOPS | 3,040 IOPS | -7% |
降级模式下读性能几乎腰斩,是因为每次读幸存盘数据时,都需要额外读其它盘数据并用 XOR 反解。写性能下降不多,因为写仍是把数据写到幸存盘,只需调整校验策略。这个数据能直观告诉你在单盘失效后系统的真实表现,做产品设计时务必要提前想好降级模式下的业务降载策略。
6. 常见问题与排查技巧实录
6.1 条带不一致告警,但盘没坏
这是最容易误判的情况。系统启动扫描时发现某个条带的世代号不一致,但所有盘都能正常读写。多半原因是上次掉电时刚好有日志没提交完。处理方式很简单,把该条带标记为待重建,触发一次恢复,一般读取其它盘重新算出数据就能对齐。切忌直接把盘标记失效,导致不必要的全盘重建。
排查技巧:日志里如果只看到零星几个条带号报错,大概率是掉电残留;如果大量条带报错且集中在某一块盘,那就要怀疑那块盘有坏道了。
6.2 随机写性能远低于预期
如果随机写 IOPS 连理论值一半都不到,通常是条带缓存太小或者刷新阈值太激进。我调试时遇到过把刷新阈值设成 10ms,结果每个小写入都触发一次"读-改-写",IOPS 直线下降。调成"条带满员刷新"后,性能立刻回到正常区间。
还有一个隐蔽坑:如果文件系统本身开启了"同步写"(每次 write 都要等落盘),RAID5 的条带聚合会被完全打碎。嵌入式应用如果有同步写需求,建议走日志落盘机制,而不是让每一个小 IO 都穿透到 RAID5 层。
6.3 拔掉一块盘后系统无法启动
这是典型的引导工程问题。文件系统里有 RAID5 虚拟卷,但引导加载程序(bootloader)不认这个卷,它企图从第一块盘直接引导内核,结果盘失效了就起不来。
解决办法是单独划分一个引导分区,用标准 ext4 或 fat 格式存储内核和 bootloader,RAID5 只承载业务数据。引导分区也可以做成两盘互备的小镜像,但没必要加入 RAID5。这个经验我们是一次生产事故换来的,提出来希望读者少踩一次坑。
6.4 重建过程中 IO 性能波动剧烈
RAID5 重建需要大量读幸存盘、计算校验、写新盘,跟正常业务 IO 抢带宽和 CPU。如果重建优先级太高,业务会卡死;太低,重建拖太久增加二次故障风险。
我建议把重建 IO 的优先级固定成"业务不到 60% 负载时才允许跑"。实现上就在 IO 调度器里加一个动态带宽控制。实测效果是重建吞吐降到满载的 70%,但业务侧基本无感。
7. 可扩展方向
这次项目做完,后续演进我留了几个方向。
一是 RAID6。在 RAID5 基础上多一个校验块可以同时容忍两块盘故障。代价是写惩罚进一步增加到 6,校验计算复杂度也翻倍。对于数据价值特别高的嵌入式场景,值得做。
二是把冗余策略做成可配置。一个存储系统同时支持 RAID0、RAID5、RAID10,让用户在管理界面里按需选择。目前架构上虚拟卷已经是独立的,实现多个策略并存不会伤筋动骨。
三是引入快照和远程复制。既然文件系统里已经有条带级元数据,做快照可以基于条带级别做 COW(写时复制),对嵌入式备份场景很有价值。
最后再分享一个我在实际测试中养成的习惯:每改一次条带逻辑或校验算法,一定要做一次完整的掉电注入测试。我这边用一个继电器控制板,随机在写入过程中切断整机电源,跑 200 轮,确认每次都能恢复且数据一致。嵌入式存储的可靠性,不是靠代码写出来的,是靠这种反复折腾验证出来的。