任務卡在 61% 四個小時沒人發現:給長跑任務裝一顆心跳
上週我們的一條批次管線掛了。凌晨三點開始跑,十一點我去看儀表板,進度卡在 61%,任務狀態還是 running,日誌最後一行停在 03:42,行程還在,CPU 0%。

任務卡在 61% 四個小時沒人發現:給長跑任務裝一顆心跳
上週我們的一條批次管線掛了。凌晨三點開始跑,十一點我去看儀表板,進度卡在 61%,任務狀態還是 running,日誌最後一行停在 03:42,行程還在,CPU 0%。
查下去發現是 SDK 內部重試層把請求卡住了:外層 timeout 600 秒沒問題,但內部重試一層層堆,累計跑了三個小時,每次都「合規」。行程活、狀態活,人就死了。
從那以後上了三件事,都不貴,但那天要是都在,二十分鐘內就會有人被叫醒。
一、心跳:別問任務「活著嗎」,問「你在做什麼」
最先扔掉的是基於狀態判斷。running 是一個狀態,不是證據。心跳才是證據:任務在每一個 stage 結束時往心跳檔案寫一行 `stage=xxx p=61 eta=42m at=
watchdog 每 10 分鐘檢查兩個條件:
- 心跳超過 15 分鐘沒重新整理 → 判為當機
- 連續 30 分鐘進度數字不動,但檔案還在寫 → 判為空轉
兩類分開處理是刻意的:前者是「裝死」,立刻叫人;後者是「某一環變慢但還在走」,嚴重級別低一級,只進日報。分開之後,要叫人的次數少了四分之三。
二、超時分層:當機往往不是外層的鍋
當晚那次當機,外層 600 秒超時根本沒觸發,是內部重試一圈一圈疊起來的。教訓是:超時要說清楚是哪一層的。
- 單個 HTTP 請求:90 秒硬超時,超時即報錯,不做內層重試
- 單個業務 stage:15 分鐘,第一次觸發發告警,第二次直接殺掉
- 整個任務:6 小時,只記錄不殺
第三層不殺,是因為卡在中段時斷點續跑經常能救回來(2026-08-21 那篇寫過),直接殺等於把「當機」升級成「全丟」。
真正省事的一步是把三層的超時值放進同一個設定檔,並在測試裡斷言「內層必須嚴格小於外層」。靠設定互相約束,比靠人記靠譜——總有人手一鬆把某層調大,全套保護就失效了。
三、殺行程分兩步:先 SIGTERM 等 30 秒
以前殺任務是直接 kill -9。問題出在:有的任務正寫到 checkpoint 一半,檔案寫了一半,下次 resume 從壞掉的 checkpoint 直接崩潰。
現在殺行程分兩步,是關鍵改動:
1. 發 SIGTERM,任務的信号處理器停下來補一件事——把當前 stage 標記為 interrupted,未寫完的 checkpoint 換成 .tmp
2. 等 30 秒還活著,再 SIGKILL
這 30 秒裡行程還有能力做收尾,和裸 kill -9 的區別就在這個。看起來瑣碎,但把「當機後 resume 失敗」基本變成了「resume 一次就好」。
檢測到當機之後做什麼
檢測到當機,不要讓 watchdog 自己啟動補救。理由是 watchdog 和業務同機,當機的時候機器往往已經是奇怪狀態——記憶體滿、磁碟滿,直接補救容易踩同一個坑。
我們的路徑:檢測當機 → 推送通知,附一行現場資訊(stage、最後進度、心跳停了多久)→ on-call 二選一:resume 或丟棄。決策最多三秒,因為決策需要的資訊都在通知裡。
一句話收個尾
- running 是狀態,心跳檔案才是證據
- 超時分層寫,內層嚴格小於外層,且靠設定斷言兜底
- 殺行程給 30 秒收尾視窗,別裸 kill -9
- watchdog 只叫人,不自救;決策要三秒內能做完
上了這套之後,同一條管線再沒出過「掛了八小時沒人知道」的事故。最久的一次當機,從發生到有人在手機上看到通知,是 18 分鐘。
留言區
歡迎分享你的想法!
載入留言中…