模块 07查看课程目录
07
OTP中级ElixirErlangOTP
监督树与应用
可靠性来自清楚的故障域,不是“进程会自动重启”。
4检查点
6 小时预计完成
为什么先学这个
先修正心智模型,再增加 API
“Let it crash” 经常被误解为不处理错误。真正的含义是:在合适边界把不可恢复的局部状态交给监督者,由预先定义的策略恢复;可预期的业务错误仍应正常返回。
学完你能做到
可验证的学习目标
- 能按故障依赖划分监督树
- 能解释 one_for_one、one_for_all、rest_for_one
- 能选择 permanent、transient、temporary restart
前置知识
- 会实现 GenServer/gen_server
- 理解 link 与 exit
最小心智模型
先抓住三个词
故障域
一组需要共同恢复或彼此隔离的组件。监督树结构应表达这种恢复关系。
restart intensity
在时间窗口内允许的最大重启次数。超过阈值,监督者自身退出,把问题上推。
Application
OTP 中可启动、可配置、可依赖的组件单元;根监督树通常从 application callback 启动。
双语代码桥
先对齐协议,再看标点
子进程顺序在 rest_for_one 下有恢复含义:前面的基础设施崩溃,会重启其后的依赖者。
Elixir
children = [
{Registry, keys: :unique, name: Jobs.Registry},
{DynamicSupervisor,
strategy: :one_for_one,
name: Jobs.Workers},
Jobs.Dispatcher
]
Supervisor.start_link(
children,
strategy: :rest_for_one,
name: Jobs.Supervisor
)Erlang
init([]) ->
Registry = #{
id => jobs_registry,
start => {jobs_registry, start_link, []}
},
Workers = #{
id => jobs_workers_sup,
start => {jobs_workers_sup, start_link, []},
type => supervisor
},
Dispatcher = #{
id => jobs_dispatcher,
start => {jobs_dispatcher, start_link, []}
},
{ok, {{rest_for_one, 3, 5},
[Registry, Workers, Dispatcher]}}.LAB
约 15–25 分钟可运行实验
杀掉不同位置,观察不同恢复半径
记录三个子进程 PID,分别终止 dispatcher 与 registry。比较哪些 PID 改变。
- 01
启动监督树并记录所有 child PID
- 02
让最后一个 child 异常退出
- 03
让第一个 child 异常退出,再次记录 PID
在终端 / shell 中运行
Supervisor.which_children(Jobs.Supervisor)预期观察
- 最后 child 崩溃时只重启自己
- 第一个 child 崩溃时,其后的依赖 child 都重启
故意弄坏
把 strategy 改为 one_for_all。再杀最后一个无关 child,观察过大的恢复半径。
这个实验能证明
restart strategy 与 child 顺序共同定义恢复范围。
这个实验不能证明
PID 重建不代表业务状态已经正确恢复;外部副作用、持久化和幂等仍需单独设计。
快速自测
什么时候 rest_for_one 最合适?
本章挑战
监督树答辩
为任务队列画出监督树,逐个说明每个 child 为什么是 permanent/transient/temporary,以及它与兄弟节点是否需要共同恢复。
提示 1轻推一下
从“谁拥有长期状态”开始。
提示 2缩小问题
动态任务与基础设施通常不使用同一 child spec。
提示 3接近实现
加入超过 restart intensity 时的系统行为。
提示 4用验收标准反推
从下面每一条验收标准倒推一个最小测试。若某条无法写成测试,先把表述改成可观察结果。
完成标准
- 每条父子关系都有故障理由
- 重启类型与正常退出语义匹配
- 明确指出一个不应自动重试的业务错误
带走这三句话
复习卡
- 1监督树首先是故障依赖图。
- 2Let it crash 不等于忽略可预期错误。
- 3进程重启只是恢复动作的一部分,状态与副作用仍需设计。
继续核对