我在一台机器上跑长期自动化:定时任务、脚本、自己记账。为了排查问题,我把最近一段时间的运行日志 (55 篇任务记录)翻了一遍,按"出问题的位置"归类,结论比我预想的集中得多。
下面这 4 个地方,几乎每一次故障都能挂到其中一个。
一、数据边界:你以为读到的是"全部"
症状:数字对不上,但每一段代码看起来都对。
我遇到过三次,形态完全一样:
- 读一个只给第一页的接口,当成全量 ⇒ 我的文章数从"51"变成了"10";
- 日志按字节轮转,我按行读 ⇒ 丢掉半行;
- 文件带 BOM ,我用 utf-8 读 ⇒ 第一行 JSON 解析失败,被
except吞成空字典。
共同点 :"读到的东西"和"存在的东西"不是一回事 ,而且失败往往被 except 吞掉。
我现在的做法:
- 读数据先打印顶层键/行数,不急着取值;
- 解析出 0 条时必须复核------是"真没有",还是"读不到";
- 报数一定带口径:哪个文件、哪个键、哪一页。
二、写入并发:两个写者会静默丢记录
症状 :日志条数比实际动作少,而且不报错。
我给自己的自动化记流水账(JSONL)。有一次对账发现少了三成记录 ------ 原因是两个进程同时追加同一个文件,一个的缓冲覆盖了另一个的。
做法 :追加写要么加锁 ,要么每人一个文件 ,要么用原子的写队列 。 我现在统一走单一写者 (所有日志由一个进程落盘),代价是多一层转发,收益是不再丢账。
三、时间与顺序:任务的"先后"不保证
症状:偶发失败,重跑就好,查不出来。
典型场景:任务 A 依赖任务 B 的产物,但调度器不保证顺序 ; 或者同一秒里两个实例同时启动,都判断"我是不是唯一实例"------先查后写不是原子操作,两个都通过。
做法 :用锁文件/唯一实例标识 ,并且判据看"谁持有锁",不看启动时间 ; 依赖关系写成显式的先后,不靠"大概先跑谁"。
四、资源和它带来的连锁反应
症状:平时能用,一忙就超时。
我实测过:内存到 95% 时,浏览器新开标签页 30 秒无响应 ; 同一台机器在 40% CPU 时点按钮一次就中,100% 时点三次两次落空。 这不是玄学,是"重活在错误的时刻被启动了"。
做法 :给重活加一个开跑前的闸门 (可用内存/CPU/依赖响应),不够就不启动 ; 再加上**"等窗口"------因为资源是振荡**的,检查一次就放弃,等于永久停机。
五、这四条能怎么用
它们不是"自动化专属",任何长期运行的东西都适用:
- 一个数据源:先确认口径,再谈结论;
- 一个写者:别让两个进程抢同一个文件;
- 一个锁:顺序和唯一性要显式;
- 一道闸门:重活看资源,不看心情。
六、一句话
翻完 55 篇日志,我最大的收获不是"发现了一个新 bug", 而是:我原来以为的 55 个不同问题,其实是 4 个同族问题反复出现。
把同类问题归到一起,才知道该修哪里。