เอกสารนี้อธิบายแนวทางปฏิบัติแนะนำสำหรับการออกแบบ การติดตั้งใช้งาน การทดสอบ และการนำ Cloud Functions ไปใช้งาน
ความถูกต้อง
ส่วนนี้จะอธิบายแนวทางปฏิบัติแนะนำทั่วไปสำหรับการออกแบบและการใช้งาน Cloud Functions
เขียนฟังก์ชันที่ idempotent
ฟังก์ชันควรให้ผลลัพธ์เดียวกันแม้ว่าจะเรียกใช้หลายครั้งก็ตาม ซึ่งจะช่วยให้คุณลองเรียกใช้อีกครั้งได้หากการเรียกใช้ก่อนหน้านี้ไม่สําเร็จขณะประมวลผลโค้ด ดูข้อมูลเพิ่มเติมได้ที่ การลองใช้ฟังก์ชันที่ทำงานตามเหตุการณ์อีกครั้ง
ไม่เริ่มกิจกรรมในเบื้องหลัง
กิจกรรมเบื้องหลังคือสิ่งที่เกิดขึ้นหลังจากที่ฟังก์ชันสิ้นสุดลง
การเรียกใช้ฟังก์ชันจะเสร็จสมบูรณ์เมื่อฟังก์ชันแสดงผลหรือส่งสัญญาณการทำงานเสร็จสิ้น เช่น การเรียกใช้อาร์กิวเมนต์ callback
ในฟังก์ชันที่ทำงานตามเหตุการณ์ของ Node.js โค้ดที่ทำงานหลังจากการสิ้นสุดอย่างราบรื่นจะเข้าถึง CPU ไม่ได้และจะไม่ทํางานใดๆ
นอกจากนี้ เมื่อเรียกใช้ครั้งถัดไปในสภาพแวดล้อมเดียวกัน กิจกรรมเบื้องหลังจะกลับมาทำงานต่อ ซึ่งจะรบกวนการเรียกใช้ใหม่ ซึ่งอาจทําให้เกิดลักษณะการทํางานที่ไม่คาดคิดและข้อผิดพลาดที่วิเคราะห์ได้ยาก การเข้าถึงเครือข่ายหลังจากฟังก์ชันสิ้นสุดลงมักจะทําให้การเชื่อมต่อรีเซ็ต (ECONNRESET
รหัสข้อผิดพลาด)
กิจกรรมเบื้องหลังมักตรวจพบได้ในบันทึกจากคําเรียกแต่ละรายการ โดยค้นหาสิ่งที่บันทึกไว้หลังบรรทัดที่ระบุว่าคําเรียกเสร็จสิ้นแล้ว บางครั้งกิจกรรมเบื้องหลังอาจฝังอยู่ในโค้ดลึกลงไป โดยเฉพาะเมื่อมีการดำเนินการแบบไม่พร้อมกัน เช่น การเรียกกลับหรือตัวจับเวลา ตรวจสอบโค้ดเพื่อให้แน่ใจว่าการดำเนินการแบบไม่สอดคล้องกันทั้งหมดเสร็จสิ้นแล้วก่อนที่จะสิ้นสุดฟังก์ชัน
ลบไฟล์ชั่วคราวเสมอ
พื้นที่เก็บข้อมูลในดิสก์ในเครื่องของไดเรกทอรีชั่วคราวคือระบบไฟล์ในหน่วยความจำ ไฟล์ที่คุณเขียนจะใช้หน่วยความจําที่มีให้ฟังก์ชัน และบางครั้งจะยังคงอยู่ระหว่างการเรียกใช้ การลบไฟล์เหล่านี้อย่างชัดเจนไม่สำเร็จอาจส่งผลให้เกิดข้อผิดพลาดหน่วยความจำไม่เพียงพอและระบบต้องเริ่มต้นใหม่ในภายหลัง
คุณสามารถดูหน่วยความจำที่ฟังก์ชันแต่ละรายการใช้ได้โดยเลือกฟังก์ชันนั้นในรายการฟังก์ชันในคอนโซล Google Cloud แล้วเลือกผังการใช้หน่วยความจำ
หากต้องการเข้าถึงพื้นที่เก็บข้อมูลระยะยาว ให้ลองใช้Cloud Runการต่อเชื่อมวอลุ่มด้วยCloud Storageหรือวอลุ่ม NFS
คุณลดความต้องการหน่วยความจำได้เมื่อประมวลผลไฟล์ขนาดใหญ่โดยใช้ไปป์ไลน์ เช่น คุณสามารถประมวลผลไฟล์ใน Cloud Storage ได้โดยการสร้างสตรีมการอ่าน ส่งผ่านกระบวนการที่อิงตามสตรีม และเขียนสตรีมเอาต์พุตไปยัง Cloud Storage โดยตรง
เฟรมเวิร์กฟังก์ชัน
เราขอแนะนำให้คุณรวมไลบรารี Functions Framework ไว้ในเครื่องมือจัดการแพ็กเกจและปักหมุดการพึ่งพาไว้กับ Functions Framework เวอร์ชันที่เจาะจง เพื่อให้มั่นใจว่ามีการนําเข้าการพึ่งพาเดียวกันอย่างสอดคล้องกันในสภาพแวดล้อมต่างๆ
โดยให้ระบุเวอร์ชันที่ต้องการในไฟล์ล็อกที่เกี่ยวข้อง (เช่น package-lock.json
สำหรับ Node.js หรือ requirements.txt
สำหรับ Python)
หากไม่ได้ระบุเฟรมเวิร์กฟังก์ชันเป็นข้อกำหนดอย่างชัดแจ้ง ระบบจะเพิ่มเฟรมเวิร์กดังกล่าวโดยอัตโนมัติในระหว่างกระบวนการสร้างโดยใช้เวอร์ชันล่าสุดที่มี
เครื่องมือ
ส่วนนี้จะให้หลักเกณฑ์เกี่ยวกับวิธีใช้เครื่องมือเพื่อติดตั้งใช้งาน ทดสอบ และโต้ตอบกับ Cloud Functions
การพัฒนาในพื้นที่
การปรับใช้งานฟังก์ชันจะใช้เวลาสักครู่ การทดสอบโค้ดของฟังก์ชันในเครื่องจึงมักจะเร็วกว่า
นักพัฒนาแอป Firebase สามารถใช้โปรแกรมจำลอง Cloud FunctionsFirebase CLIหลีกเลี่ยงการหมดเวลาของการติดตั้งใช้งานระหว่างการเริ่มต้น
หากการทําให้ฟังก์ชันใช้งานได้ไม่สําเร็จเนื่องจากข้อผิดพลาดเกี่ยวกับเวลาหมด แสดงว่าโค้ดขอบเขตส่วนกลางของฟังก์ชันใช้เวลาในการดำเนินการนานเกินไปในระหว่างกระบวนการทําให้ใช้งานได้
Firebase CLI มีระยะหมดเวลาเริ่มต้นสำหรับการค้นหาฟังก์ชันระหว่างการทำให้ใช้งานได้ หากตรรกะการเริ่มต้นในซอร์สโค้ดของฟังก์ชัน (การโหลดข้อบังคับ การเรียกใช้เครือข่าย และอื่นๆ) ใช้เวลาเกินการหมดเวลานี้ การทำให้ใช้งานได้อาจล้มเหลว
หากต้องการหลีกเลี่ยงการหมดเวลา ให้ใช้กลยุทธ์ข้อใดข้อหนึ่งต่อไปนี้
(แนะนํา) ใช้ onInit()
เพื่อเลื่อนการเริ่มต้น
ใช้ฮุก onInit()
เพื่อหลีกเลี่ยงการเรียกใช้โค้ดเริ่มต้นระหว่างการทําให้ใช้งานได้ โค้ดภายในฮุก onInit()
จะทำงานก็ต่อเมื่อมีการทำให้ฟังก์ชันใช้งานได้ใน Cloud Run เท่านั้น ไม่ใช่ระหว่างกระบวนการทำให้ใช้งานได้
const { onInit } = require('firebase-functions/v2/core'); const { onRequest } = require('firebase-functions/v2/https'); // Example of a slow initialization task function slowInitialization() { // Simulate a long-running operation (e.g., loading a large model, network request). return new Promise(resolve => { setTimeout(() => { console.log("Slow initialization complete"); resolve("Initialized Value"); }, 20000); // Simulate a 20-second delay }); } let initializedValue; onInit(async () => { initializedValue = await slowInitialization(); }); exports.myFunction = onRequest((req, res) => { // Access the initialized value. It will be ready after the first invocation. res.send(`Value: ${initializedValue}`); });
from firebase_functions.core import init from firebase_functions import https_fn import time # Example of a slow initialization task def _slow_initialization(): time.sleep(20) # Simulate a 20-second delay print("Slow initialization complete") return "Initialized Value" _initialized_value = None @init def initialize(): global _initialized_value _initialized_value = _slow_initialization() @https_fn.on_request() def my_function(req: https_fn.Request) -> https_fn.Response: # Access the initialized value. It will be ready after the first invocation. return https_fn.Response(f"Value: {_initialized_value}")
(ทางเลือก) เพิ่มระยะหมดเวลาการค้นพบ
หากไม่สามารถเปลี่ยนรูปแบบโค้ดให้ใช้ onInit()
ได้ คุณสามารถเพิ่มการหมดเวลาของการนำส่ง CLI โดยใช้ตัวแปรสภาพแวดล้อม FUNCTIONS_DISCOVERY_TIMEOUT
ดังนี้
$ export FUNCTIONS_DISCOVERY_TIMEOUT=30
$ firebase deploy --only functions
ใช้ Sendgrid เพื่อส่งอีเมล
Cloud Functions ไม่อนุญาตให้มีการเชื่อมต่อขาออกในพอร์ต 25 คุณจึงทำการเชื่อมต่อที่ไม่ปลอดภัยกับเซิร์ฟเวอร์ SMTP ไม่ได้ วิธีส่งอีเมลที่แนะนําคือใช้บริการของบุคคลที่สาม เช่น SendGrid คุณดูตัวเลือกอื่นๆ ในการส่งอีเมลได้ในบทแนะนำการส่งอีเมลจากอินสแตนซ์สำหรับ Google Compute Engine
ประสิทธิภาพ
ส่วนนี้จะอธิบายแนวทางปฏิบัติแนะนำในการเพิ่มประสิทธิภาพ
หลีกเลี่ยงการทำงานพร้อมกันต่ำ
เนื่องจาก Cold Start มีค่าใช้จ่ายสูง ความสามารถในการนำอินสแตนซ์ที่เพิ่งเริ่มต้นใช้งานไปใช้งานซ้ำในช่วงที่มีปริมาณงานเพิ่มขึ้นจึงเป็นการเพิ่มประสิทธิภาพที่ยอดเยี่ยมในการจัดการกับภาระงาน การจำกัดการทำงานพร้อมกันจะจำกัดวิธีใช้ประโยชน์จากอินสแตนซ์ที่มีอยู่ จึงทำให้เกิด Cold Start มากขึ้น
การเพิ่มการเกิดขึ้นพร้อมกันช่วยเลื่อนคําขอหลายรายการต่ออินสแตนซ์ออกไปได้ ซึ่งทำให้จัดการกับปริมาณงานที่เพิ่มขึ้นได้ง่ายขึ้นใช้ Dependency อย่างชาญฉลาด
เนื่องจากฟังก์ชันไม่มีสถานะ สภาพแวดล้อมการดําเนินการจึงมักจะเริ่มต้นจากต้น (ในช่วงที่เรียกว่าการเริ่มต้นแบบเย็น) เมื่อเกิด Cold Start ระบบจะประเมินบริบทส่วนกลางของฟังก์ชัน
หากฟังก์ชันนําเข้าโมดูล เวลาในการโหลดโมดูลเหล่านั้นอาจเพิ่มเวลาในการตอบสนองของการเรียกใช้ระหว่างการเริ่มต้นระบบแบบเย็น คุณสามารถลดเวลาในการตอบสนองนี้ รวมถึงเวลาที่จำเป็นในการทำให้ฟังก์ชันใช้งานได้ โดยการโหลดไลบรารีอย่างถูกต้องและไม่โหลดไลบรารีที่ฟังก์ชันไม่ได้ใช้
ใช้ตัวแปรส่วนกลางเพื่อใช้ออบเจ็กต์ซ้ำในการเรียกใช้ในอนาคต
ไม่มีการรับประกันว่าระบบจะเก็บสถานะของฟังก์ชันไว้สำหรับการเรียกใช้ในอนาคต อย่างไรก็ตาม Cloud Functions มักนําสภาพแวดล้อมการดําเนินการของการเรียกใช้ก่อนหน้ามาใช้ซ้ำ หากคุณประกาศตัวแปรในขอบเขตส่วนกลาง ระบบจะใช้ค่าของตัวแปรนั้นซ้ำในการเรียกใช้ครั้งต่อๆ ไปได้โดยที่ไม่ต้องคํานวณใหม่
วิธีนี้ช่วยให้คุณแคชออบเจ็กต์ที่อาจสร้างใหม่ได้ยากในแต่ละการเรียกใช้ฟังก์ชัน การย้ายออบเจ็กต์ดังกล่าวจากเนื้อหาฟังก์ชันไปยังขอบเขตส่วนกลางอาจส่งผลให้ประสิทธิภาพดีขึ้นอย่างมาก ตัวอย่างต่อไปนี้จะสร้างออบเจ็กต์ขนาดใหญ่เพียงครั้งเดียวต่ออินสแตนซ์ของฟังก์ชัน และแชร์ออบเจ็กต์นั้นกับการเรียกใช้ฟังก์ชันทั้งหมดที่เข้าถึงอินสแตนซ์ที่ระบุ
console.log('Global scope'); const perInstance = heavyComputation(); const functions = require('firebase-functions'); exports.function = functions.https.onRequest((req, res) => { console.log('Function invocation'); const perFunction = lightweightComputation(); res.send(`Per instance: ${perInstance}, per function: ${perFunction}`); });
import time from firebase_functions import https_fn # Placeholder def heavy_computation(): return time.time() # Placeholder def light_computation(): return time.time() # Global (instance-wide) scope # This computation runs at instance cold-start instance_var = heavy_computation() @https_fn.on_request() def scope_demo(request): # Per-function scope # This computation runs every time this function is called function_var = light_computation() return https_fn.Response(f"Instance: {instance_var}; function: {function_var}")
ฟังก์ชัน HTTP นี้จะรับออบเจ็กต์คำขอ (flask.Request
) และแสดงผลข้อความตอบกลับ หรือชุดค่าผสมใดๆ ที่เปลี่ยนเป็นออบเจ็กต์ Response
ได้โดยใช้ make_response
การแคชการเชื่อมต่อเครือข่าย การอ้างอิงไลบรารี และออบเจ็กต์ไคลเอ็นต์ API ในขอบเขตส่วนกลางมีความสําคัญอย่างยิ่ง ดูตัวอย่างได้ที่การเพิ่มประสิทธิภาพเครือข่าย
ลดจำนวน Cold Start โดยการกําหนดจํานวนอินสแตนซ์ขั้นต่ำ
โดยค่าเริ่มต้น Cloud Functions จะปรับขนาดจำนวนอินสแตนซ์ตามจำนวนคำขอขาเข้า คุณเปลี่ยนลักษณะการทำงานเริ่มต้นนี้ได้โดยการตั้งค่าจำนวนอินสแตนซ์ขั้นต่ำที่ Cloud Functions ต้องพร้อมให้บริการคำขอ การตั้งค่าจำนวนอินสแตนซ์ขั้นต่ำจะลดจำนวนครั้งที่แอปพลิเคชันต้องทำการ Cold Start เราขอแนะนำให้ตั้งค่าจำนวนอินสแตนซ์ขั้นต่ำ และทำการเริ่มต้นให้เสร็จสมบูรณ์ในเวลาที่โหลด หากแอปพลิเคชันของคุณมีความไวต่อเวลาในการตอบสนอง
ดูข้อมูลเพิ่มเติมเกี่ยวกับตัวเลือกรันไทม์เหล่านี้ได้ที่หัวข้อควบคุมลักษณะการการปรับขนาดหมายเหตุเกี่ยวกับ Cold Start และการจัดเตรียม
การเริ่มต้นแบบรวมจะเกิดขึ้นเมื่อโหลด หากไม่มีแคชนี้ คําขอแรกจะต้องทําการเริ่มต้นและโหลดโมดูลให้เสร็จสมบูรณ์ จึงจะทําให้เกิดความล่าช้ามากขึ้น
อย่างไรก็ตาม การจัดเตรียมเริ่มต้นแบบรวมก็มีผลต่อ Cold Start ด้วย หากต้องการลดผลกระทบนี้ ให้เริ่มต้นเฉพาะสิ่งที่จําเป็นสําหรับคําขอแรกเพื่อรักษาเวลาในการตอบสนองของคําขอแรกให้ต่ำที่สุด
ซึ่งสำคัญมากในกรณีที่คุณกําหนดค่าอินสแตนซ์ขั้นต่ำตามที่อธิบายไว้ข้างต้นสําหรับฟังก์ชันที่ไวต่อเวลาในการตอบสนอง ในกรณีนี้ การทำข้อมูลเริ่มต้นให้เสร็จสมบูรณ์ในเวลาที่โหลดและการแคชข้อมูลที่เป็นประโยชน์จะช่วยให้คำขอแรกไม่ต้องทำขั้นตอนดังกล่าวและแสดงผลโดยมีเวลาในการตอบสนองต่ำ
หากคุณเริ่มต้นตัวแปรในขอบเขตส่วนกลาง เวลาที่ใช้ในการเริ่มต้นอาจส่งผลให้เกิดลักษณะการทำงาน 2 แบบ ทั้งนี้ขึ้นอยู่กับภาษาที่ใช้ - สำหรับชุดค่าผสมบางอย่างของภาษาและไลบรารีแบบแอสซิงค์ เฟรมเวิร์กฟังก์ชันอาจทำงานแบบแอสซิงค์และแสดงผลทันที ซึ่งจะทำให้โค้ดทำงานต่อไปในเบื้องหลัง ซึ่งอาจทำให้เกิดปัญหาต่างๆ เช่น ไม่สามารถเข้าถึง CPU คุณควรบล็อกการเริ่มต้นโมดูลตามที่อธิบายไว้ด้านล่างเพื่อหลีกเลี่ยงปัญหานี้ ซึ่งจะช่วยให้มั่นใจว่าระบบจะไม่แสดงคําขอจนกว่าการเริ่มต้นจะเสร็จสมบูรณ์ - ในทางกลับกัน หากการเริ่มต้นเป็นแบบซิงค์ เวลาเริ่มต้นที่นานจะทำให้การเริ่มต้นแบบ Cold นานขึ้น ซึ่งอาจเป็นปัญหาได้ โดยเฉพาะกับฟังก์ชันการทำงานพร้อมกันต่ำในช่วงที่มีการโหลดสูง
ตัวอย่างการอุ่นเครื่องไลบรารี Node.js แบบไม่พร้อมกัน
Node.js ที่มี Firestore เป็นตัวอย่างของไลบรารี Node.js แบบอะซิงโครนัส หากต้องการใช้ประโยชน์จาก min_instances โค้ดต่อไปนี้จะโหลดและเริ่มต้นให้เสร็จสมบูรณ์ในเวลาที่โหลด โดยบล็อกการโหลดโมดูล
ใช้ TLA ซึ่งหมายความว่าต้องใช้ ES6 โดยใช้ส่วนขยาย .mjs
สำหรับโค้ด node.js หรือเพิ่ม type: module
ลงในไฟล์ package.json
{ "main": "main.js", "type": "module", "dependencies": { "@google-cloud/firestore": "^7.10.0", "@google-cloud/functions-framework": "^3.4.5" } }
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. });
ตัวอย่างการเริ่มต้นแบบรวม
const functions = require('firebase-functions'); let myCostlyVariable; exports.function = functions.https.onRequest((req, res) => { doUsualWork(); if(unlikelyCondition()){ myCostlyVariable = myCostlyVariable || buildCostlyVariable(); } res.status(200).send('OK'); });
from firebase_functions import https_fn # Always initialized (at cold-start) non_lazy_global = file_wide_computation() # Declared at cold-start, but only initialized if/when the function executes lazy_global = None @https_fn.on_request() def lazy_globals(request): global lazy_global, non_lazy_global # This value is initialized only if (and when) the function is called if not lazy_global: lazy_global = function_specific_computation() return https_fn.Response(f"Lazy: {lazy_global}, non-lazy: {non_lazy_global}.")
ฟังก์ชัน HTTP นี้ใช้ตัวแปรส่วนกลางที่เริ่มต้นแบบล่าช้า โดยจะใช้ออบเจ็กต์คำขอ (flask.Request
) และแสดงผลข้อความตอบกลับ หรือชุดค่าผสมใดๆ ที่เปลี่ยนให้เป็นออบเจ็กต์ Response
ได้โดยใช้ make_response
ซึ่งสำคัญอย่างยิ่งหากคุณกำหนดฟังก์ชันหลายรายการในไฟล์เดียว และฟังก์ชันต่างๆ ใช้ตัวแปรที่แตกต่างกัน คุณอาจสิ้นเปลืองทรัพยากรไปกับตัวแปรที่เริ่มต้นแต่ไม่เคยใช้ เว้นแต่จะใช้การเริ่มต้นแบบเลื่อนเวลา
แหล่งข้อมูลเพิ่มเติม
ดูข้อมูลเพิ่มเติมเกี่ยวกับการเพิ่มประสิทธิภาพในวิดีโอ "Google Cloud Performance Atlas" Cloud Functions เวลาในการบูตเย็น