给后台任务加三道护栏:幂等、超时与可观测性
后台任务最怕的不是写不出来,而是跑起来之后失控:同一条消息被消费了两次,某个任务卡住一整夜,或者出了问题却没有任何人知道。很多线上事故回头看,都不是业务逻辑写错了,而是缺了三道最基本的护栏:幂等、超时与可观测性。这三道护栏都不难,难的是在任务上线之前就把它们当成默认配置,而不是出事之后再补。
第一道护栏:幂等,让重跑成为安全操作
消息队列、定时调度、人工重试,都会让同一个任务被执行不止一次。网络抖动时生产者重发,消费者处理完还没来得及确认就崩溃,调度器在任务超时后又补发一次,这些都是正常现象,不是异常。把重跑当成异常来防,是防不住的;正确的做法是让重跑本身无害。
幂等最常用的办法是给每个任务一个唯一键。消息自带的消息 ID、业务单号加操作类型、调度周期加任务名,都可以做唯一键。执行之前先以这个键做一次原子性的认领:能认领到的才真正执行,认领不到的说明已经有人在做或者已经做过,直接返回成功。这个认领记录要和业务结果放在同一个事务里,或者至少保证结果写成功之后认领状态不会丢,否则会出现执行成功却被判定为未执行,下一次重跑又执行一遍。
还有一种常见的坑,是把“查一下有没有”当成幂等。先查库发现没有,再插入,这两步之间有竞态,两个并发执行会同时查到没有、同时插入。必须用唯一约束、原子操作或者分布式锁来保证只有一个能成功,查库只能做辅助,不能做最终裁决。
幂等还要覆盖副作用。发短信、调第三方接口、扣减库存,这些操作本身可能不支持幂等,办法是把外部调用的结果也记下来:同一个唯一键第二次执行时,直接返回第一次记录的结果,不再真的调用对方。成本是一次写库,换来的是重跑不再产生重复的外部影响。
第二道护栏:超时,让卡住的任务自己停下来
没有超时的任务,卡住就是永久卡住。下游接口不返回、数据库锁等待、死循环、磁盘写满,任何一种情况都能让一个任务占着 worker 不放。worker 池被占满之后,整个任务系统都会停摆,而监控上可能只看到队列在变长,找不到具体是哪一个任务在作怪。
超时要分三层设。第一层是单次外部调用超时,比如 HTTP 请求设连接超时和读取超时,数据库操作设语句超时。第二层是任务整体超时,从任务开始到结束给一个上限,超过就中断并标记失败。第三层是调度层面的看门狗:任务开始执行后如果超过预期时间还没有上报心跳或者结束,调度器就把它标记为可疑,必要时由另一个 worker 接手或者直接告警。
超时之后的处理同样重要。超时不等于失败已经处理完:被中断的任务可能已经写了一半数据,也可能外部调用其实已经成功只是响应丢了。所以超时和幂等是绑在一起的,超时触发重试时,靠幂等键保证第二次执行不会重复产生副作用。只设超时不设幂等,重试会把问题放大;只设幂等不设超时,卡住的任务永远不会触发重试。
另外,超时时间不要拍脑袋。先看历史执行时间的分布,取一个覆盖绝大多数正常情况、又能及时发现异常的值。比如任务平时在几秒内完成,偶尔到一分钟,那么整体超时设在五分钟就比较合理。超时太短会误杀正常任务,太长则等于没有护栏。
第三道护栏:可观测性,让问题自己说话
任务系统最危险的状态不是报错,而是静默失败:任务没跑、跑了一半、结果写错了地方,却没有任何动静。等到用户投诉才发现,往往已经过去了好几天。可观测性要回答三个问题:任务跑了没有,跑成什么样,失败时能不能快速定位。
最基础的是三条记录:开始、结束和关键节点。开始时记任务名、唯一键和输入摘要,结束时记耗时和结果,关键节点记进度。日志里一定要带上唯一键,这样同一个任务的多次执行、上下游调用都能串起来查。只记“任务失败”四个字是没有用的,失败时要记下当时的输入、重试次数、最后一次错误和外部调用的返回码。
其次是指标。每个任务至少要有四个指标:执行次数、成功率、耗时分布和队列等待时间。成功率突然下降说明下游或者逻辑出了问题,耗时分布变长说明有慢查询或者外部依赖变慢,队列等待时间变长说明 worker 不够或者有任务卡住了。指标要配告警阈值,但阈值要按任务分级:核心任务失败一次就告警,低优先级任务可以容忍几次失败再告警,避免告警疲劳。
最后是死信和人工介入通道。重试次数用完仍然失败的任务,不要直接丢弃,要进死信队列并保留完整上下文,方便人工查看和手动重放。死信队列本身也要有监控,堆积超过一定数量就告警,否则它会变成另一个静默失败的地方。
三道护栏的落地顺序
如果只能先做一件事,先做超时。因为没有超时,一个卡住的任务就能拖垮整个系统,而且排查成本最高。第二做幂等,因为一旦超时和重试成为常态,没有幂等就等于主动制造重复数据。第三做可观测性,因为前两道护栏上线之后,你需要知道它们有没有在正常工作:超时触发了多少次、幂等拦截了多少次重复执行,这些数字本身就是系统健康度的晴雨表。
这三道护栏的共同点是:它们都不属于某个具体业务,却决定了所有业务任务能不能长期稳定运行。写任务逻辑的时候多花半小时把它们接好,比上线之后花半夜排查一次事故要划算得多。
- 点赞
- 收藏
- 关注作者
评论(0)