啟用事件導向函式的重試功能 (第 1 代)
本文說明如何為事件驅動函式啟用重試功能。自動重試功能不適用於 HTTP 函式。
重試的語意
Cloud Run functions 會針對事件來源發出的每個事件,至少執行一次事件導向函式。根據預設,如果函式呼叫因錯誤而終止,系統不會再次呼叫函式,並會捨棄事件。在事件驅動函式中啟用重試功能後,Cloud Run 函式會重試失敗的函式叫用,直到成功完成或重試時間範圍到期為止。
重試期限為 7 天。Cloud Run 函式會使用指數輪詢策略重試新建立的事件驅動函式,輪詢時間會逐漸增加,介於 10 到 600 秒之間。這項政策適用於您首次部署的新函式。即使重新部署函式,這項異動也不會追溯套用至在本版本資訊所述變更生效前首次部署的現有函式。
如果函式未啟用重試功能 (預設為停用),函式一律會回報執行成功,且記錄中可能會顯示 200 OK
回應代碼。即使函式發生錯誤,也會發生這種情況。為清楚指出函式何時發生錯誤,請務必適當回報錯誤。
事件導向函式無法完成的原因
在極少數情況下,函式可能會因內部錯誤而提早結束,根據預設,函式可能會也可能不會自動重試。
更常見的情況是,事件驅動函式可能會因函式程式碼本身擲回的錯誤,而無法順利完成。可能原因包括:
- 函式包含錯誤且執行階段擲回例外狀況。
- 函式無法連上服務端點,或嘗試連線時逾時。
- 函式會刻意擲回例外狀況 (例如參數驗證失敗時)。
- Node.js 函式傳回遭拒絕的 Promise,或將非
null
值傳遞至回呼。
在上述任一情況下,函式預設會停止執行,並捨棄事件。如要在發生錯誤時重試函式,可以設定「retry on failure」屬性,變更預設重試政策。這會導致系統重複重試事件,直到函式順利完成或重試逾時為止。
啟用或停用重試功能
如要啟用或停用重試功能,可以使用 gcloud
指令列工具或 Google Cloud 主控台。根據預設,系統不會重試。
透過 gcloud
指令列工具設定重試
如要使用 gcloud
指令列工具啟用重試功能,請在部署函式時加入 --retry
標記:
gcloud functions deploy FUNCTION_NAME --retry FLAGS...
如要停用重試功能,請重新部署函式,但不要使用 --retry
旗標:
gcloud functions deploy FUNCTION_NAME FLAGS...
從控制台設定重試作業
如要建立新函式:
如要更新現有函式:
- 在「Cloud Run functions Overview」(Cloud Run 函式總覽) 頁面中,按一下要更新的函式名稱,開啟「Function details」(函式詳細資料) 畫面,然後從選單列選擇「Edit」(編輯),顯示「Trigger」(觸發條件) 窗格。
- 選取或取消選取「失敗時重試」核取方塊,即可啟用或停用重試功能。
最佳做法
本節將說明使用重試的最佳做法。
使用重試來處理暫時性錯誤
由於系統會持續重試函式,直到執行成功為止,因此啟用重試功能前,請先透過測試從程式碼中排除錯誤等永久性錯誤。重試最適合處理間歇性或暫時性故障,這類故障重試後很可能就能解決,例如服務端點不穩定或逾時。
設定結束條件以避免無限的重試循環
使用重試功能時,最佳做法是保護函式,避免持續迴圈。方法是在函式開始處理前,加入明確定義的結束條件。請注意,只有在函式順利啟動並能評估結束條件時,這項技術才有效。
簡單但有效的方法是捨棄時間戳記早於特定時間的事件。如果失敗情況持續發生或持續時間超出預期,這有助於避免過度執行作業。
例如,下面的程式碼片段會捨棄 10 秒之前的所有事件:
Node.js
Python
Go
Java
C#
using CloudNative.CloudEvents; using Google.Cloud.Functions.Framework; using Google.Events.Protobuf.Cloud.PubSub.V1; using Microsoft.Extensions.Logging; using System; using System.Threading; using System.Threading.Tasks; namespace TimeBoundedRetries; public class Function : ICloudEventFunction<MessagePublishedData> { private static readonly TimeSpan MaxEventAge = TimeSpan.FromSeconds(10); private readonly ILogger _logger; // Note: for additional testability, use an injectable clock abstraction. public Function(ILogger<Function> logger) => _logger = logger; public Task HandleAsync(CloudEvent cloudEvent, MessagePublishedData data, CancellationToken cancellationToken) { string textData = data.Message.TextData; DateTimeOffset utcNow = DateTimeOffset.UtcNow; // Every PubSub CloudEvent will contain a timestamp. DateTimeOffset timestamp = cloudEvent.Time.Value; DateTimeOffset expiry = timestamp + MaxEventAge; // Ignore events that are too old. if (utcNow > expiry) { _logger.LogInformation("Dropping PubSub message '{text}'", textData); return Task.CompletedTask; } // Process events that are recent enough. // If this processing throws an exception, the message will be retried until either // processing succeeds or the event becomes too old and is dropped by the code above. _logger.LogInformation("Processing PubSub message '{text}'", textData); return Task.CompletedTask; } }
Ruby
PHP
/** * This function shows an example method for avoiding infinite retries in * Google Cloud Functions. By default, functions configured to automatically * retry execution on failure will be retried indefinitely - causing an * infinite loop. To avoid this, we stop retrying executions (by not throwing * exceptions) for any events that are older than a predefined threshold. */ use Google\CloudFunctions\CloudEvent; function avoidInfiniteRetries(CloudEvent $event): void { $log = fopen(getenv('LOGGER_OUTPUT') ?: 'php://stderr', 'wb'); $eventId = $event->getId(); // The maximum age of events to process. $maxAge = 10; // 10 seconds // The age of the event being processed. $eventAge = time() - strtotime($event->getTime()); // Ignore events that are too old if ($eventAge > $maxAge) { fwrite($log, 'Dropping event ' . $eventId . ' with age ' . $eventAge . ' seconds' . PHP_EOL); return; } // Do what the function is supposed to do fwrite($log, 'Processing event: ' . $eventId . ' with age ' . $eventAge . ' seconds' . PHP_EOL); // infinite_retries failed function executions $failed = true; if ($failed) { throw new Exception('Event ' . $eventId . ' failed; retrying...'); } }
區分可重試的函式和嚴重錯誤
如果函式已啟用重試功能,任何未處理的錯誤都會觸發重試。 請確保程式碼會擷取不應導致重試的任何錯誤。
Node.js
Python
Go
Java
C#
using CloudNative.CloudEvents; using Google.Cloud.Functions.Framework; using Google.Events.Protobuf.Cloud.PubSub.V1; using Microsoft.Extensions.Logging; using System; using System.Text.Json; using System.Threading; using System.Threading.Tasks; namespace Retry; public class Function : ICloudEventFunction<MessagePublishedData> { private readonly ILogger _logger; public Function(ILogger<Function> logger) => _logger = logger; public Task HandleAsync(CloudEvent cloudEvent, MessagePublishedData data, CancellationToken cancellationToken) { bool retry = false; string text = data.Message?.TextData; // Get the value of the "retry" JSON parameter, if one exists. if (!string.IsNullOrEmpty(text)) { JsonElement element = JsonSerializer.Deserialize<JsonElement>(data.Message.TextData); retry = element.TryGetProperty("retry", out var property) && property.ValueKind == JsonValueKind.True; } // Throwing an exception causes the execution to be retried. if (retry) { throw new InvalidOperationException("Retrying..."); } else { _logger.LogInformation("Not retrying..."); } return Task.CompletedTask; } }
Ruby
PHP
use Google\CloudFunctions\CloudEvent; function tipsRetry(CloudEvent $event): void { $cloudEventData = $event->getData(); $pubSubData = $cloudEventData['message']['data']; $json = json_decode(base64_decode($pubSubData), true); // Determine whether to retry the invocation based on a parameter $tryAgain = $json['some_parameter']; if ($tryAgain) { /** * Functions with automatic retries enabled should throw exceptions to * indicate intermittent failures that a retry might fix. In this * case, a thrown exception will cause the original function * invocation to be re-sent. */ throw new Exception('Intermittent failure occurred; retrying...'); } /** * If a function with retries enabled encounters a non-retriable * failure, it should return *without* throwing an exception. */ $log = fopen(getenv('LOGGER_OUTPUT') ?: 'php://stderr', 'wb'); fwrite($log, 'Not retrying' . PHP_EOL); }
讓可重試的事件導向函式具備等冪性
可重試的事件導向函式必須是等冪函式。以下是讓這類函式具備等冪性的幾項一般準則:
- 許多外部 API (例如 Stripe) 都允許您以參數形式提供冪等金鑰。如果您使用這類 API,應將事件 ID 做為等冪鍵。
- 冪等性與「至少一次」遞送機制搭配使用效果良好,因為這樣重試作業才安全。因此,撰寫可靠程式碼的一般最佳做法,是將等冪性與重試機制結合。
- 請確保您的程式碼為內部冪等。例如:
- 請確保異動可發生多次,但結果不會改變。
- 在變更狀態之前,查詢交易中的資料庫狀態。
- 確保所有連帶影響本身也是冪等的。
- 在函式外部強制執行交易檢查,與程式碼無關。舉例來說,您可以將狀態保留在某個位置,記錄特定事件 ID 是否已處理完畢。
- 請在頻外處理重複的函式呼叫。舉例來說,您可以另外建立清除程序,在重複的函式呼叫後執行清除作業。
設定重試政策
視函式需求而定,您可能需要直接設定重試政策。這樣一來,您就能設定下列任意組合:
- 將重試時間從 7 天縮短至 10 分鐘。
- 變更指數輪詢重試策略的最短和最長輪詢時間。
- 將重試策略變更為立即重試。
- 設定無效信件主題。
- 設定傳送嘗試次數上限和下限。
如要設定重試政策,請按照下列步驟操作:
- 編寫 HTTP 函式。
- 使用 Pub/Sub API 建立 Pub/Sub 訂閱項目,並將函式的網址指定為目標。
如要進一步瞭解如何直接設定 Pub/Sub,請參閱 Pub/Sub 說明文件中的失敗處理方式。
後續步驟
- 部署 Cloud Run 函式。
- 呼叫 Pub/Sub 觸發條件函式。
- 呼叫 Cloud Storage 觸發條件函式。
- 搭配 Pub/Sub 使用 Cloud Run 函式教學課程。
- Cloud Run functions with Cloud Storage 教學課程。