如何在網頁應用程式中運行背景工作:佇列、排程器與 Cron
要在網頁應用程式中運行背景工作,首先要把工作分為兩類。觸發式工作(發送歡迎電郵、處理上載的檔案)可以儲存到工作佇列中,通常就是資料庫中的一張資料表,然後由具備重試功能的工作程序處理。排程工作(每晚的報告、每 15 分鐘一次的提醒)則需要按時間表觸發的機制:由主機的 cron 功能提供,或由外部排程器呼叫應用程式中受保護的端點。務必讓工作可以安全地重複執行,記錄失敗的情況,並且不要依賴一個可能休眠或重新啟動的伺服器內的計時器。
本指南會講解各種選項、它們各自適用的情況,以及如何借助 AI 應用程式建置工具設定,包括在可以縮減至零的主機上的做法。
什麼算是背景工作
任何緩慢、不可靠,或不需要即時回應給用戶的工作:
- 發送電郵、通知及收據
- 調整圖片大小、轉碼影片、生成 PDF
- 呼叫緩慢的第三方 API,包括為較長的任務呼叫 AI 模型
- 從其他系統匯入或同步資料
- 每晚的清理、報告及提醒
如果把這些工作放在請求之內進行,頁面會變慢,而且容易出錯:假如電郵服務供應商出現故障,用戶的註冊就會失敗。把它們移到背景執行,用戶可以立即獲得回應,而工作失敗時也可以重試。
各種選項
| 選項 | 運作方式 | 優點 | 缺點 |
|---|---|---|---|
| 同一程序內「發射後不管」 | 先回應,然後在伺服器中繼續處理 | 不需要設定 | 伺服器停止時會遺失;沒有重試 |
程序內計時器(setInterval、setTimeout) | 在運行中的伺服器內設一個計時器 | 非常簡單 | 休眠時不會執行;多個副本時會重複 |
| 資料庫佇列 | 工作以資料列形式儲存;工作程序認領並執行 | 使用你現有的資料庫;持久可靠;容易查看 | 你需要自行建置重試與鎖定機制;需要有東西來運行工作程序 |
| 專用佇列(基於 Redis 或託管服務) | 佇列服務加上工作程序 | 重試與排程功能成熟 | 需要另外運行及付費的服務 |
| 託管的工作或工作流程服務 | 第三方服務按步驟呼叫你的端點,並提供重試及排程 | 排程和重試由服務代為處理 | 依賴供應商;需要另一個帳戶及金鑰 |
| 外部排程器呼叫端點 | 其他地方的計時器按時發送 HTTP 請求 | 適用於任何託管平台,包括可縮減至零的平台 | 有最短間隔及延遲;需要保護端點 |
| 主機內建的 cron | 平台按時間表運行指令 | 在支援的地方非常簡單 | 並非每個主機都提供 |
沒有單一的正確答案。小型應用程式通常只需要一個資料庫佇列,再加上一個外部排程器。
為何可縮減至零的主機會改變情況
許多現代的主機在沒有人使用應用程式時會讓它休眠,並在下次收到請求時喚醒。這樣可以節省費用,但休眠中的伺服器不會運行任何程式碼:計時器不會觸發,工作程序也不會輪詢。在這類主機上,排程工作必須由一個進入的請求開始,而最簡單的請求來源就是外部排程器。每次呼叫都會喚醒應用程式(通常連同其資料庫),因此每分鐘呼叫一次的成本,會高於每 15 分鐘呼叫一次。請選擇能夠滿足需要的最長間隔。
模式一:資料庫工作佇列
適用於觸發式工作:
- 建立一張
jobs資料表,包含:類型、承載內容(JSON)、狀態(pending、running、done、failed)、嘗試次數、run_after、last_error 及時間戳記。 - 當某件事情發生時,在引發該變動的同一個交易中插入一筆工作資料列,這樣兩者會一同成功或一同失敗。
- 工作程序選取到期的工作,將其標記為
running,執行工作後再標記為done。 - 如果失敗,增加
attempts,把run_after設定在較後的時間(例如 1、5、30 分鐘後),並儲存錯誤。達到最大次數後,將其標記為failed,並通知相關人員。
如果有多個工作程序可能同時運行,工作必須以安全的方式被認領。在 Postgres 中,SELECT ... FOR UPDATE SKIP LOCKED 讓每個工作程序認領不同的資料列,而不會互相衝突。
那麼由什麼來運行工作程序呢?在一直運行的伺服器上,可以是獨立程序中的循環。在可以縮減至零的主機上,模式二中排程的端點,可以在每次呼叫時處理一批到期的工作。
模式二:由外部排程器呼叫你的應用程式
適用於排程工作:
- 新增一個端點,例如
POST /api/cron/send-reminders。 - 保護它:要求在標頭中附上一個機密權杖,並在雙方都將它儲存為機密;拒絕其他任何請求。
- 令它具備冪等性:在同一分鐘內執行兩次,不可以發出兩次提醒。記錄已發送的內容。
- 讓每次呼叫都簡短:處理一批(例如 100 項),餘下的交由下一次呼叫處理。
- 將排程器指向這個端點。
排程器的選擇:
- GitHub Actions 排定工作流程。 在 GitHub 的使用限額內免費。根據 GitHub 於 2026 年 9 月的文件,最短間隔為每 5 分鐘一次;在繁忙時段(尤其是每小時開始時)執行可能會延遲;排程預設使用 UTC;而公開儲存庫中的排定工作流程,若超過 60 天沒有儲存庫活動,便會被停用。
- 託管的 cron 服務。 有很多服務的唯一功能,就是按時間表呼叫一個網址。在依賴它們之前,請檢查其可靠性、最短間隔及警示功能。
- 你的雲端服務供應商的排程器。 如果你已經使用大型雲端服務供應商,它通常會提供可以呼叫 HTTPS 端點的託管排程器。
- 託管的工作或工作流程服務。 這些服務結合了排程、佇列及重試,並為每個步驟呼叫你的端點。請在各供應商的網站上比較價格及限制。
模式三:交由服務供應商處理
有時候最好的背景工作,根本就不需要自己做:
- 付款。 Stripe 會為失敗的訂閱付款重試,並發送 Webhook;你只需回應,而不必輪詢。參閱什麼是 Webhook。
- 電郵。 當你把郵件交給服務供應商後,供應商會排隊並重試傳送。
- 大量 AI 工作。 OpenAI 及 Anthropic 為不急切的請求提供批次 API,你可以提交請求,之後再收取結果。
給 AI 建置工具的提示
用於佇列:
在我們的資料庫中加入一個背景工作佇列,使用 jobs 資料表(類型、承載內容 JSON、狀態、嘗試次數、run_after、last_error)。當用戶註冊時,在同一個交易中插入一個 send_welcome_email 工作,而不是直接在請求中發送。新增 processJobs(limit) 函式,使用 SELECT ... FOR UPDATE SKIP LOCKED 認領到期的工作並執行;失敗時在 1、5、30 分鐘後重試,嘗試 4 次後標記為 failed。
用於排程觸發:
新增 POST /api/cron/tick,以機密 CRON_SECRET 作為 bearer 權杖保護;拒絕任何沒有該權杖的請求。每次呼叫執行 processJobs(100),並發送未來 24 小時內到期、且尚未發送的預約提醒,同時記錄已發送的提醒,確保不會重複發送。以 JSON 摘要回傳執行的內容。然後撰寫一個 GitHub Actions 工作流程,每 15 分鐘呼叫這個端點一次,並從儲存庫的機密中讀取網址及權杖。
常見錯誤
- 依賴
setInterval。 伺服器休眠或重新啟動時它會悄悄停止,而兩個副本同時運行時則會重複執行。 - 未受保護的 cron 端點。 任何找到它的人都可以觸發你的工作,或令你的電郵費用上升。
- 非冪等的工作。 重試及重複的排程器呼叫一定會發生。設計時就要把它們考慮在內。
- 無法得知失敗情況。 一項工作在一星期內靜默地失敗,比大聲地失敗更糟糕。記錄錯誤,並在重複失敗時發出警示。
- 在單一請求中進行長時間的工作。 請求會逾時。應分批處理。
- 排程過於頻密。 在可縮減至零的主機上,每次呼叫都會喚醒並計費應用程式。間隔應配合實際需要。
- 時區。 「早上 9 時」對你的排程器(通常是 UTC)與對你的用戶,意思可能不同。應以 UTC 儲存時間,並按用戶換算。
- 把機密放在工作流程檔案中。 權杖應保存在排程器的機密儲存中。參閱如何妥善保管 API 金鑰。
檢查清單
- 慢速的工作已移出用戶請求
- 工作以持久方式儲存,而不只是存在記憶體中
- 重試時間間隔逐步增加,並設有最大次數
- 每項工作都可以安全地重複執行
- 排程端點以機密權杖保護
- 每次執行只處理有限的一批
- 失敗的工作清晰可見,並設有警示
- 排程間隔的選擇已考慮託管成本
- 時間以 UTC 儲存
Mythex 的背景工作
Mythex 的文件並未描述排程或 cron 工作,而已發佈的應用程式及其資料庫在閒置時會休眠,並在下次請求時醒來(參閱託管限制)。因此,不要依賴應用程式內的計時器。可行的做法就是上面所述的方法:請 agent 建立一張 jobs 資料表和一個以權杖保護的端點,然後把外部排程器指向你已發佈的網址。把權杖儲存為專案機密,並留意每次喚醒都會為應用程式及資料庫醒着的時間消耗點數,因此請選擇合理的間隔。如果你的應用程式需要為此提供 API 後端,REST API 後端 食譜是一個起點;如何建立 REST API 則說明如何設計端點。如果你的產品核心是常駐的工作程序或機械人,一個專為持續運行的程序而設計的主機可能更適合你。
常見問題
什麼是背景工作?
背景工作是指應用程式在用戶等待的請求以外進行的工作,例如發送電郵、調整圖片大小、生成報告或同步資料。用戶可以即時獲得回應,而工作則會另外執行,失敗時還可以重試。
如果託管平台沒有 cron,我怎樣運行排程任務?
新增一個受保護的端點來執行工作,然後讓外部排程器按時間表呼叫它。可選的方案包括排定的 GitHub Actions 工作流程、託管的 cron 服務,或你的雲端服務供應商提供的排程器。請以機密權杖保護該端點。
我可以用 setInterval 或 setTimeout 執行排程工作嗎?
只適用於不重要的小型任務,而且伺服器必須一直運行。如果伺服器重新啟動、縮減至零,或同時運行多個副本,計時器就會遺失,或重複執行。排程的工作應儲存在資料庫中,或改用排程器。
Mythex 支援 cron 工作嗎?
Mythex 的文件並未描述排程或 cron 工作,而已發佈的應用程式在閒置時會休眠。較可靠的做法是讓外部排程器呼叫應用程式中受保護的端點,這會喚醒應用程式並執行工作。