開發與維運

基於事件驅動應用的福音-函數計算異步調用目標

事件驅動帶來的挑戰及功能背景

函數計算異步調用

函數計算非常適合構建事件驅動的應用。這類應用往往通過不同的事件觸發器(如 OSS 觸發器、時間觸發器、消息隊列觸發器等)來觸發一次請求,並調用具體的微服務模塊來處理不同邏輯。函數計算按請求量計費、高度彈性的特性以及原生 Serverless 化的架構能夠很好的為業務執行提供算力並控制成本。作為各類雲服務的黏合劑,用戶在函數計算中也能夠很容易的使用各類雲服務作為 BaaS 層來實現存儲、數據持久化、記錄日誌等需求。

在我們的實際用戶場景中,有很大一類是通過觸發器進行數據處理(比如圖片處理、視頻轉碼)任務。這類任務實時性不高,但希望能夠保證成功率及自動化程度,降低人為介入次數。當然,在實際的使用的過程中,用戶有時會面臨以下幾個方面的挑戰。

異步調用面臨的挑戰

異步事件的可觀測性

基於事件驅動的應用自然也存在各類挑戰,其中一點就是整個服務鏈路的可觀測性。不同於單體或模塊化的應用,一個事件在微服務化(或 Serverless 化)的應用中是非常難追尋的。整個鏈路中存在各類用於解耦的消息隊列、存儲等,往往很多鏈路上同時存在多個異步調用。這類異步調用為問題排查帶來了比較大的挑戰。”我從哪裡來“、”我到哪裡去“成為了微服務間消息傳遞需要額外關注的地方。

同事件驅動的應用一樣,用戶要想準確知道一個異步調用消息到底是否被成功消費,失敗是因為丟失了還是其他原因要花費很多周折。比如某個用戶的任務(函數)偶爾會執行超時(數據量導致),但是用戶無法感知也無法自動化處理這類超時失敗的場景。往往都是很久之後發現數據缺失,之後再進行手動補償及修復後重新觸發函數。目前解決這類問題比較常見的一些做法就是在函數中打印日誌,或者根據調用結果在函數中調用一些其他服務(如 MNS)來通知執行情況。這類解法不但增加了業務無關的工作量,也難以保證數據不被遺漏(比如錯誤出現在打日誌前、其他服務暫時不可用等)。

如果我們能夠提供一些指標,並在用戶函數執行成功/失敗時調用其他服務通知執行結果,那麼用戶將很容易的使用該功能進行執行結果的自動化處理。

事件驅動閉環

在另外的一些場景下,用戶不僅希望函數計算能夠作為事件的消費者,也希望函數能夠作為事件的生產者。比如,函數開發者希望能夠將用戶的請求經過簡單處理直接轉為事件,並觸發後續流程;或者希望由某個函數定時生產一些事件來觸發一系列處理流程。

一個比較典型的場景是使用 OSS 觸發器調用函數來處理用戶上傳的圖片。但用戶如果一次上傳的圖片很多,可能造成函數執行超時。這類問題的解決思路就是將函數從消費者的角度轉變為生產者,將一次處理的數據劃分為多個可穩定執行完成的大小分片。比如 OSS 觸發函數後,這個函數不進行處理,而是將數據分類分片,生成一批事件交由實際處理函數來進行處理。使用 Serverless 工作流是很好的解決方案,但是如果用戶流程邏輯非常簡單,又希望快速設計服務原型,如果我們能夠提供一種在函數執行完成後可靠的調用其他服務的功能,用戶的接入成本將大大減少。

異步調用策略的可定製性

為保證用戶函數因偶發的失敗導致執行成功率下降,函數計算默認會為用戶異步執行的函數錯誤進行 3 次重試。但在一些情況下,用戶可能不希望進行重試,或希望進行更多的重試。比如:

  1. 用戶某個任務(函數)經常執行超時(與輸入的待處理數據大小有關),如果第一次超時,則重試也會超時。此時用戶不希望執行出錯就進行重試而造成資源浪費;
  2. 函數可重入,但部分場景失敗率較高,用戶希望能夠增加重試次數儘可能增加異步調用成功率;
  3. 用戶某個高峰時間段因為系統 bug,發出了過多的異步調用請求,結果導致業務正常的異步請求受到阻塞,系統整體延時及失敗率增加。

此時如果能提供一種方式,用戶可以配置這個重試次數及消息超時丟棄的策略,那麼可以很好的解決上述問題。

異步調用配置

上述挑戰的解決方案

函數計算最近上線的異步目標配置功能為上述場景提供瞭解決方案。

異步調用後執行目標

通過設置異步調用的 Destination,函數計算可以根據每次異步請求的執行情況(成功或失敗)來調用不同的”目標“。目前支持的目標有函數計算和消息服務(MNS 主題/隊列)。函數計算將會收集異步調用的一些基本情況(如請求 Payload、函數異常錯誤碼、函數返回、重試次數等)併發往目標服務,用戶可根據這些數據來執行具體的後續策略。

比如,用戶可以設置異步函數的成功/失敗調用目標。當異步函數執行結果符合設置時,函數計算將確保調用異步目標,以便執行後續的處理邏輯。用戶也可以使用該工能將函數變為生產者,如劃分數據集並串行的調用一系列函數按序執行。

配置異步調用的策略(重試次數及消息存活時間)

為了提供更加靈活的異步執行自定義策略,函數計算同時開放了異步配置的消息最大存活時間(MaxAsyncEventAgeInSeconds)以及最大重試次數(MaxAsyncRetryAttemps),用戶可以通過自定義以上參數對異步調用進行配置及設置。

異步調用配置的使用方式

目前,我們支持通過控制檯、SDK 或 API 來配置異步調用目標。配置的結構如下:

{
  "DestinationConfig": {
        "OnSuccess": { 
          "Destination": "acs:fc:{region}:{account}:services/{service_name}.{Qualifier}/functions/{function_name}}",
        },
        "OnFailure": {
          "Destination": "acs:mns:{region}:{account}:/queues/{queue_name}/messages"
        }
  }
  "MaxAsyncEventAgeInSeconds": 100,
  "MaxAsyncRetryAttemps": 1
}

下面對設置的異步調用數據結構進行解釋:

DestinationConfig

OnSuccess

如果配置了 Destination 的 OnSuccess 目標,函數計算將在執行成功後調用配置的目標服務。如果配置目標為函數計算,您甚至可以實現一個小型的工作流來完成您的任務,比如將一些處理結果通過 destination 傳遞到另外一個函數,避免諸如單個函數的最大 10 min 執行時長等限制。

OnFailure

如果配置了 Destination 的 OnFailure 目標,函數計算將在執行失敗後調用配置的目標服務。失敗情況包括系統的一些內部錯誤、函數中的各類異常。配置失敗後的調用目標可以降低異步函數開發過程中的調試難度、提高線上運行的函數的可觀測性。您可以通過配置失敗目標來進行一些諸如資源回收、監控報警的需求。

調用 Destination 時傳遞的數據

當調用結果符合異步目標配置時,我們將調用對應服務,併發送相應數據。發送的數據內容如下:

{
    "timestamp": "2020-08-20T12:00:00.000Z",
    "requestContext": {
        "requestId": "xxx",
        "functionArn": "acs:fc:::services/{serviceName}/functions/{functionName}",
        "condition": "FunctionResourceExhausted", 
        "approximateInvokeCount": 3
    },
    "requestPayload": "",
    "responseContext": {
        "statusCode": 200,
        "functionError": ""
    },
    "responsePayload": ""
}

我們傳遞的數據包括了調用基本信息(requestContext:異步請求ID、請求的函數、請求結果以及異步調用次數)、異步調用輸入 (requestPayload),調用結果信息(responseContext:調用狀態碼以及函數錯誤)以及異步調用函數輸出(responsePayload)。這些數據覆蓋了異步函數從輸入到執行輸出的大部分信息,可以在 Destination 目標中使用。在使用 MNS 作為目標時我們會將上述數據作為消息寫入 MNS 隊列/主題,在使用 FC 作為目標時我們會將上述數據作為 Event 調用函數。

MaxAsyncRetryAttempts

該參數設置了出錯最大重試次數。當您的函數不是可重入的,或不希望在執行錯誤時被多次調用,可以通過設置該參數進行重試限制。

MaxAsyncEventAgeInSeconds

該參數可以設置異步消息最大存活時長。當您的異步調用任務較多,可能會出現消息積壓時,為了保證後面的高優先級任務不被餓死,可以設置消息的最大存活時長。當消息超過該時長後,將不觸發函數並被直接丟棄。配置該參數後,當 獲取消息的時間 - 異步消息入隊時間 > MaxAsyncEventAgeInSeconds 時,該消息將直接被拋棄,並記錄 AsyncEventExpiredDropped 指標,進而保證後面的正常請求不被阻塞。

異步目標配置更為詳細說明及使用文檔,見 文檔

示例項目

為方便理解,我們創建了一個示例項目展示如何在實際業務中使用異步配置功能。該項目配置了異步調用目標為 mns 隊列,實現了異步消息死信隊列的功能,方便業務人員感知異步函數執行失敗的消息並進行後續處理。具體的項目代碼見:

https://github.com/awesome-fc/async-invocation-dlq

總結

函數的異步調用方式有利於拉平負載,提高任務的成功率,但也帶來了一系列挑戰。我們結合用戶的實際場景,可總結為下述幾類:

  1. 用戶希望根據不同的業務場景,對於異步執行失敗的錯誤進行不同的重試策略;
  2. 用戶希望異步消息的存活時間有一個有效期。如果消息積壓,希望優先處理後面更為重要的消息,而不是對所有的消息不加區別的按序處理;
  3. 用戶希望函數計算能夠提供一種方式,即在函數執行後系統幫忙執行一些調用其他服務的邏輯,以便進行錯誤處理、資源回收或生產後續事件。

為解決這些問題,函數計算提供了異步配置功能,用戶可以配置函數的異步執行策略並配置異步執行後的調用目標。該功能提高了函數的異步執行任務的可觀測性,使得函數計算也可以成為事件驅動應用的生產者。異步調用配置不但擴展了用戶使用函數計算連接不同雲服務的方式,也簡化了函數在處理這類需求的邏輯,方便用戶更專注於具體的業務。

Leave a Reply

Your email address will not be published. Required fields are marked *