从零件到整机
论文把前三个思想合成一个东西,叫组件。一个组件说清三件事:我需要什么(反效应),我提供什么,我上岗以后做什么(效应)。说白了是一份岗位说明书。
说明书本身不干活。真正上岗的,是它的一次具体实例,论文叫 fiber。打个比方:说明书是「前台」这个职位,fiber 是某天入职的那个人,随身带着自己的回执串。同一份说明书可以同时有好几个人在岗,各管各的。
人只下三道命令
整个系统里,人(或者配置文件)能下的命令只有三道:插入一个组件、让它退役、把它移除。
其余的事全按规则自动流转。一个 fiber 一生会经过这几个阶段:
| 阶段 | 意思 | 什么时候进入 |
|---|---|---|
| 空闲 | 在名册上,没上岗 | 刚插入,或依赖还不齐 |
| 激活中 | 正在一件件做它的效应、收回执 | 需求清单从缺到齐 |
| 在岗 | 正常工作 | 效应全部做完 |
| 停用中 | 正在倒着撕回执 | 退役,或依赖从齐到缺 |
| 失败 | 停下了,原因写明 | 激活到一半出错 |
你从来不用手动说「现在激活它」「现在停用它」。你只说想要什么,什么时候上岗、什么时候歇着,由需求清单和环境的变化决定。
走的时候,为什么不会卡死
最容易出事的是「走」。数据库要停用,可提醒插件还在用它,怎么办?
规矩是:有人还在用你,你就不能先走。这类护栏通常会死锁:两边互相等,谁也走不了 p.37。Cordis 的解法分两步:
- 要走的那位,先挂出停工告示:把自己从「可供货」的名单上撤下来。从这一刻起,没有新的组件能再依赖它。
- 已经依赖它的组件,发现供货的要走,就按依赖关系一个个停用,各自撕干净回执。
新的依赖进不来,旧的依赖都在退场,护栏就一定会放行。论文把这条证成了「进展」:系统不会卡在半路。还有一条配套规矩:同一个名字,同一时刻最多一个供货的。不会出现两个数据库同时自称是「数据库」,下游不知道该听谁的。
乱按也不怕
演示最后那一拍,是这一章最重要的结论,论文叫汇聚:不管一个系统中途经历过怎样的装装卸卸,它停下来时的状态,都等于把最后在岗的那些组件按依赖顺序各装一次、从未卸载过 p.51。
换句话说,过程不留痕迹。中途装过又拆掉的组件、反复断开的依赖,不会在系统里留下任何「历史」。这也是为什么前面几章的保证可以放心叠在一起用。
还有一种结局要说清:如果一个组件激活到一半失败了,它停在「失败」,原因写明,已经做成的部分按回执撤掉;环境没变,就不会反复重试。它不会把错误传染给邻居:和成功的结局相比,只差这一个组件的状态 p.55。
热重载与声明式配置
有了上面这些,两个实用功能几乎是白送的。

热重载:拆旧装新,失败整体回滚
改了源码,不用重启。热重载的做法就是拆掉旧 fiber、用新代码装一个新的 p.67。
而且它是事务式的:哪怕一个模块因为语法错误导入失败,所有改动整体回滚到备份,系统永远不会停在「换了一半」p.68。代价是新 fiber 从头装起,组件自己内存里攒的东西不保留。
声明式配置更进一步:整个系统写成一份配置,你只写「应该是什么样」,加载器负责把现实改成那样,而且一直保持同步 p.65。你改一行配置,加载器只动有差别的那几个组件。这就是演示里左边那排开关在做的事。
人只说想要什么;谁先上岗、谁先离场、半路失败怎么收尾,交给规则。
请为这个项目设计一个声明式的模块加载器: 1. 用一份配置列出想启用的模块,模块各自声明依赖; 2. 加载器根据配置和依赖,计算每个模块应处于「运行」「等待依赖」还是「未启用」; 3. 配置变化时,只处理有差别的模块:先按依赖倒序停止,再按依赖正序启动; 4. 模块启动失败时,只撤销它已完成的部分,标记为失败并记录原因,不影响其他模块; 5. 写一个测试:随机开关模块 100 次后,系统状态应与「按最终配置从零启动」完全一致。
