一家金融企业的合规部门给监管政策监测智能体设定了一项定时任务:每天上午九点检查十个政府网站的最新政策栏目,发现新增文件就向合规专员推送提醒。任务上线头几天运行正常,专员陆续收到了几条政策更新提醒。两周后专员整理邮件时发现,已经连续十天没有收到任何提醒。她以为近期确实没有新政策发布,直到上级问起一项新出台的行业规范为何没有被监测到,她去查后台日志才发现,其中一个网站的栏目结构调整后,那个网站的检查任务连续十天报错,但报错被任务运行器静默捕获,没有触发任何通知。另外几个网站的检查仍在正常执行,每天返回未发现新增,但这些返回是因为内容没有变化,不是因为任务出了问题。专员区分不出哪些未发现新增是真正没变化,哪些是某个网站的检查根本没跑通。
团队的直觉反应是给定时任务补更多日志,把每个步骤的输出都记下来。但日志补全之后问题依然在:报错确实被记下来了,可仍然没有人去看——日志写在服务器文件里,合规专员不会去翻日志文件。另一部分团队选择加自动重试,遇到报错就重跑几次。但如果网站改版导致栏目地址变化,重试多少次结果都一样,重试只会把同一个错误重复一遍。真正的问题不在日志多不多、重试有没有,而在于定时任务从调度、执行、结果保存到交付确认和逾期告警,缺少一套完整的机制把每一次计划执行的状态和业务结果可靠地记录并送达该收到的人手里。
一类原因是每次计划执行没有独立运行实例。定时任务的配置存在调度器里,但每次到了计划时间实际执行的那一轮,没有独立的运行记录。合规专员问昨天九点的任务到底跑没跑、跑到了什么结果,系统只能给出任务配置信息,给不出那一轮执行的计划时间、实际开始时间、实际结束时间、执行状态和业务结果。没有运行实例,每一次执行都是无痕的,失败也好、成功也好,事后都无从追溯。
另一类原因是执行状态和业务结果没有区分。任务跑完了,运行器报告执行成功,但执行成功不等于业务结果有效。检查十个网站,九个正常返回未发现新增,一个报错——执行状态是部分成功,业务结果是九个未发现新增、一个未完成检查。如果只看执行状态,部分成功被当作成功处理,那个未完成检查的网站就被掩盖了。执行状态回答的是任务本身有没有跑通,业务结果回答的是跑通之后查到了什么,两者混为一谈,就无法区分任务执行异常和业务结果为空这两种完全不同的情况。
还有一类原因是结果没有独立保存和交付记录。任务执行完成后,结果直接尝试推送到用户当时的会话。如果结果投递依赖原会话且没有独立保存交付目标,会话过期后结果就找不到投递入口。更根本的问题是结果本身没有先写入持久化存储——结果生成后既没有保存到独立的任务结果表,也没有创建交付记录来跟踪接收人、投递渠道、投递状态和重试情况。结果要么送到了,要么消失了,中间没有留痕。
还有一类原因是缺少子任务级别的状态记录。每天的整体任务实际已经运行,但十个网站的检查结果没有分别记录执行状态、业务结果和错误原因,只有一个笼统的批次汇总。其中一个网站连续十天失败,整体任务每天报告部分成功,没有机制在子任务连续失败时识别具体哪个网站出了问题并触发告警。合规专员看到的是任务每天正常执行、大部分网站未发现新增,连续失败的网站淹没在部分成功的汇总里。没有子任务级别的状态记录,局部持续失败和整体正常运行在用户看来没有区别。
本文基于青山不语AI工作室在部分项目中的异步任务治理实践,将这套处理框架概括为异步任务执行与结果回传保障。
任务定义与运行实例解决每次执行有没有记录的问题。任务在创建时写入持久化存储,记录任务标识、触发计划、执行参数、成功标准和交付目标。每次到了计划时间实际执行时,创建一个独立的运行实例,记录这一轮的计划执行时间、实际开始时间、实际结束时间、执行状态和业务结果。运行实例和任务定义是两层:任务定义是静态配置,运行实例是每次执行的动态记录。服务器重启后,调度器从存储中读取任务定义恢复调度,同时可以查询历史运行实例追溯任意一轮执行的状态和结果。
执行状态与业务结果分离解决跑通了但查到了什么的问题。每次运行实例记录两类信息:执行状态包括成功、部分成功、失败、超时,反映任务本身的运行情况;业务结果包括发现新增、未发现新增、未完成检查、结果无法确认,反映任务跑通之后实际查到了什么。十个网站检查中九个正常一个报错,批次执行状态是部分成功,批次业务结果是九个未发现新增加一个未完成检查。合规专员看到部分成功就知道有环节出了问题,但具体是哪个网站需要排查,还需要子任务级别的运行记录来定位。
结果持久化与交付记录解决结果送到谁手里以及送没送到的问题。任务执行完成后,结果先写入持久化存储,确保结果本身不会因为会话过期或服务重启而丢失。结果保存后创建独立的交付记录,记录接收人、投递渠道、投递状态、重试次数和错误原因。投递渠道使用邮件、企业消息、任务中心、应用内通知或待办看板——消息队列只作为内部投递缓冲,不作为用户接收渠道。交付记录让每一次结果投递都可追溯:送到了显示已送达,没送到显示投递失败并记录原因,重试几次都有记录。如果结果投递依赖原会话且没有独立保存交付目标,才可能出现无法送达的情况——持久化存储和独立交付记录正是为了消除这种依赖。
子任务运行记录与逾期检测解决具体哪个环节出了问题以及任务该跑没跑有没有人知道的问题。每次运行实例下按子任务粒度记录执行状态、业务结果和错误原因——十个网站分别记录各自的检查结果,批次状态由子任务汇总得出。某个网站连续失败时,系统在子任务级别识别具体故障源并告警,而不是只看整体部分成功就认为一切正常。同时,到了计划时间运行实例未按计划创建,系统检测到逾期并告警;任务启动后超过配置时间窗口仍没有产生终态,系统检测到执行超时同样告警。子任务级状态记录解决局部持续失败的可见性,逾期检测解决任务整体未执行或未终结的发现,两道防线覆盖不同层级的异常。
运行幂等与投递幂等解决重复执行和重复通知的问题。服务器恢复后多节点同时调度,可能把同一轮计划任务各跑一遍——运行幂等使用任务标识加计划执行时间生成运行键,通过数据库约束确保同一运行键不可重复创建,或通过原子创建操作阻止多节点重复创建运行实例,而不是仅靠先查询是否存在来判断。结果投递也可能因为通知服务抖动而重试——投递幂等使用独立的投递键,通过同样的约束机制确保同一结果不会被重复通知。两类幂等分别从运行层面和投递层面阻断重复,避免合规专员收到同一条政策提醒两次,也避免下游系统被同一轮检查结果重复触发。
这套机制的运行依赖几个前提:任务定义、运行实例、子任务运行记录和交付记录的存储由开发团队设计和维护,存储容量和保留周期根据执行频率和数据量确定;执行状态和业务结果的分类标准、子任务失败连续次数阈值和逾期检测的时间窗口由业务侧定义,哪些结果必须实时交付、哪些可以异步汇总后批量推送需要业务确认;投递渠道的配置由运维侧负责。任务执行和结果回传是工程实现,任务的业务价值和交付优先级由企业内部定义。
在我看来,异步任务的问题不是模型够不够聪明,而是任务从提交到交付之间的链路有没有被当作一个完整的工程问题来对待。一个每天定时执行、其中一个子任务连续失败十天却无人知晓的任务,比不设置任务还要糟糕——它让团队误以为风险在持续监控之下,实际上近一成的监控源已经失效了十天。把任务定义与运行实例、执行状态与业务结果分离、结果持久化与交付记录、子任务运行记录与逾期检测和运行投递幂等在设计阶段就规划完整,异步任务才能从发了就忘变成可追踪、可恢复、可追责的闭环。