← 返回目录
p8.9动手⏱ 约 15 分钟

部署与运维:主动把系统弄坏,看它怎么坏

五项故障演练,两项需要改进——其中最危险的那一项,是系统读到 0 根 Bar 却不报错

🔎 最后验证 2026-08📚 来源:五项故障演练实测,2026-08-01 于 ECS 实测,输出见 code/outputs/stdout.txt🧰 Python 3.12.13、qsys(本课自研)
为什么学这个

没演练过的故障处理代码,等于没写。

主动注入五类真实会发生的故障,看系统怎么坏:

数据文件缺失     抛出 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多标的组合与资金分配
模拟撮合、纸上交易盘中实时循环(本课是日频回放)
落盘、任务化、指纹权限、多用户、审计日志
故障演练灾备与异地容灾

它是一个能跑通全流程、能被审计、能被排障的教学系统——不是一个生产系统。

而下一节要回答的是一个更重要的问题:为什么本课到此为止,不往实盘走。

❓ 测验
故障演练中,系统在数据被截断到 10 行时「不报错、照常跑完、给出净值曲线」。这属于什么问题?
✏️ 填空
运维的第一原则不是「别让它挂」,而是「让它在该挂的时候挂,而不是带着 ___ 继续跑」。

📌 免责:本课为技术教学,系统为教学用途,只做模拟撮合与纸上交易;不构成投资建议。

✅ 小结

四件事:

  1. 没演练过的故障处理代码,等于没写。五项演练里 2 项需要改进——而演练的价值不在"全绿",在于把"我以为它会怎样"变成"它实际会怎样"
  2. 最危险的是静默降级:数据被截断到 10 行,系统不报错、照常跑完、给出一条净值曲线。兜住它不在运维层,在数据层(p8.3 的四类校验)
  3. 演练 5 揭示了一个设计缺陷:read() 遇到半行 JSON 整体失败——一条坏行不该让前面 10000 条好行都读不出来。正确做法是跳过并计数,而这个计数必须被报出来(p8.6:任何丢弃都要计数)
  4. 运维清单五项:进程守护(重启后靠落盘恢复)· 日志分级(每次丢弃都要有 WARN)· 告警(告警太多 = 没有告警,同 p7.5)· 定时任务(退出码非零就停)· 故障演练

一条贯穿的原则:

运维的第一原则不是"别让它挂",而是"让它在该挂的时候挂,而不是带着错误继续跑"。

最后诚实地说:这套系统离"能用"还差不少——没有多标的组合、没有盘中实时循环、没有权限与审计、没有灾备。它是一个能跑通全流程、能被审计、能被排障的教学系统,不是一个生产系统。

下一节是全课的最后一节,回答那个从 p0.1 就悬着的问题:为什么本课到此为止,不往实盘走。

下一节 → 实盘的最后一公里
🔎 来源与核验· 1 条,点开核对
本节每个关键论断都对应一个可追溯的来源 —— 这是本课程"靠谱、不过时"的底线。
「五项故障演练实测:①数据文件缺失抛 FileNotFoundError(大声失败)②数据被截断至 10 行时读到 0 根 Bar 且不报错(需靠行数校验兜住)③无行情下单被拒且 reason=「无行情」④进程重启后新进程读到 5 条记录、末条权益 1,000,400(状态可恢复)⑤半行 JSON 使 read() 抛 JSONDecodeError(应改为跳过并计数);5 项中 2 项需改进」
📚 本节 code/p89_ops.py,2026-08-01 于 ECS 实测,输出见 code/outputs/stdout.txt✓ 已核验 2026-08
演练在临时目录内进行,不影响生产数据;系统为教学用途,仅含模拟撮合。
智图软件的赞赏码
都看到这了,打个赏呗!
接下来 · p8.10
实盘的最后一公里:为什么本课到此为止
继续读下一节 →