模块 11查看课程目录
11
实战项目ElixirErlangOTP
综合项目:可靠任务调度器
把函数式建模、进程协议、OTP、互操作、容量与运维串成一个系统。
4检查点
10 小时预计完成
为什么先学这个
先修正心智模型,再增加 API
零散练习容易把每个 API 都做对,却没有面对系统级取舍。这个项目刻意加入容量、失败和语言边界,让你必须说明:谁拥有状态、什么能重试、恢复半径多大、过载如何暴露。
学完你能做到
可验证的学习目标
- 能从业务不变量推导进程与监督树
- 能让 Erlang 与 Elixir 在同一发布包中承担真实职责
- 能通过故障注入验证恢复,而不是只跑 happy path
前置知识
- 完成前 11 个模块
- 能独立编写 GenServer/gen_server 与 Supervisor
最小心智模型
先抓住三个词
at-least-once
任务可能重复执行。系统需要幂等键或去重策略,不能用“重试”假装恰好一次。
bounded queue
队列有固定容量;满时系统必须阻塞、拒绝或降级,而不是继续承诺。
runbook
面向运行者的诊断与处置说明:看什么指标、如何判断、可以执行什么安全动作。
双语代码桥
先对齐协议,再看标点
Elixir 管理易用 API 与输入验证;Erlang worker 明确掌控队列容量和调度状态。
Elixir
defmodule Scheduler.API do
def submit(payload, opts \\ []) do
with :ok <- validate(payload),
{:ok, id} <- :scheduler_core.enqueue(payload, opts) do
{:accepted, id}
else
{:error, :queue_full} -> {:rejected, :busy}
{:error, reason} -> {:rejected, reason}
end
end
endErlang
handle_call({enqueue, Job, Opts}, _From,
State = #state{queued = Queue, max = Max}) ->
case queue:len(Queue) < Max of
true ->
{Id, Next} = add_job(Job, Opts, State),
{reply, {ok, Id}, dispatch(Next)};
false ->
{reply, {error, queue_full}, State}
end.LAB
约 15–25 分钟可运行实验
四次故障注入
不要把“进程重启了”当作验收。对 worker 崩溃、非法消息、任务超时、节点断开分别定义期望状态。
- 01
执行中的 worker 主动 exit,检查任务重试与容量计数
- 02
发送不符合协议的消息,确认服务不中断且有观测信号
- 03
让任务超过 timeout,检查取消/隔离与迟到结果
- 04
断开远程节点,检查 stale 状态与重连
在终端 / shell 中运行
mix test --only fault_injection --trace预期观察
- 所有长期进程仍在监督树中
- 任务不会无限重试
- 队列与并发计数最终恢复一致
- 每次恢复都有结构化日志或指标
故意弄坏
删除重试上限并让任务稳定失败。观察 restart/重试风暴如何放大原始故障。
这个实验能证明
指定故障下,系统按已定义策略恢复或拒绝。
这个实验不能证明
四个场景不能穷尽生产故障;尤其不能证明恰好一次、跨节点一致性或外部副作用安全。
快速自测
任务失败后自动重试,最先必须明确什么?
本章挑战
毕业答辩
交付源码、监督树图、消息协议、容量预算、故障实验记录、release 与一页 runbook;用 10 分钟解释三个最重要取舍。
提示 1轻推一下
取舍必须包含一个你明确没有实现的保证。
提示 2缩小问题
演示 queue_full,而不是只演示成功提交。
提示 3接近实现
runbook 从“用户看到什么”反推需要的信号。
提示 4用验收标准反推
从下面每一条验收标准倒推一个最小测试。若某条无法写成测试,先把表述改成可观察结果。
完成标准
- Elixir 与 Erlang 都有真实且合理职责
- 并发、队列、timeout、retry 全部有上限
- ExUnit 与 EUnit 穿过互操作边界
- release 可启动,runbook 可由他人执行
带走这三句话
复习卡
- 1可靠系统先定义不变量、容量和失败语义,再选择进程结构。
- 2重试扩大副作用;幂等与上限必须成对出现。
- 3可运维性不是最后加日志,而是从协议开始保留证据。
继续核对