今年年初,我们那款面向边缘视频监控的嵌入式存储网关碰到一个非常现实的难题:4盘位设备,两块一组做RAID1镜像,可用容量直接打了五折。客户那边是48路高清码流要保留30天,按单路4Mbps码率粗算,需要约20TB可用空间,RAID1配两块10TB盘刚好卡在临界点,再多一路都危险。于是我们把目光转向了RAID5——同样的单盘冗余,容量利用率从50%提到75%,四盘位瞬间多出10TB空间。决定好做,真正把RAID5支持做进嵌入式存储栈、和上层文件系统对齐、躲开掉电不一致的坑、扛住坏盘重建,这一路踩的坑比预想多得多。这篇文章就从头拆一遍这段经历:容量焦虑怎么来的、RAID5底层的数据组织和写惩罚、中间层架构怎么落位、写路径怎么保证掉电安全、坏盘重建怎么做增量优化,最后聊聊RAID5和RAID10在嵌入式场景下的真实取舍。
1. 嵌入式存储的容量焦虑:为什么镜像不够用了
1.1 先算一笔容量账
先回到那个让人头疼的容量账。以一台4盘位设备、每块盘10TB为例,传统方案是两块一组做RAID1镜像:可用空间20TB,冗余度100%——任何一块盘坏了,镜像对还在,数据一件不少。这套方案在很长一段时间里是嵌入式设备的默认答案,原因很简单:实现成熟、重建简单、没有任何校验计算开销。但代价同样明显,50%的磁盘空间被锁死,磁盘容量越做越大,这种浪费就越让人肉疼。
RAID5的诱惑在于,同样是容忍一块盘损坏,它把冗余成本从"N块盘里砍一半"变成"N块盘里只扣掉一块"。同样四盘位,RAID5可用容量是30TB,比RAID1整整多了50%。我把几个常见方案的容量账拉了个表:
| 方案 | 4×10TB可用容量 | 容量利用率 | 最少盘数 | 冗余能力 |
|---|---|---|---|---|
| RAID1 | 20TB | 50% | 2 | 单盘 |
| RAID5 | 30TB | 75% | 3 | 单盘 |
| RAID6 | 20TB | 50% | 4 | 双盘 |
| RAID10 | 20TB | 50% | 4 | 每个镜像组单盘 |
回到我们那个48路存储的案例:16路4Mbps码流30天大约需要20.7TB,20TB的RAID1连冗余余量都留不出来,RAID5的30TB则能轻松覆盖,还能再扩展几十路。这类容量敏感型负载正是RAID5最擅长的战场,也是主流NVR和边缘存储网关这两年集体向RAID5迁移的根本原因。
1.2 RAID5能扛什么,不能扛什么
不过得先把边界说清楚。RAID5解决的是"磁盘单体故障下的数据可用性问题":一块盘物理损坏、固件卡死、接口松脱,阵列里其它盘依然能通过校验把这块盘上的数据算出来,业务不中断。它不解决所有问题——文件被误删、系统被勒索、固件BUG把元数据写坏、同一批次磁盘在短时间内连续故障,这些都不是RAID5的职责范围。把RAID5当备份用,是嵌入式行业最常见的误解之一。
还有一个容易被忽略的点:RAID5的冗余是"计算出来的",不是"复制出来的"。读数据时一切正常,一旦需要从校验重建某一块盘,就意味着所有幸存盘都要参与XOR运算。这个运算本身不复杂,但在嵌入式平台上,CPU频率、内存带宽、I/O调度都会成为瓶颈。所以评估RAID5合不合适,不能只看容量提升,更要看主控芯片有没有富余的算力和内存通道。
1.3 什么时候该认真考虑RAID5
概括下来,我判断一个嵌入式项目适不适合上RAID5,就看三个信号:第一,盘位至少三块,最好四块以上,否则容量收益不显著;第二,业务负载以顺序写、大块写为主,或者能够通过对齐设计把随机写转化为条带级的满写;第三,团队有能力处理掉电一致性、重建调度这些系统工程问题,而不是只接一个内核模块就跑。三条全中,RAID5就是好选择;缺任何一条,我建议你把后面RAID10那节看完再决定。
2. 条带、校验与写惩罚:RAID5的底层心法
2.1 分布式校验的原理
RAID5的数据组织方式可以用一句话概括:把N块盘的空间切成一组组等长的条带,每条带里专门留一块放校验,校验块的位置在条带之间依次轮换,所以叫"分布式校验"。
具体到一条带里,假设有4块数据盘和1块校验盘,数据块D1、D2、D3、D4和校验块P同属一条带,校验关系就是P = D1 XOR D2 XOR D3 XOR D4。XOR的逆运算就是它自己,这是RAID5最妙的地方:想恢复D2时,只要拿幸存块D1、D3、D4和P全部异或一遍就能算出来。整个恢复过程只需要一次按位异或,硬件实现极简,逻辑电路也好、NEON指令也好,都能干得非常快。
校验块在条带间轮换分布,是为了避免所有写压力都集中在一块盘上。如果固定让0号盘当校验盘,那任何数据块的任何一次更新,都要触发对0号盘的写操作,这块盘的寿命和吞吐会先于其它盘崩溃。轮换之后,每块盘的写负载被摊平,这也是RAID5区别于更早的RAID4最核心的一点。
2.2 写惩罚:RMW与RCW的取舍
校验关系带来了一个绕不开的代价:写惩罚。当你只更新一条带里的一个数据块时,不能直接一写了之,因为旧校验已经对不上新数据。这时有两条路。
第一条是读改写(Read-Modify-Write,RMW)。先把旧数据D_old和旧校验P_old读出来,计算新校验P_new = D_old XOR D_new XOR P_old,然后写D_new和P_new。一起两读两写,四次I/O。
第二条是重构写(Reconstruct-Write,RCW)。不读旧数据,改读这条带里其余所有幸存数据块,全部异或直接得到新校验,再写数据块和校验块。在4块数据盘的配置下是三读两写,五次I/O。
这两种策略没有绝对优劣,取决于一次更新涉及这条带里多少个块。经验阈值是:当需要更新的数据块数量超过条带内数据块总数的一半时,RCW更划算;否则RMW更划算。成熟的RAID5实现会在两种策略间动态切换,我们也保留了这套逻辑。
这个写惩罚直接决定了一个结论:RAID5对小块随机写极其不友好。每次4KB的元数据更新,落到底层可能变成四次甚至五次I/O,在机械盘上意味着多几倍寻道,在SSD上意味着多几倍写放大。所以如果业务负载是大量随机小写,RAID5不是好选择,这一点在讲RAID10对比时还会展开。
2.3 chunk size选型:嵌入式的特殊考量
条带内单个数据块的大小,也就是chunk size,是RAID5规划时必须先定死的参数。它决定了业务请求切分的粒度:一个4KB的小请求可能只落在一个块上,一个128KB的大请求会横跨多个块。我们的产品最终取64KB,主要权衡了三点:视频监控的码流在秒级粒度上本来就是连续大块,64KB能让一个条带装下足够多的有效数据;单条带缓存开销适中,不至于挤压主控内存;重建时以条带为单位推进,恢复速度不会太碎。
如果主控内存宽裕、业务又偏大块顺序写,128KB甚至256KB也可以,重建效率更高;反之如果业务小块居多,chunk调小一些能减少写惩罚的放大范围,但会增多校验块的碎片。这个参数在建阵列之后很难再改,千万别图省事用默认值,一定按真实业务做一轮基准测试再定。
3. 架构落位:文件系统与物理盘之间的那条中间层
3.1 三条路线:内核MD、改造MD还是自研层
在嵌入式Linux平台上,最省事的路是直接用内核的MD模块(md/raid5),它成熟、稳定、久经考验。但"能用"和"好用"是两回事。MD模块的内存占用、线程调度、重建优先级都是面向通用服务器设计的,直接搬进一个只有256MB内存的设备里,会遇到两个典型问题:条带缓存数量默认偏大导致内存吃紧;后台重建线程与前台业务I/O的调度策略不可控,业务高峰时可能被重建拖垮IOPS。
我们最终没有直接跑纯原版MD,而是对它做了三层改造。一是把条带缓存上限压到只保留少量活跃条带,用可控并发深度换内存稳定;二是把重建线程优先级调低,并且每重建一段就检查前台I/O队列深度,超过阈值就让路;三是把写意图位图从MD默认的位图文件改成固定在系统分区里的裸区,避免位图本身挂在文件系统上形成"鸡生蛋"的依赖。如果你们平台是用buildroot或Yocto裁剪的,改动路径基本一致,这三条是嵌入式部署MD的通用经验。
如果是在RTOS或裸机环境里做,那就得自己实现一个块设备抽象层了。思路也不复杂:上层文件系统看到的是一个完整的逻辑块设备,底层把这个逻辑块按chunk映射到多块物理盘上。映射表、条带调度、XOR校验、重建恢复,本质上是把一套小型MD搬进你的存储栈。工作量大,但自由度最高,很多做SSD控制器的团队就是这么干的。
3.2 中间层的模块划分与数据流
不论走哪条路线,RAID5层建议拆成五个职责清晰的模块:
- 设备映射表:维护逻辑块地址到(盘号、物理块地址)的映射,记录哪些块属于同一条带。
- 条带管理器:把上层逻辑I/O请求拆成针对单块盘的物理请求,管理活跃条带生命周期。
- 校验引擎:封装XOR计算,在Cortex-A系列上优先走NEON,在低端内核上退化为逐字节XOR。
- 写意图位图:记录哪些条带正在被更新且校验尚未落盘,是掉电恢复的关键。
- 恢复代理:负责降级读、坏盘后的重建调度,以及重建期间的资源控制。
数据流大致是这样:文件系统下发一个写请求,条带管理器先查映射表,确定它落在哪条带;根据请求大小和条带内已有脏块情况,决定走RMW还是RCW;更新写意图位图,置位对应条带;执行物理盘读写;计算新校验;写入数据块和校验块;校验落盘确认后,清除位图标记,再向上层返回完成。这套流程里,位图的更新时机和校验落盘的确认级别是后面要重点讲的两个细节。
3.3 与上层文件系统的对齐
RAID5层不是孤立存在的,它上面还挂着一个文件系统,我们这边用的是ext4。链路一旦拉长,对齐问题就来了。
第一是写对齐。文件系统的块大小、日志(journal)区域、数据段划分,尽量设计成条带大小的整数倍。我们的做法是把文件系统的stripe_width参数设成64KB×5=320KB,让ext4在做块分配时就知道RAID5的条带边界,把连续写尽量落到同一条带,减少RMW触发。纯粹靠RAID5层在底下硬扛随机碎片,收益远不如文件系统配合分配策略来得明显。
第二是刷盘语义。文件系统下发的fsync和带FUA的写,到RAID5层必须如实传递给物理盘,不能因为中间层有缓存就把刷盘请求吞掉。我们曾为了性能让校验块走Write Back缓存,结果把掉电安全的窗口拉大了,后来老老实实给校验块和位图更新都加上FUA,性能损失在可接受范围,一致性却从"碰运气"变成了"可证明"。
第三是TRIM/Discard透传。如果底下是SSD,文件系统发来的discard请求要能正确映射到各物理盘,否则SSD的垃圾回收会退化,长期插着用性能就劣化了。这个点很多自研层会漏,做测试的时候一定要覆盖。
4. 写路径与掉电一致性:没有BBU的嵌入式只能靠设计
4.1 写洞问题:RAID5的命中注定
RAID5的写路径上藏着一个经典问题,叫"写洞"(write hole)。一条带里既有数据块又有校验块,更新这条带至少要写两个块。如果系统在一号数据块写完、校验块还没写的时候突然掉电,重启后这条带的校验就和数据对不上了,而你无法判断到底哪个块是新的——数据块是新的、校验是旧的,结果就是静默数据损坏。
服务器阵营对付写洞的办法是BBU或NVMe的掉电保护电容,保证掉电瞬间缓存里的数据还能完整落盘。嵌入式设备可没这个条件,要么没有BBU,要么只有一小块只够保护文件系统元数据的专用电容,根本覆盖不了整条带。所以嵌入式做RAID5,必须从写路径的调度设计上把写洞风险压到最低。
4.2 我们的方案:顺序约束加写意图位图
我们选的方案分两层:顺序约束加写意图位图。
先说顺序约束。更新一条带时,永远保证先写数据块、后写校验块,并且校验块落盘确认后才向上层返回"写入成功"。这一条强约束的好处是,最坏情况就是掉电时这条带的数据已经更新而校验还是旧的,系统启动时能通过位图发现不一致,触发重算修复。如果反过来先写校验,掉电后数据是旧的、校验是新的,同样不一致,但数据损坏方式更隐蔽,有时连判断哪块盘需要重建都很困难。
再说位图。我们划分了一个裸的位图区,每一位对应一条带。写路径上,在发起条带更新之前就置位,校验落盘成功后再清除。启动时扫描位图,凡是置位的条带全部进入重算流程:读该带所有数据块,重新计算校验,写回,清除标记。这里有个关键细节:位图本身的更新也必须可靠,不能出现"实际已经开始写了、位图还没置位"的窗口。所以位图更新走带FUA的写命令,宁可多一次刷盘开销,也决不压缩这个安全窗口。
4.3 故障注入与断电测试
一致性这东西,嘴上说没用,得上手段。我们做了三类测试。第一类是注入式故障测试,在驱动层制造随机写中断,模拟盘在条带更新的任意时刻掉线;第二类是系统级断电测试,用可控电源循环器对整机做随机掉电,连续跑200轮,每次重启后都挂载文件系统做全量校验;第三类是长时间稳定运行测试,把设备挂在持续写入的视频流下跑72小时,同时跟踪位图脏带数量,确认不同负载下都能收敛到接近零。
三轮测试跑下来,抓到过两个真问题。一个是位图清除时机偏早,个别条带在校验未完全落盘时就被标记为干净,重启后出现校验损坏。根因是盘厂商的Write Back缓存把"写到盘接口"当成了"写到盘片",于是给校验块下发加上了FUA标志。另一个是重建期间如果恰好再坏一块盘,重建流程缺少锁保护,两台后台任务会同时写重建目标块的竞态,后来在恢复代理里加了按条带粒度的自旋锁才堵住。这些坑,都是内核文档和芯片手册里不会写的。
5. 坏盘重建:从全量扫描到增量恢复
5.1 降级模式:坏一块盘之后怎么活着
RAID5的容错是"少一块盘还能完整服务"。盘掉线后阵列进入降级模式:读请求命中坏盘对应条带时,把幸存盘的对应块全部读出来做XOR,重建那个丢失的块;写请求则直接把新数据和计算出的新校验写到幸存盘上,同时把更新过的条带记录在重建日志或位图里,等新盘替换后再补写。
降级模式的性能会明显下降,原因是每次读都变成多次物理读。以四盘阵列为例子,坏了一块盘后,一次逻辑读对应的物理读从1次涨到4次,读放大明显。还有一个更隐蔽的问题:如果掉线前有部分条带本来就处于校验不一致状态,比如刚发生过掉电事故,那这些条带在降级模式下是否还能正确重建,需要提前用位图区分。我们的做法是,降级开始时先扫一遍写意图位图,把脏条带优先重算干净,再开放对外业务。
5.2 全量重建为什么慢
等用户换上备用盘,就要触发重建。最粗暴的方式是全量重建:把幸存盘上所有条带逐条读出,XOR算出坏盘数据,写到新盘。这个流程逻辑最简单,但对大容量盘来说慢得离谱。10TB机械盘按实际200MB/s的重建速度算,要跑14小时以上;如果期间还有前台业务在抢带宽,这个时间轻松突破24小时。重建窗口越长,第二块盘在压力下出事的概率就越高,这也是RAID5在超大容量阵列里被诟病的核心原因。
全量重建还有一个容易被忽视的问题:它让新盘承受持续的大规模写入,在SMR叠瓦盘上,一旦写入缓存被冲垮,重建速度会断崖式下跌。所以嵌入式选盘时,如果要给RAID5用,尽量避开SMR盘,或者至少确认盘的写入策略能应对连续大块覆盖写。
5.3 增量重建:我们实际采用的工程优化
最终我们在产品里用的是增量重建:平时把降级期间被更新过的条带记录到一块专门的重建日志中;换盘后先进行全盘扫描,但只重建日志里标记的条带。实测下来,日常掉线后马上换盘的场景,绝大多数条带在降级期间根本没被写过,增量重建通常能在1小时内跑完,重建期间对前台视频写入的影响也小得多。
增量重建逻辑不复杂,但有一个坑:掉线前就有写洞脏带的场景。如果掉线前位图里还有脏条带,那么这些带的旧校验本来就不可信,降级期间如果又恰好读过这些带,读出来就可能是错误数据。我们的处理是:降级模式启动时强制把所有脏条带先修复干净,再做后续切换。宁可停机几秒钟,也不带着不干净的校验进入降级模式,这个原则到现在都没动摇过。
6. RAID5还是RAID10:别被参数表绑架,要看业务长什么样
6.1 核心差异对照
最近"RAID5和RAID10"的讨论热度不低,很多嵌入式工程师在这两个方案之间反复横跳。先给一张核心差异对照表,再展开讲场景。
| 对比项 | RAID5 | RAID10 |
|---|---|---|
| 最少盘数 | 3 | 4(2盘组阵列时等同镜像) |
| 可用容量 | (N-1)/N | N/2 |
| 冗余原理 | XOR校验重建 | 完全复制镜像 |
| 随机写惩罚 | RMW/RCW带来2~4倍I/O放大 | 约2倍I/O放大(双写) |
| 顺序大块写 | 全条带写时几乎没有惩罚 | 稳定 |
| 坏盘重建速度 | 全量或增量XOR重算 | 直接复制镜像,更快 |
| 重建期间风险 | 幸存盘全部参与计算 | 只读源盘一份,风险较低 |
| CPU开销 | 需要XOR算力 | 几乎为零 |
| 适合负载 | 顺序写、大文件、容量敏感 | 随机小写、事务型、性能敏感 |
从表里能看出,RAID5是用"计算"换"容量",RAID10是用"容量"换"简单和稳定"。没有谁是绝对好的,只有跟业务对不对得上。
6.2 三类典型场景怎么选
如果是安防视频监控、影像归档、日志采集这类设备,负载以顺序大块写为主,只要把写入记录段设计成和条带对齐,RAID5的写惩罚几乎不会出现,而容量收益立竿见影。我们的边缘存储网关最终就选了这条路:64KB chunk让视频写入按条带对齐,全条带写占比超过90%,RAID5的劣势被业务特性天然规避了。
如果是边缘AI推理服务器、数据库一体机,或者任何存在大量随机小写和事务型更新的场景,我建议老老实实上RAID10。虽然容量砍一半,但每次写只是双写镜像,没有读改写、重构写、位图这些复杂逻辑,重建也快,整个系统的心智负担低非常多。尤其嵌入式团队往往没有专职存储工程师,一个维护成本极低却性能稳定的RAID10,可能比微优化的RAID5方案更合适。
再补一类容易被忽略的场景:设备盘位不对称的。比如5盘位设备,如果走RAID5,可用容量是4/5;如果走RAID10,只能做两对镜像加一块空盘,可用容量是2/5,浪费严重。这种硬件形态天然就该选RAID5,强行上RAID10是拿用户的预算填坑。
6.3 一个折中的实战组合
最后分享一个我们目前在方案层面比较推荐的折中思路:RAID5加热备盘。比如五盘位设备做四盘RAID5加一块热备盘,坏盘后系统自动把热备盘拉进阵列开始重建,用户只需要在方便的时候更换坏盘即可。代价是可用容量从80%掉到60%,换来的是重建不依赖人工介入、窗口期大幅缩短。
这个组合在做产品时尤其合适:嵌入式设备往往部署在无人值守的机房或路边站点,指望现场有人第一时间换盘不现实。热备盘让系统在坏盘后立即自我修复,等维护人员到场时重建通常已经完成了大半。如果产品形态支持插拔盘位,这个"RAID5加热备"是目前我们最愿意推荐的生产组合。
我在实际部署中还发现一个容易被忽略的细节:无论选RAID5还是RAID10,都一定在出厂前做好盘位标识和序列号记录。阵列恢复时插错盘位、插回旧盘导致的数据错乱,我见过不止一次。把盘位信息固化到设备管理界面里,比任何技术优化都更能保护用户数据。