Python 自动化任务:Cron 之外还要有状态机 📅 2026/7/5 1:10:29 Python 自动化任务Cron 之外还要有状态机一、定时执行不等于任务可靠Python 自动化任务常从一个脚本开始后来被放进 Cron。每天跑一次、每小时跑一次看起来实现了自动化。但只要出现失败重试、部分成功、重复执行和数据补偿单纯 Cron 就完全不够了。我曾接手过一个真实的定时任务地狱团队有 30 多个 Cron 脚本跑在不同的服务器上。有的成功了没通知有的失败了整整一周没人知道有的因为服务器时区设错了每天比正确时间晚 2 小时执行而且这个时区错误持续了 5 个月。直到业务同学反馈数据总是对不上排查才发现那台旧服务器上的所有定时任务都在错误的时间窗口采集数据。更麻烦的是因为没有用带日期的分区路径每次执行都把旧结果文件覆盖了。5 个月里没有任何一个自动化监控告诉你数据可能有问题因为所有脚本都是成功退出——只是处理了错误时间窗口的数据。还有两个脚本在同一个分区上同时运行相互覆盖对方的输出导致报表数据有时对有时错。可靠任务需要状态机。每个任务实例都应该有明确的状态待执行、运行中、成功、失败、可重试、已跳过。不能只用脚本的退出码来判断一切。脚本失败后后续动作不能靠人工翻日志猜测必须由系统状态来驱动。工程化的自动化任务核心不是按时跑起来而是任何时候都能说清楚各分区的处理状态能从任意中断点安全恢复。二、任务状态要持久化状态持久化可以用数据库也可以用轻量任务表。关键是每次执行都要有唯一的 run_id、明确的输入参数、开始时间、结束时间、错误原因和输出摘要。任务重启时先查询状态再决定是否继续——如果状态显示某个分区已经成功完成就直接跳过如果显示失败且错误类型是可重试的如网络超时就重新执行如果显示失败且错误是永久性的如数据格式错误就跳过并告警人工介入。stateDiagram-v2 [*] -- pending pending -- running running -- success running -- failed failed -- retrying : 可重试错误 retrying -- running failed -- skipped : 永久错误/人工确认跳过 running -- timeout : 执行超时 timeout -- retrying不要只用日志当状态。日志适合后续排查细节不适合驱动流程决策。状态表的一个查询能回答分区 A 是否处理过而日志需要全文搜索才能找到模糊答案。在凌晨三点的事故场景里这个差别决定了排障是 30 秒还是 30 分钟。状态表设计里还要加入处理进度字段。一个任务可能要处理几十个分区全部成功才标 success中间某几个分区失败应该标 partial 状态并记录哪些分区失败。这样重跑时只跑失败分区不用全量重来。三、任务代码要可重入可重入意味着同一个分区被重复执行不会产生错误结果。常用手段包括按日期分区覆盖写入、按文件 hash 去重、按业务主键 upsert。任务失败后重跑应该从未成功的分区继续而不是全量重来——全量重来不仅浪费时间还可能覆盖掉中间已经正确产出的那部分数据。异常处理要区分错误类型网络超时可以自动重试 3 次数据格式错误需要直接标记为需要人工处理并停止重试。async def run_partition(partition: str, store) - None: state await store.get_state(partition) if state success: logger.info(f分区 {partition} 已完成跳过) return if state failed and not is_retryable(state.error_type): logger.warning(f分区 {partition} 永久失败需人工介入) return await store.mark_running(partition) try: await process_data(partition) await store.mark_success(partition) except TemporaryError as e: await store.mark_retrying(partition, str(e)) raise # 让调度器决定是否重试 except PermanentError as e: await store.mark_failed(partition, str(e)) # 永久失败触发告警 raise可重入的关键不仅仅是用 upsert 或者分区覆盖。还需要考虑如果任务在写入一半时被杀死下次重跑会不会读到半成品。解决方式是先写入临时分区或临时表全部完成后做原子重命名或 swap。一次 swap 操作的原子性比写入 1000 条数据后更新状态可靠得多。四、告警要带处理建议任务失败告警不能只发一句任务失败了。至少要带任务名、run_id、失败分区、错误类型、已重试次数和推荐的下一步操作。自动化任务还要支持补跑入口必须复用同一套状态机逻辑不能另写绕过校验的临时脚本。并发执行时要加锁——同一个分区不能被两个 worker 同时处理可以用数据库乐观锁或分布式锁。每次运行后记录输入行数、输出行数、异常行数等摘要信息。只看任务成功退出远远不够因为脚本可能成功处理了空数据或过期数据。告警还要设置抑制窗口。同一个任务同一个错误类型在短时间内连续失败不应重复发送相同的告警消息。可以用首次失败告警 每 N 次失败后更新的模式减少告警噪音。但抑制不能变成静默——抑制窗口过后如果仍然失败需要升级到 P1 通道。五、总结Python 自动化任务不能只靠 Cron。需要有持久化的状态机、可重入的处理逻辑、错误分类的重试策略和方便可靠的补跑入口。脚本能跑起来只是进入生产的第一步任务能在失败后自己说清楚发生了什么、能从哪里安全恢复才算真正达到了生产级可用。生产任务最大的价值不是从来不失败而是失败了能快速恢复。