部署与运维:主动把系统弄坏,看它怎么坏
五项故障演练,两项需要改进——其中最危险的那一项,是系统读到 0 根 Bar 却不报错
没演练过的故障处理代码,等于没写。
主动注入五类真实会发生的故障,看系统怎么坏:
数据文件缺失 抛出 FileNotFoundError ✅ 大声失败
数据被截断 读到 0 根 Bar,不报错 ⚠️ 需要改进
无行情就下单 拒单,reason="无行情" ✅ 拒单并给出原因
进程重启 新进程读到 5 条记录,状态可恢复 ✅
写到一半断电 read() 抛 JSONDecodeError ⚠️ 应改为跳过坏行并计数
5 项里 2 项需要改进。 而其中最危险的是第二项:
数据被截断到只剩 10 行,系统不报错,照样跑完并给出一条净值曲线——而那条曲线是用 10 行数据算出来的。
判据只有一条:
故障必须让系统停下来或报警,而不是让它继续输出看起来正常的结果。
消防演习不是为了证明"我们有灭火器",是为了发现灭火器过期了、安全门被杂物堵了、没人知道集合点在哪。
没演习过的应急预案,只是一份文件。
一、五项演练与判定
| 演练 | 系统的实际反应 | 判定 |
|---|---|---|
| 数据文件不存在 | 抛出 FileNotFoundError | ✅ 大声失败 |
| 数据被截断(只剩 10 行) | 读到 0 根 Bar,不报错 | ⚠️ 必须靠行数校验兜住(p8.3) |
| 没有行情就下单 | OrderAck.accepted=False,reason="无行情" | ✅ 拒单并给出原因 |
| 进程重启 | 新进程读到 5 条记录,末条权益 1,000,400 | ✅ 状态可从落盘恢复 |
| 写到一半断电(半行 JSON) | read() 抛 JSONDecodeError | ⚠️ 应改为跳过坏行并计数 |
演练的价值不在于"全绿",在于把"我以为它会怎样"变成"它实际会怎样"。
二、最危险的那一项
"静默降级"比"直接崩溃"危险得多。
演练 2 就是标准案例:数据被截断到 10 行,LocalParquetData 不报错——它老老实实读了那 10 行,然后返回 0 根符合区间的 Bar。
而下游会照常跑完,给出一条"净值曲线"。
它不会告诉你这条曲线是用 10 行数据算出来的。
所以运维的第一原则不是"别让它挂",而是:
「让它在该挂的时候挂,而不是带着错误继续跑」。
兜住它的办法不在运维层,在数据层(p8.3):落盘后立刻校验行数、区间、缺失率、主键唯一性,不通过就拒绝上线这份数据。
🔧 动手做:主动把系统弄坏,看它怎么坏(6 分钟)
跑故障演练:把数据文件截断到只剩 10 行,再让系统去读它跑回测。
你会看到(实测):系统读到 0 根 Bar,却不报错——它安静地跑完、给你一个空结果,你还以为一切正常。这是五项演练里最危险的一项(其他如数据文件缺失会大声抛 FileNotFoundError,反而安全)。
想明白:系统出错分两种——大声挂掉的(抛异常,你立刻知道)和安静变坏的(读到 0 根 Bar 照样跑,结果全错还不报错)。后者致命得多。所以运维不是"祈祷别出事",是主动把它弄坏,看它怎么坏:缺文件、断网、截断数据、时钟漂……凡是"该挂却没挂"的,都要补一道校验让它在该挂的时候挂。
三、演练 5 揭示的一个设计缺陷
read() 遇到半行 JSON 直接抛异常——大声失败,比静默好,但对流水账来说仍然不对:
一条坏行不应该让前面 10000 条好行都读不出来。
正确做法:
bad = 0
for line in text.splitlines():
try:
yield json.loads(line)
except json.JSONDecodeError:
bad += 1 # ← 跳过并计数,而不是整体失败
而 bad 这个计数必须被报出来——这正是 p8.6 那条纪律:任何丢弃都要计数,不记录就是黑洞。
四、上线前的运维清单
| 项 | 做什么 | 关键点 |
|---|---|---|
| 进程守护 | systemd / supervisor / pm2,必须能自动重启 | 重启后靠落盘恢复状态(演练 4) |
| 日志分级 | INFO 记流程、WARN 记降级、ERROR 记中断 | 每一次"丢弃数据"都要有一条 WARN |
| 告警 | 只对需要人介入的事告警 | 告警太多 = 没有告警(同 p7.5 的 linter) |
| 定时任务 | 取数 → 校验 → 回测 → 报告,每步都有退出码 | 退出码非零就停,不要带病往下跑 |
| 故障演练 | 定期主动注入故障 | 没演练过的处理代码等于没写 |
"告警太多 = 没有告警"这一条与 p7.5 完全同构:一个天天报 73 条红的 linter,和一个没有 linter,效果是一样的。
五、这一章的系统,离"能用"还差什么
诚实地说:还差不少。
| 已有 | 还没有 |
|---|---|
| 事件、Gateway、Adapter | 多标的组合与资金分配 |
| 模拟撮合、纸上交易 | 盘中实时循环(本课是日频回放) |
| 落盘、任务化、指纹 | 权限、多用户、审计日志 |
| 故障演练 | 灾备与异地容灾 |
它是一个能跑通全流程、能被审计、能被排障的教学系统——不是一个生产系统。
而下一节要回答的是一个更重要的问题:为什么本课到此为止,不往实盘走。
📌 免责:本课为技术教学,系统为教学用途,只做模拟撮合与纸上交易;不构成投资建议。
四件事:
- 没演练过的故障处理代码,等于没写。五项演练里 2 项需要改进——而演练的价值不在"全绿",在于把"我以为它会怎样"变成"它实际会怎样"
- 最危险的是静默降级:数据被截断到 10 行,系统不报错、照常跑完、给出一条净值曲线。兜住它不在运维层,在数据层(p8.3 的四类校验)
- 演练 5 揭示了一个设计缺陷:
read()遇到半行 JSON 整体失败——一条坏行不该让前面 10000 条好行都读不出来。正确做法是跳过并计数,而这个计数必须被报出来(p8.6:任何丢弃都要计数) - 运维清单五项:进程守护(重启后靠落盘恢复)· 日志分级(每次丢弃都要有 WARN)· 告警(告警太多 = 没有告警,同 p7.5)· 定时任务(退出码非零就停)· 故障演练
一条贯穿的原则:
运维的第一原则不是"别让它挂",而是"让它在该挂的时候挂,而不是带着错误继续跑"。
最后诚实地说:这套系统离"能用"还差不少——没有多标的组合、没有盘中实时循环、没有权限与审计、没有灾备。它是一个能跑通全流程、能被审计、能被排障的教学系统,不是一个生产系统。
下一节是全课的最后一节,回答那个从 p0.1 就悬着的问题:为什么本课到此为止,不往实盘走。
🔎 来源与核验· 1 条,点开核对
