有些後端工作很奇怪——沒有人去點它,它自己時間到了就會跑

每天半夜 3 點跑前一天的對帳、每月 1 號寄帳單、每小時清一次過期的 session、每 5 分鐘去第三方拉一次匯率。這些都沒有使用者按按鈕觸發,是時間到了系統自己動。這類工作,就叫排程任務(scheduled job),最經典的工具叫 cron。

它跟我們前面講的 Queue 任務長得有點像(都是背景在跑),但觸發的方式完全不同——Queue 是「有事發生才丟任務」(有人下單 → 寄信),排程是「時間到了就跑任務」(半夜 3 點 → 對帳)。一個被事件推、一個被時鐘推。


為什麼需要它

因為有一整類工作,本質上就是「定時 / 定期」的,硬要靠使用者觸發反而很怪。

對帳這種事,你總不能等使用者來幫你按「開始對帳」吧?它就是該每天固定時間自己跑。清過期資料也是,沒有任何「事件」會通知你「欸有資料過期了」——是你得自己定期去掃。報表更是,老闆要的是「每月 1 號信箱裡有報表」,不是「我手動去產一份」。

這些工作的共同點:跟某個時間點 / 時間週期綁定,而不是跟某個動作綁定。 這就是排程存在的理由。


「那我寫個 while 迴圈 sleep 不就好了」

又來了,土法煉鋼直覺。要每小時跑一次,那我寫個無窮迴圈、做完睡一小時不就好了:

# 土法煉鋼版
while True:
    do_hourly_job()
    time.sleep(3600)  # 睡一小時

一樣,能動,但問題一堆:

它跟「整點」對不齊。 你要的是「每小時整點跑」,但這個迴圈是「做完一次、睡一小時、再做」——job 跑了 5 分鐘,下次就變成 1 小時 5 分後跑,時間會一直往後漂。

程式一重啟,排程就重置。 你部署一次、或它掛一次重開,這個計時就從頭算。「半夜 3 點」這種絕對時間點,它根本對不準。

它睡著的時候啥都做不了。 這個 process 為了等下一次排程,整整一小時卡在 sleep,不能拿去做別的事。

所以才有 cron 這種專門的東西——你只要用一行 0 3 * * * 告訴它「每天 3 點」,它就會在每天 3 點整準時幫你跑,不漂移、重啟也記得。現代框架(Celery Beat、BullMQ 的 repeatable job、K8s 的 CronJob)做的都是同一件事:幫你管「什麼時間該跑什麼」,你只要寫那個 job 本身。


真正會讓你半夜被叫起來的坑:跑了兩次

前面都還算簡單。排程真正的魔王,在你有不只一台機器的時候才現身。

想像你的後端為了高可用開了三台 instance,每台都裝了「每天 3 點對帳」這個排程。半夜 3 點一到——三台同時醒過來,各跑了一次對帳

結果就是對帳跑了三遍。如果這個 job 是「發薪水」「結算分潤」「扣款」這種,那可不是鬧著玩的,是真的會把錢算錯、發三次。

這就是分散式排程的核心難題:怎麼保證一個排程,就算有 N 台機器都裝了它,每次也只有一台真的去跑?

最常見的解法是分散式鎖(還記得 Redis 那把瑞士刀嗎):三台機器 3 點都醒來,但動手前先去搶同一把鎖,搶到的那台才跑,沒搶到的摸摸鼻子回去睡。這樣不管你開幾台,同一個時間點都只會有一台真的執行。再進階一點的做法是 leader election——選一台老大專門負責跑排程,這些就是之後 27-distributed-scheduling 要深入的。

個人經驗:我們現在的排程主要在跑報表。先不管報表本身大不大,我遇到比較刁鑽的問題其實是排程之間會互相踩到資源——具體說,是某次 IO 被前一個排程吃滿了,下一個排程時間到了照樣硬啟動,結果兩個一起壞掉

最麻煩的是當下根本不知道怎麼除錯,因為表面看到的只是「兩個排程都失敗」,但真正的根因是 IO 在重疊的時間被榨乾,而我們的 log 又沒把這層資源狀況記下來,根本追不出兇手。這件事讓我學到——排程除了「會不會重複跑」之外,還有一個超容易被忽略的雷:排程之間的資源排擠。你以為它們各跑各的,其實它們在搶同一份 IO / CPU;而且真的出事時,你的 log 要記得夠細,才有辦法回頭指認是誰把資源吃光的。


接下來往哪走

這篇先讓你認得「排程任務」這種被時鐘推著跑的工作,還有它在分散式下最大的雷。再往下:

  • 多台機器怎麼確保只跑一次(鎖 / leader election) → 本章 27-distributed-scheduling(規劃中)
  • 排程跟一般背景任務的關係 → Background Jobs(框架視角)
  • 被事件推的那種背景任務先補 → Queue 是什麼

反思

排程任務的觀念其實很單純——有些事該被時間推著跑,不是被使用者推著跑。難的從來不是「怎麼定時」,cron 一行就解決了。

難的是這兩題,每次寫排程都要先想:

  1. 這個 job 跑了兩次會怎樣?會算錯錢、發兩次 → 你一定要處理「多台只跑一次」。
  2. 上次沒跑成功(機器剛好掛了),這次要不要補跑?補的話,補一次還是把漏掉的全補上?

第一題沒想清楚,就是在等某個半夜,三台機器一起把同一件事做三遍,然後你被電話叫醒。