重試非同步函式

本文說明如何要求非同步 (非 HTTPS) 背景函式在失敗時重試。

事件導向函式無法完成的原因

在極少數情況下,函式可能會因內部錯誤而提早結束,根據預設,函式可能會也可能不會自動重試。

更常見的情況是,事件驅動函式可能會因函式程式碼本身擲回的錯誤,而無法順利完成。可能原因包括:

  • 函式包含錯誤且執行階段擲回例外狀況。
  • 函式無法連上服務端點,或嘗試連線時逾時。
  • 函式會刻意擲回例外狀況 (例如參數驗證失敗時)。
  • Node.js 函式傳回遭拒絕的 Promise,或將非 null 值傳遞至回呼。

在上述任一情況下,函式都會停止執行並傳回錯誤。產生訊息的事件觸發程序具有重試政策,您可以自訂這些政策,以符合函式需求。

重試的語意

Cloud Functions 會針對事件來源發出的每個事件,至少執行一次事件驅動函式。設定重試的方式取決於函式的建立方式:

  • Google Cloud 控制台或使用 Cloud Run Admin API 建立的函式,需要另外建立及管理事件觸發程序。觸發條件具有預設的重試行為,您可以根據函式需求自訂。
  • 使用 Cloud Functions v2 API 建立的函式會隱含建立必要的事件觸發條件,例如 Pub/Sub 主題或 Eventarc 觸發條件。根據預設,系統會停用這些觸發條件的重試機制,但您可以使用 Cloud Functions v2 API 重新啟用。

使用 Cloud Run 建立的事件導向函式

在 Google Cloud 控制台或使用 Cloud Run Admin API 建立的函式,需要另外建立及管理事件觸發程序。強烈建議您查看每種觸發條件類型的預設行為:

使用 Cloud Functions v2 API 建立的事件導向函式

使用 Cloud Functions v2 API 建立的函式 (例如使用 Cloud Functions gcloud CLI、REST API 或 Terraform),會代表您建立及管理事件觸發條件。根據預設,如果函式呼叫因錯誤而終止,系統不會再次呼叫函式,並會捨棄事件。啟用事件驅動函式的重試功能後,Cloud Functions會重試失敗的函式呼叫,直到成功完成或重試時間範圍到期為止。

如果函式未啟用重試功能 (預設為停用),函式一律會回報執行成功,且記錄中可能會顯示 200 OK 回應代碼。即使函式發生錯誤,也會發生這種情況。為清楚指出函式何時發生錯誤,請務必適當回報錯誤

啟用或停用重試功能

透過函式程式碼設定重試

使用 Cloud Functions for Firebase,您可以在函式的程式碼中啟用重試功能。如要為背景函式 (例如 functions.foo.onBar(myHandler);) 執行這項操作,請使用 runWith 並設定失敗政策:

functions.runWith({failurePolicy: true}).foo.onBar(myHandler);

如圖所示設定 true,即可設定函式在失敗時重試。

重試時間範圍

如果是第 2 代函式,這個重試視窗會在 24 小時後到期。第 1 代函式會在 7 天後過期。Cloud Functions 會使用指數輪詢策略重試新建立的事件驅動函式,輪詢時間會介於 10 到 600 秒之間。這項政策適用於您首次部署的新函式。即使重新部署函式,這項異動也不會追溯套用至在本版本資訊所述異動生效前首次部署的現有函式。

最佳做法

本節將說明使用重試的最佳做法。

使用重試來處理暫時性錯誤

由於系統會持續重試函式,直到執行成功為止,因此啟用重試功能前,請先透過測試從程式碼中排除錯誤等永久性錯誤。重試最適合處理間歇性或暫時性故障,這類故障重試後很可能就能解決,例如服務端點不穩定或逾時。

設定結束條件以避免無限的重試循環

使用重試功能時,最佳做法是保護函式,避免持續迴圈。方法是在函式開始處理前,加入明確定義的結束條件。請注意,只有在函式順利啟動並能評估結束條件時,這項技術才有效。

簡單但有效的方法是捨棄時間戳記早於特定時間的事件。如果失敗情況持續發生或持續時間超出預期,這有助於避免過度執行作業。

例如,下面的程式碼片段會捨棄 10 秒之前的所有事件:

const eventAgeMs = Date.now() - Date.parse(event.timestamp);
const eventMaxAgeMs = 10000;
if (eventAgeMs > eventMaxAgeMs) {
  console.log(`Dropping event ${event} with age[ms]: ${eventAgeMs}`);
  callback();
  return;
}

搭配 Promise 使用 catch

如果函式已啟用重試功能,任何未處理的錯誤都會觸發重試。 請確保程式碼會擷取不應導致重試的任何錯誤。

以下舉例說明如何處理:

return doFooAsync().catch((err) => {
    if (isFatal(err)) {
        console.error(`Fatal error ${err}`);
    }
    return Promise.reject(err);
});

讓可重試的事件導向函式具備等冪性

可重試的事件導向函式必須是等冪函式。以下是讓這類函式具備等冪性的幾項一般準則:

  • 許多外部 API (例如 Stripe) 都允許您以參數形式提供冪等金鑰。如果您使用這類 API,應將事件 ID 做為等冪鍵。
  • 冪等性與「至少一次」遞送機制搭配使用效果良好,因為這樣重試作業才安全。因此,撰寫可靠程式碼的一般最佳做法,是將等冪性與重試機制結合。
  • 請確保您的程式碼為內部冪等。例如:
    • 請確保異動可發生多次,但結果不會改變。
    • 在變更狀態之前,查詢交易中的資料庫狀態。
    • 確保所有連帶影響本身也是冪等的。
  • 在函式外部強制執行交易檢查,與程式碼無關。舉例來說,您可以將狀態保留在某個位置,記錄特定事件 ID 是否已處理完畢。
  • 請在頻外處理重複的函式呼叫。舉例來說,您可以另外建立清除程序,在重複的函式呼叫後執行清除作業。

設定重試政策

視函式需求而定,您可能需要直接設定重試政策。這樣一來,您就能設定下列任意組合:

  • 將重試時間從 7 天縮短至 10 分鐘。
  • 變更指數輪詢重試策略的最短和最長輪詢時間。
  • 將重試策略變更為立即重試。
  • 設定無效信件主題
  • 設定傳送嘗試次數上限和下限。

如要設定重試政策,請按照下列步驟操作:

  1. 編寫 HTTP 函式。
  2. 使用 Pub/Sub API 建立 Pub/Sub 訂閱項目,並將函式的網址指定為目標。

如要進一步瞭解如何直接設定 Pub/Sub,請參閱Pub/Sub 說明文件,瞭解如何處理失敗情形