可靠的 cron:最單純的定時任務,一分散就變難
· tech
📑 目錄
cron 大概是最單純的一種基礎設施:時間到,跑一個任務。單機上寫過 crontab 的人都覺得它理所當然。但只要加上兩個字——「可靠」(那台機器掛了,任務還是得照跑),它就從最簡單的東西,一夕變成一個會扯到分散式共識的硬問題。這篇講為什麼,以及它逼你面對的一個沒有完美解的抉擇。
單機 cron 很簡單,可靠的 cron 很難
單機 cron 的致命傷很明顯:它是單點故障。那台機器一掛,所有排程跟著停擺,而且你可能過了好幾個週期才發現。直覺的修法是把它做成分散式——多台副本、選一個 leader 來跑、掛了換人接手。但這一搬,立刻冒出一個新的硬問題:leader 換人接手的那一刻,「哪些任務已經跑過了」這份狀態絕不能丟——否則新接手的人根本不知道某個任務跑了沒,結果不是重跑就是漏跑。
換句話說,可靠的 cron 難點不在排程本身,而在狀態的持久性:那份「哪些任務、在哪個週期、跑過了沒」的帳,必須撐得過任何一台機器的崩潰與 leader 的改朝換代。而這正是分散式共識的應用題——用 Paxos 之類的共識,把這份帳存成一份大家都同意、且失效也不會丟的日誌。
沒有免費的 exactly-once:跳過 vs 重複,選一個
就算有了共識存狀態,還有一個更狡猾的問題,藏在「決定要跑」和「記錄跑過了」這兩個動作之間的空窗裡。leader 只要在這個空窗中崩潰,你就必然踩到兩種災難之一:
所以真正要先問的問題是:這個任務,跳過比較痛,還是重複比較痛? 寄一封帳單通知,重複寄兩次很尷尬,寧可有機制擋重複;產一份可覆寫的報表,漏產一次比產兩次糟,那寧可重跑。而最漂亮的解法是把選擇權拿掉——把任務做成冪等,重複執行也無害,你就能永遠選「至少跑一次」,睡得著覺。這正是我在Airflow 排程那篇一直強調的:冪等可重跑,是資料任務的地基,不是加分項。
還有一個坑:午夜的驚群
最後一個實務陷阱:大家排程都愛整點,尤其 0 0 * * *(午夜)。結果就是每天 00:00:00 那一瞬間,成百上千個任務同時湧出、同時搶資源、同時打同一個下游——這就是 thundering herd(驚群)。解法很簡單但常被忘記:加抖動(jitter),把觸發時間在一個小區間內隨機打散,別讓所有任務擠在同一秒。
反思
cron 是「簡單的東西一分散就變難」的最佳範例
我很喜歡拿 cron 當例子,因為它完美示範了分散式系統的一個殘酷規律:一個東西在單機上有多簡單,搬到分散式就有多難。 單機 cron 是新手都能寫的 crontab;可靠的 cron 卻要用上 leader 選舉、共識日誌、崩潰空窗分析——難度差了好幾個數量級,而需求聽起來只是「多加兩個字:可靠」。這讓我對「這需求很簡單吧?」這句話越來越警惕——很多時候,簡單的是happy path,真正的成本全藏在「掛了怎麼辦、剛好崩在中間怎麼辦」的邊界裡。估工時,要估的是那些邊界,不是那條快樂路徑。
跳過還是重複,先想清楚你的任務怕哪一個
「沒有免費的 exactly-once」是我覺得每個做排程、做訊息、做資料管線的人都該刻進骨子裡的一句話。太多人預設系統會「剛好跑一次」,然後在某次故障後,對著重複的帳單或漏掉的結算一臉錯愕。現實是:你必然要在跳過和重複之間選一個,那不如提早、清醒地選。而我的預設答案幾乎永遠是——把任務做成冪等,然後選「寧可重複」。因為冪等把一個「二選一的兩難」變成了「怎麼選都沒差」的舒適區,這是我看過投報率最高的一種防禦性設計,它跟資料管線的可重跑、訊息系統的去重,講的都是同一件事。
說到底,可靠的 cron 是共識的一道應用題
寫這篇最有意思的體會,是發現「可靠 cron」根本不是一個獨立的題目,而是分散式共識的應用。你以為在解排程,實際上在解的是「一群會掛的機器,如何對『這個任務跑過了沒』達成一致」——這跟選 leader、管分散式鎖是同一個問題的不同外衣。這也再次印證了我的一個信念:分散式系統的難題,翻來覆去其實就那幾個核心(共識、狀態、故障邊界),把核心啃透,遇到的各種「新問題」,多半只是老問題換了張臉。也因此,這種底層設施更該用被驗證過的現成方案,而不是每個團隊自己重造一次那道空窗。