任務卡在 61% 四個小時沒人發現:給長跑任務裝一顆心跳

上週我們的一條批次管線掛了。凌晨三點開始跑,十一點我去看儀表板,進度卡在 61%,任務狀態還是 running,日誌最後一行停在 03:42,行程還在,CPU 0%。

專屬插圖
任務卡在 61% 四個小時沒人發現:給長跑任務裝一顆心跳

任務卡在 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 分鐘。

留言區

歡迎分享你的想法!

發表留言

0/500

載入留言中…