本文說明設計、實作、測試及部署 Cloud Run 函式的最佳做法。
正確性
本節說明設計和實作 Cloud Run 函式的一般最佳做法。
編寫冪等函式
即使多次呼叫函式,這些函式也應該產生相同結果。這樣一來,如果先前的叫用在程式碼中失敗,您就可以重試叫用。詳情請參閱「重試事件驅動函式」。
確保 HTTP 函式傳送 HTTP 回應
如果函式是 HTTP 觸發,請記得傳送 HTTP 回應,如下所示。否則,函式可能會一直執行,直到逾時為止。在這種情況下,您將為整個逾時時間付費。逾時也可能導致無法預測的行為,或在後續叫用時造成無法預測的行為或額外延遲。
Node.js
Python
Go
Java
C#
Ruby
PHP
請勿啟動背景活動
背景活動是指函式終止後發生的任何活動。函式傳回或以其他方式表示完成後,函式叫用就會結束,例如在 Node.js 事件驅動函式中呼叫 callback
引數。在安全終止後執行的任何程式碼都無法存取 CPU,也不會取得任何進展。
此外,如果後續叫用在相同環境中執行,背景活動就會恢復,並干擾新的叫用。這可能會導致發生難以診斷的非預期行為和錯誤。在函式終止後存取網路通常會導致連線重設 (ECONNRESET
錯誤代碼)。
您通常可以透過個別叫用作業的記錄,偵測背景活動,方法是尋找在表示叫用作業已完成的資料行後面記錄的任何內容。背景活動有時可能會埋藏在程式碼的較深層位置,尤其是在存在回呼或計時器等非同步作業時。請審查您的程式碼,確認在您終止函式之前,所有非同步作業皆已完成。
一律刪除暫存檔案
暫存目錄中的本機磁碟儲存空間是一個記憶體內部檔案系統。您編寫的檔案會耗用用於函式的記憶體,而且有時會在叫用間持續存在。不明確刪除這些檔案最終可能會導致發生記憶體不足的錯誤,並造成後續冷啟動。
如要查看個別函式使用的記憶體,請在Google Cloud 主控台的函式清單中選取該函式,然後選擇「Memory usage」圖表。
如果您需要存取長期儲存空間,建議使用 Cloud Run 搭配 Cloud Storage 或 NFS 磁碟區掛載的磁碟區。
使用管道處理較大的檔案時,可以減少記憶體需求。舉例來說,您可以建立讀取串流,將其傳遞至以串流為基礎的程序,然後直接將輸出串流寫入 Cloud Storage,藉此在 Cloud Storage 中處理檔案。
Functions Framework
為確保在各環境中安裝相同的依附元件,我們建議您在套件管理工具中加入 Functions Framework 程式庫,並將依附元件釘選至特定版本的 Functions Framework。
如要這麼做,請在相關的鎖定檔案中加入偏好的版本 (例如 Node.js 的 package-lock.json
,或 Python 的 requirements.txt
)。
如果未明確將 Functions Framework 列為依附元件,系統會在建構程序中自動使用最新可用的版本。
工具
本節提供如何使用工具實作、測試及與 Cloud Run 函式互動的規範。
本機開發
函式部署作業需要一些時間,因此通常在本機測試函式的程式碼會比較快。
錯誤報告
在使用例外狀況處理的語言中,請勿擲回未偵測到的例外狀況,因為這會在日後的叫用中強制執行冷啟動。
請勿手動退出
手動退出可能會導致非預期的行為。請改用下列語言專屬慣用語:
Node.js
請勿使用 process.exit()
。HTTP 函式應透過 res.status(200).send(message)
傳送回應,而事件驅動函式會在傳回 (隱含或明確) 後結束。
Python
請勿使用 sys.exit()
。HTTP 函式應明確地以字串形式傳回回應,而事件驅動函式會在傳回值 (明確或隱含) 後結束。
Go
請勿使用 os.Exit()
。HTTP 函式應明確地以字串形式傳回回應,而事件驅動函式會在傳回值 (明確或隱含) 後結束。
Java
請勿使用 System.exit()
。HTTP 函式應透過 response.getWriter().write(message)
傳送回應,而事件驅動函式會在傳回 (隱含或明確) 後結束。
C#
請勿使用 System.Environment.Exit()
。HTTP 函式應透過 context.Response.WriteAsync(message)
傳送回應,而事件驅動函式會在傳回 (隱含或明確) 後結束。
Ruby
請勿使用 exit()
或 abort()
。HTTP 函式應明確地以字串形式傳回回應,而事件驅動函式會在傳回值 (明確或隱含) 後結束。
PHP
請勿使用 exit()
或 die()
。HTTP 函式應明確地以字串形式傳回回應,而事件驅動函式會在傳回值 (明確或隱含) 後結束。
使用 Sendgrid 傳送電子郵件
Cloud Run 函式不允許通訊埠 25 的傳出連線,因此您無法建立與 SMTP 伺服器的非安全連線。建議您使用 SendGrid 等第三方服務傳送電子郵件。如要瞭解其他傳送電子郵件的方法,請參閱 Google Compute Engine 的從執行個體傳送電子郵件教學課程。
成效
本節說明最佳化效能的最佳做法。
避免低並行性
由於冷啟動成本高昂,因此在尖峰期間重複使用最近啟動的執行個體,是處理負載的絕佳最佳化方式。限制並行作業會限制現有執行個體的使用方式,因此會導致更多冷啟動。
增加並行性有助於延遲每個執行個體的多個要求,讓您更輕鬆地處理負載尖峰。謹慎使用依附元件
由於函式是無狀態的,因此執行環境通常是從頭開始初始化 (這期間就是所謂的「冷啟動」)。發生冷啟動時,會評估函式的全域背景資訊。
如果函式匯入模組,在冷啟動期間,這些模組的載入時間會增加叫用的延遲時間。您可以正確載入依附元件,而不載入函式不使用的依附元件,來減少這一延遲時間以及部署函式需要的時間。
使用全域變數在未來叫用中重複使用物件
我們無法保證 Cloud Run 函式的狀態會保留供日後叫用。不過,Cloud Run 函式通常會回收先前呼叫的執行環境。如果您在全域範圍中宣告變數,其值可以在後續叫用中重複使用,而無需重新計算。
這樣一來,您便可以快取在每次叫用函式時重新建立起來費用可能比較高的的物件。將這類物件從函式主體移至全域範圍可能會使效能大幅提升。下列範例只會為每個函式執行個體建立一個重型物件,並在到達指定執行個體的所有函式叫用中共用這個物件:
Node.js
Python
Go
Java
C#
Ruby
PHP
在全域範圍內快取網路連線、程式庫參照和 API 用戶端物件特別重要。如需範例,請參閱「網路最佳做法」。
設定執行個體數量下限,減少冷啟動
根據預設,Cloud Run 函式會依據傳入要求的數量調整執行個體數量。如要變更這項預設行為,請設定 Cloud Run 函式必須保持待命以便處理要求的最低執行個體數量。設定執行個體數量下限可減少應用程式的冷啟動情形。如果應用程式對延遲時間較為敏感,建議您設定最少的例項數量,並在載入期間完成初始化作業。
如要瞭解如何設定執行個體數量下限,請參閱「使用最小執行個體數」。
關於冷啟動和初始化的注意事項
全域初始化作業會在載入期間執行。否則,第一個要求就必須完成初始化和載入模組,因此會造成更高的延遲。
不過,全域初始化也會對冷啟動造成影響。為盡量減少這種影響,請只初始化第一個要求所需的內容,以便將第一個要求的延遲時間降到最低。
如果您已依照上述方式為延遲時間敏感的函式設定最低執行個體數,這一點就格外重要。在這種情況下,如果可以在載入時完成初始化作業,並快取實用的資料,就能確保第一個要求不必執行這項作業,且可以低延遲提供。
如果您在全域範圍內初始化變數,視語言而定,長時間的初始化作業可能會導致兩種行為:- 對於某些語言和非同步程式庫的組合,函式架構可以非同步執行並立即傳回,導致程式碼繼續在背景執行,這可能導致無法存取 CPU等問題。為避免這種情況,您應按照下文所述,在模組初始化時進行阻斷。這麼做也可以確保系統在初始化完成前,不會提供任何要求。- 另一方面,如果初始化是同步的,則長時間初始化會導致冷啟動時間更長,這可能會造成問題,尤其是在負載量激增期間,並且同時有低並行函式時。
非同步 Node.js 程式庫預熱示例
Node.js 搭配 Firestore 就是非同步 Node.js 程式庫的範例。為了充分利用 min_instances,下列程式碼會在載入時完成載入和初始化作業,並阻斷模組載入作業。
使用 TLA,也就是說必須使用 ES6,為 node.js 程式碼使用 .mjs
擴充功能,或將 type: module
新增至 package.json 檔案。
{ "main": "main.js", "type": "module", "dependencies": { "@google-cloud/firestore": "^7.10.0", "@google-cloud/functions-framework": "^3.4.5" } }
Node.js
import Firestore from '@google-cloud/firestore'; import * as functions from '@google-cloud/functions-framework'; const firestore = new Firestore({preferRest: true}); // Pre-warm firestore connection pool, and preload our global config // document in cache. In order to ensure no other request comes in, // block the module loading with a synchronous global request: const config = await firestore.collection('collection').doc('config').get(); functions.http('fetch', (req, res) => { // Do something with config and firestore client, which are now preloaded // and will execute at lower latency. });
全域初始化的範例
如果您在單一檔案中定義多個函式,且不同函式使用不同變數,這個方法尤為重要。除非您使用延遲初始化,否則會浪費已初始化但從未使用的變數資源。
其他資源
如要進一步瞭解如何提升效能,請觀看「Google Cloud Performance Atlas」影片中的「Cloud Run 函式的冷啟動時間」。