里程碑④:在 forgefs 上编译内核模块
终极验收:挂载 forgefs,跑一次真实的编译任务,中途拔掉一个节点 —— 编译必须成功,产物 checksum 必须正确。
学完这节你能做到
- 通过全部端到端验收项
- 记录并解释性能数据:吞吐、元数据 ops、重建速度
- 写出 forgefs 与 Weka 的差距清单(诚实版)
验收现场:弄不坏才算数
L0 立过的规矩今天全额兑现:能跑不算数,弄不坏才算数。这节课没有新概念, 只有三个验收项和一份诚实的差距清单。验收标准提前写死 —— 这是你运维时做重大变更的习惯(方案里必须有验证步骤和回退条件), 现在用同样的纪律验收自己造的文件系统。
环境:3 个 forge-node(每节点若干数据盘目录)+ 1 个 forge-fuse 挂载点。 EC 4+2 意味着 6 个分片要落在不同故障域 —— 3 节点场景按「每节点 2 个故障域」配置放置约束, 挂掉一个节点最多丢每条带 2 个分片,恰好压着 4+2 的容错上限。这不是巧合, 是我们故意把验收压在最坏许可条件上。
验收项 1:pjdfstest 子集
pjdfstest 是 POSIX 语义的考官,一共 8000 多个用例。forgefs 不做 link/chflags/ 特殊设备文件,所以跑声明过的子集,但子集内零容忍:
# 在挂载点上跑,只跑 forgefs 声明支持的语义组
cd /mnt/forgefs && mkdir pjd && cd pjd
prove -rv /opt/pjdfstest/tests/{open,mkdir,rmdir,unlink,rename,chmod,chown,truncate}
通过标准:上述 8 组全部 PASS,0 个 FAIL。跑不过的常见坑,按出现频率排:
- errno 翻译错(ENOTEMPTY 写成 EEXIST,ENOTDIR 和 ENOENT 混用)—— fuse-client 课警告过
- rename 的边界:rename 到自身要成功;rename 目录到自己的子目录要回
EINVAL - truncate 缩小文件后再读,尾部必须是 0 而不是旧数据 —— 这个 bug 藏在 extent map 的截断逻辑里,危害等级是「静默脏数据」,pjdfstest 抓不到的话编译验收也会抓到
NFS、CephFS、各家对象网关的 POSIX 层全都只支持子集,关键是写进文档并在 不支持的调用上返回明确的 ENOSYS,而不是装作成功。运维时你排查过 「应用在某 NAS 上行为诡异」的工单,根源常是文件系统对不支持的语义返回了假成功。
验收项 2:编译 + 拔节点,产物必须分毫不差
主菜。完整演练脚本,照做:
# 1. 基线:在本地 ext4 编译同版本 zlib,记录产物 checksum
cd /tmp && git clone https://github.com/madler/zlib && cd zlib
./configure && make -j8
sha256sum libz.a > /tmp/golden.sha256 # 黄金标准
# 2. 在 forgefs 上重来,编译进行到一半时杀节点
cd /mnt/forgefs && git clone /tmp/zlib zlib && cd zlib
./configure
make -j8 & # 后台编译
sleep 5 && ssh node-2 'systemctl kill --signal=SIGKILL forge-node' # 编译中途硬杀
wait $! # 编译必须正常结束,退出码 0
# 3. 验收三连
sha256sum -c /tmp/golden.sha256 --ignore-missing <<< "$(cat /tmp/golden.sha256 | sed 's|libz.a|/mnt/forgefs/zlib/libz.a|')"
make test # 功能测试全绿
forge-cli rebuild status # 危险条带应为 0,重建在进行
判分标准,三条全过才算过:
- make 退出码 0:节点死亡期间,读走降级重构(缺 2 分片可拼回), 写照常提交到幸存节点 —— 应用无感知,这是 L3+L4 全部工作的总和
- libz.a 的 sha256 与 ext4 基线一致:编译产物是确定性的,checksum 不同 意味着某个环节吐了脏数据 —— 端到端 crc32c 应该在更早的位置抓住它, 两道防线都漏了才会走到这里
make test全绿:防止「checksum 恰好对了但中间文件坏了」的侥幸
然后把节点拉回来,验证第二个半场:systemctl start forge-node,
观察 rebuild status 里积压清零、孤儿回收,全程挂载点不许有任何 EIO。
验收为什么坚持对比编译产物的 sha256,而不是只看编译成功?
验收项 3:重建期间 P99 受控
第三项验证 rebuild 课的限速引擎。用 forge-bench(L2 的老朋友)当业务负载:
# 1. 铺底数据 + 稳态基线
forge-bench --target /mnt/forgefs --rw randrw --size 8GiB --qd 16 --runtime 120 \
--hist /tmp/baseline.hgrm # 记录稳态 P99,设为 X
# 2. 负载不停,杀一个节点触发重建,再压 10 分钟
ssh node-3 'systemctl kill --signal=SIGKILL forge-node'
forge-bench --target /mnt/forgefs --rw randrw --size 8GiB --qd 16 --runtime 600 \
--hist /tmp/degraded.hgrm
通过标准写成数字:重建期间业务 P99 不超过稳态基线的 3 倍,且无单次超 1 秒的请求;
同时 forge-cli rebuild status 的速率轨迹要能看到退让生效(业务压力大时速率下调)。
3 倍不是拍脑袋:降级读本身贵 4 倍读放大,叠加重建争抢,2~3 倍是 FUSE + tokio
架构下的现实目标;如果测出 10 倍,先查限速器有没有真的在 IO 路径上生效
(常见翻车:重建的读限了速,重构后的写忘了 acquire)。
验收脚本本身也要 review —— 它是里程碑的裁判,裁判不能有 bug。 重点盯两处。第一,失败会不会被吞掉:第二项里 sha256 校验失败, 如果它藏在一段管道中间,严格模式没开全就会被静默放过 —— 用上面那条「手工改坏黄金标准」的动作亲手验一次,比读脚本可靠。 第二,解析方式脆不脆:去 grep 给人看的文本,输出格式一变裁判就瞎了, 应该让压测工具直接吐结构化结果。裁判可靠了,后面 L5 的性能回归 CI 直接复用这套脚本。
目标:把本课三个验收项做成一套能一条命令跑完、以后每次改动都能重跑的演练套件 scripts/milestone-fs/。它是里程碑的裁判 —— 裁判不能有 bug,也不能有假绿灯。 行为规格: 1. 总入口按顺序跑三项,任何一项失败立即中止,并明确打印是哪一项、哪条断言失败;全部通过则输出一份 markdown 验收报告存档; 2. 验收项一,POSIX 语义:挂载 forgefs,跑 pjdfstest 里 open、mkdir、rmdir、unlink、rename、chmod、chown、truncate 这八组,解析结果,FAIL 数非 0 即失败。报告里要写清实际跑了几组、多少用例,不许只写一句「通过」; 3. 验收项二,编译中途杀节点:先在本机内核文件系统上编译 zlib 记下产物的 sha256 作为黄金标准;再到挂载点上编译,make 启动 5 秒后硬杀掉配置里指定的那个存储节点,等编译结束,三连校验 —— make 退出码为 0、产物 sha256 与黄金标准一致、make test 全绿。之后把节点拉回来,轮询重建状态直到积压清零,并确认整个过程内核日志和挂载进程日志里都找不到 EIO; 4. 验收项三,重建期延迟:先跑 120 秒稳态取 P99 基线,杀节点后再跑 600 秒,断言这期间业务 P99 不超基线 3 倍、单次最大延迟不超 1 秒,两条延迟直方图数据留档; 5. 节点地址、挂载点、各项阈值全部从一个配置文件读,不许硬编码。杀节点那条命令必须杀配置里指定的那台,不许写死主机名; 6. 断言的方向必须写对:「错误数为 0」不能写成「成功数大于 0」——后者在 99% 请求都失败时照样通过,是这类脚本最常见的假绿灯;测量结果的解析要走结构化输出,不要去 grep 给人看的文本,格式一变裁判就瞎了; 7. 报告内容:每项通过状态、关键数字(用例数、两边编译耗时对比、P99 基线与重建期数值、重建总时长),外加失败时的诊断线索章节; 8. 脚本用严格模式,管道中任何一段出错也要中止,不许被中间环节吞掉失败。 交付:脚本本身 + 一份逐条说明「每个断言对应正文里哪条验收标准、什么情况下它会失败」的清单。先给我一个只打印将执行命令的空跑模式,我检查完再真跑。
- 先跑空跑模式,把将要执行的命令逐条读一遍 —— 这是 shell 不是 Rust,你比 AI 更熟:有没有对着错的挂载点、有没有把不该动的目录当测试目录、杀节点那条命令杀的到底是不是配置里那台
- 完整真跑一遍,三项全绿,把报告和两份延迟直方图留档
- 假绿灯验证,必做:把黄金标准的 sha256 手工改掉一个字符再跑,确认脚本变红且失败摘要明确指出是产物校验不过;再把第三项的 P99 阈值改成一个必然超标的值,确认它也会红。一个从来不会失败的裁判等于没有裁判
- 让 AI 逐条念出「这条断言在什么情况下会失败」,念不出失败条件的那几条基本就是假的;念到测量解析那几条时,顺手把它依赖的输出格式改一个字,确认脚本报的是解析失败而不是默默判通过
- 健壮性:在第二项跑到一半时 Ctrl-C 掉,确认它没留下挂起的挂载点、没留下后台编译进程、被杀的节点状态是明确的;然后原地重跑一次,要能跑通 —— 演练脚本必须可重复执行,否则第一次失败之后你就得手工收拾现场
- 第二项里 make 退出码是 0、但产物 sha256 对不上,说明系统哪里出了问题?为什么这比 make 直接失败更可怕?
- 第三项测出 10 倍而不是 3 倍,第一顺位怀疑对象是什么?怎么用一条命令区分「限速器没在写路径上生效」和「降级读本来就贵」这两种原因?
- 「积压清零」这个判据可靠吗?有没有可能积压显示为 0、但数据其实还没补全?如果有,我该再看哪个数字才踏实?
差距清单(诚实版):forgefs 离 Weka 有多远
验收通过不等于追平 Weka。这份清单是里程碑的必交作业 —— 每一项写清 「差什么、差多少、为什么不做」,它同时是你面试或复盘时最有说服力的材料:
| 差距项 | Weka | forgefs | 差多少(量级) |
|---|---|---|---|
| 客户端 | 内核模块 + 共享内存 | FUSE 两次往返两次拷贝 | 小 IO 的 IOPS 差 5~10 倍 |
| 网络栈 | DPDK 用户态,零拷贝 | tonic over 内核 TCP | 单连接延迟差 3~5 倍 |
| NVMe 驱动 | SPDK 用户态轮询 | io_uring(仍过内核块层) | 单盘延迟差 1.5~2 倍 |
| CPU 模型 | thread-per-core 绑核 + busy poll,NUMA 感知 | tokio work stealing,不绑核 | P99/P50 比值差 2~4 倍 |
| 元数据粒度 | 64k bucket,细粒度接管 | 256 分片 | 均衡与接管粒度粗 250 倍 |
| 小写聚合 | 精细的日志分级与整理调度 | 单级日志 + 定期整理 | 整理风暴下延迟抖动更大 |
| 快照/克隆 | 有,基于元数据分层 | 无 | 功能缺失 |
| 分层(S3 下沉) | 有,冷数据下沉对象存储 | 无 | 功能缺失 |
| 大目录分裂 | 有 | 留了 DirLayout 演进位,未实现 | 单目录百万级会撞墙 |
| 客户端缓存一致性 | 分布式租约 | 固定 TTL 1 秒 | 多客户端语义弱一档 |
写这份清单的规矩:数字要有出处(自己测的写测法,引用的注来源), 「为什么不做」只允许两种诚实答案 —— 教学价值低,或工程量超出课程范围。 禁止写「未来会支持」这种既不承诺也不放弃的话,那是产品 PPT 的语言,不是工程师的。
里程碑的最后一步不是庆祝,是复盘 —— 让 AI 反过来质询你,是检验 「这个系统是你造的,还是 AI 替你造的」的试金石。四步循环走到这里闭环: 你出的行为规格、你验收过的实现,现在要能在追问下为每个决策辩护。 判据很朴素:凡是你答不上来的地方,就是三条红线里有一条失守 —— 回到那一课重开一次结对,让 AI 把那段行为重新讲到你能自己复述、 再把对应的验收动作亲手跑一遍。别跳过,L5 全建立在这些之上。
目标:角色调过来 —— 这一轮你质询我。我刚完成 forgefs 的 L4 里程碑:pjdfstest 子集全过、挂载点上编译 zlib 中途硬杀节点产物 checksum 与基线一致、重建期业务 P99 控制在稳态 3 倍内。请你扮演一位苛刻的存储系统面试官,基于 forge 仓库的实际实现盘问我,检验这个系统到底是我造的、还是你替我造的。 行为规格: 1. 你先把元数据层、数据层、挂载层的实现读一遍,挑出 3 处你认为最可能在生产环境出问题的设计; 2. 一次只问一个问题,等我回答完再追问,不许一次把三处全抛出来;问题必须落在行为层面 ——「什么情况下会发生什么、故障时现象是什么、恢复动作是什么」,不许考语法、不许问概念背诵题; 3. 质询范围至少覆盖三件事:跨分片改名在三个崩溃时刻各自的恢复动作、条带提交点(指针切换)前后崩溃分别在盘上留下什么、重建限速对业务突发的响应有多快; 4. 我答错或者答得含糊,先明确指出错在哪,再讲一遍正确的行为,然后告诉我这个行为落在仓库的哪个位置、我能敲哪条命令亲眼看到它 —— 用行为和命令说话,不要让我去读代码; 5. 结束后给我的回答打分,列出 3 个我该重点补课的主题,每个主题配一条我自己能动手做的验证实验。 交付:一份质询记录 + 打分 + 3 个补课主题及各自的动手实验。全程中文。
- 它挑出的每一处「最可能出问题的设计」,让它给一条你能亲手触发的复现路径;至少真做一遍,看现象是不是像它说的那样。做不出来的,要么它在编,要么这处其实没问题 —— 两种都要追
- 你答不上来的每个问题都记下来,让它把答案改写成一段不含代码的行为描述,你要能复述给同事听。复述不出来就是还没懂,别自欺
- 它列的 3 个补课主题,各自回到对应那一课把验收动作重做一遍,全过了再往 L5 走
- 这三处里哪一处最可能在半夜把我叫起来?那时候的现象会是什么、我第一步该敲什么命令?
- 这套系统有哪些故障是它自己扛不住、必须人介入的?介入之前它会先表现成什么样?
- 如果只给我两周、只能补一个短板,你选哪个?为什么这个比另外两个更值得?
小结
- 验收项 1:pjdfstest 声明子集零 FAIL;不支持的语义返回 ENOSYS,静默假成功是运维工单之源
- 验收项 2:编译中途 SIGKILL 节点,make 退出码 0 + 产物 sha256 与 ext4 基线一致 + make test 全绿;节点回归后重建清零且全程无 EIO
- 验收项 3:重建期业务 P99 不超稳态 3 倍、无超 1 秒请求;超标先查限速器是否漏了重构写路径
- 差距清单要有数字、有出处、有诚实的「为什么不做」;它是你能力的证明,不是系统的耻辱柱
- 里程碑④达成:forge 从对象存储长成了能扛节点故障的文件系统 —— L5 解决最后一个问题:demo 和产品之间的距离