定时任务要能安全重跑:幂等性设计的四个落点
我每天清晨 6:00 让一个浏览器自动化任务替我做几件重复的事:登录、签到、把当天能做的积分任务做掉、最后回读积分明细写一份汇报。这套东西跑了十来天,踩得最多的坑不是「能不能做对」,而是「重跑会不会做坏」。
定时任务天生会重跑:机器休眠后补跑、网络抖动超时、页面改版导致中途失败、甚至同一个 cron 表达式被注册了两次。如果一次执行不是幂等的,重跑就会变成灾难——重复发帖、重复提交、被平台判定刷量。
下面是我最终稳定下来的四个落点。
一、先查状态,再决定动作
大多数自动化脚本的写法是「直接执行,失败就重试」。幂等的写法是先问一句:这件事现在处于什么状态?
把每个任务拆成三段:check(读服务端状态)、act(执行动作)、verify(再读一次状态确认变化)。重跑时 check 命中「已完成」就直接跳过,一次 act 都不发。这条看起来朴素,但它把「重跑」从危险操作降级成了无害操作——重跑十次和重跑一次的效果完全一样。
实际收益很直接:签到这种一天只能一次的动作,靠 check 判断「今天是否已签到」,而不是靠「上次执行有没有报错」。
二、给每次执行一个幂等键
check 只能判断服务端状态,判断不了「我本地是不是已经做过」。所以本地还要有一份账:以「日期 + 任务标识」作为幂等键,执行成功后落盘记录,重跑时先读这份账。
键里必须包含日期,否则第二天的任务会被昨天的记录挡住。也建议把「失败」和「未执行」分开记:只有成功才写终态,失败保持可重试,这样才能区分「已经做过」和「还没做完」。
三、失败要分类,不能一律重试
我把失败分两类。第一类是可重试:元素还没渲染、请求超时、偶发 5xx,这类重试一两次基本能过。第二类是不可重试:需要人工验证码、需要人脸识别、需要另一个系统授权,这类重试一百次也没用,只会把账号推到风控前面。
工程上的做法是给每个任务一个重试预算,同一步连续失败两次就跳过,并在汇报里显式写「卡在哪」。这比无限重试诚实,也比静默失败有用——人第二天看到「卡在需要人脸识别」,就知道该自己上场了。
四、成功信号必须来自服务端
这是最容易翻车的一条。点击成功不等于任务完成:按钮点了没反应、表单缺一个必填项、页面静默拦截,都会让脚本以为成功了。
我自己就撞过一次:点「发表」之后页面没有任何跳转也没有报错,脚本记了一笔「成功」,实际文章根本没发出去,当天积分也没到账。更糟的是,因为本地已经记了成功,重跑时被直接跳过,问题要等到第二天对账才暴露。
所以终态只能由服务端事实决定:回读一次列表或明细,确认目标状态真的变了,才算成功。所谓「验证」,就是拿一个和你的点击无关的证据。
一段骨架
把上面四条收成伪代码,大致是这样:
for task in tasks:
key = today() + ":" + task.id
if ledger.done(key):
continue
if task.check():
ledger.mark(key, "done")
continue
for attempt in range(2):
if task.act() and task.verify():
ledger.mark(key, "done")
break
else:
ledger.mark(key, "blocked", task.reason())
写在最后
幂等性听起来是后端接口设计的话题,但任何「会被重复触发的自动化」都躲不开它。区别只是:接口可以用唯一键去重,脚本只能靠自己查状态、记账、核对。
如果你也在跑定时任务,不妨先问自己一句:这条任务今天已经跑过一次了,再跑一次会发生什么?如果答案是「会重复发一篇文章」,那它现在就还不该上定时器。
- 点赞
- 收藏
- 关注作者
评论(0)