אופטימיזציה של הרשת (דור ראשון)
הפשטות של פונקציות Cloud Run מאפשרת לכם לפתח קוד במהירות ולהריץ אותו בסביבה ללא שרת. בסביבה מתונה, העלות של הפעלת פונקציות נמוכה, ולכן יכול להיות שלא תראו חשיבות רבה לאופטימיזציה של הקוד. אבל ככל שהפריסה גדלה, חשוב יותר לבצע אופטימיזציה של הקוד.
במאמר הזה מוסבר איך לבצע אופטימיזציה של הרשתות עבור הפונקציות. אלה כמה מהיתרונות של אופטימיזציה של הרשת:
- צמצום הזמן שנדרש למעבד כדי ליצור חיבורים חדשים בכל קריאה לפונקציה.
- כדי להקטין את הסיכוי לחריגה ממכסות החיבור או ה-DNS.
שמירה על חיבורים מתמידים
בקטע הזה מופיעות דוגמאות לאופן שבו אפשר לשמור על חיבורים מתמשכים בפונקציה. אם לא תעשו את זה, יכול להיות שתגיעו במהירות למכסת החיבורים.
בקטע הזה מוסבר על התרחישים הבאים:
- HTTP/S
- Google APIs
בקשות HTTP/S
בקטע הקוד הבא מוצג איך לשמור על חיבורים קבועים במקום ליצור חיבור חדש בכל הפעלה של הפונקציה:
Node.js
Python
המשך
PHP
מומלץ להשתמש ב-Guzzle PHP HTTP Framework כדי לשלוח בקשות HTTP, כי הוא מטפל בחיבורים קבועים באופן אוטומטי.
גישה אל Google APIs
בדוגמה הבאה נעשה שימוש ב-Cloud Pub/Sub, אבל הגישה הזו מתאימה גם לספריות לקוח אחרות, למשל Cloud Natural Language או Cloud Spanner. חשוב לזכור ששיפורים בביצועים עשויים להיות תלויים בהטמעה הנוכחית של ספריות לקוח מסוימות.
יצירת אובייקט לקוח Pub/Sub מובילה לחיבור אחד ולשתי שאילתות DNS לכל הפעלה. כדי למנוע חיבורים ושאילתות DNS מיותרים, צריך ליצור את אובייקט הלקוח של Pub/Sub בהיקף גלובלי, כמו בדוגמה הבאה:
Node.js
Python
המשך
// Package contexttip is an example of how to use Pub/Sub and context.Context in // a Cloud Function. package contexttip import ( "context" "encoding/json" "fmt" "log" "net/http" "os" "sync" "cloud.google.com/go/pubsub" "github.com/GoogleCloudPlatform/functions-framework-go/functions" ) // client is a global Pub/Sub client, initialized once per instance. var client *pubsub.Client var once sync.Once // createClient creates the global pubsub Client func createClient() { // GOOGLE_CLOUD_PROJECT is a user-set environment variable. var projectID = os.Getenv("GOOGLE_CLOUD_PROJECT") // err is pre-declared to avoid shadowing client. var err error // client is initialized with context.Background() because it should // persist between function invocations. client, err = pubsub.NewClient(context.Background(), projectID) if err != nil { log.Fatalf("pubsub.NewClient: %v", err) } } func init() { // register http function functions.HTTP("PublishMessage", PublishMessage) } type publishRequest struct { Topic stringjson:"topic"Message stringjson:"message"} // PublishMessage publishes a message to Pub/Sub. PublishMessage only works // with topics that already exist. func PublishMessage(w http.ResponseWriter, r *http.Request) { // use of sync.Once ensures client is only created once. once.Do(createClient) // Parse the request body to get the topic name and message. p := publishRequest{} if err := json.NewDecoder(r.Body).Decode(&p); err != nil { log.Printf("json.NewDecoder: %v", err) http.Error(w, "Error parsing request", http.StatusBadRequest) return } if p.Topic == "" || p.Message == "" { s := "missing 'topic' or 'message' parameter" log.Println(s) http.Error(w, s, http.StatusBadRequest) return } m := &pubsub.Message{ Data: []byte(p.Message), } // Publish and Get use r.Context() because they are only needed for this // function invocation. If this were a background function, they would use // the ctx passed as an argument. id, err := client.Topic(p.Topic).Publish(r.Context(), m).Get(r.Context()) if err != nil { log.Printf("topic(%s).Publish.Get: %v", p.Topic, err) http.Error(w, "Error publishing message", http.StatusInternalServerError) return } fmt.Fprintf(w, "Message published: %v", id) }
איפוסים של חיבורים יוצאים
יכול להיות שהחיבורים מהפונקציה שלכם ל-VPC ולאינטרנט יופסקו מדי פעם ויוחלפו אם התשתית הבסיסית תופעל מחדש או תעודכן. אם האפליקציה שלכם עושה שימוש חוזר בחיבורים לטווח ארוך, מומלץ להגדיר אותה כך שתבצע חיבורים מחדש כדי למנוע שימוש חוזר בחיבור לא פעיל.
הזמן הקצוב לתפוגה של בקשות יוצאות
יכול להיות ששקעים יוצאים יוחזרו אחרי 10 דקות של חוסר פעילות. כל פעולת socket שומרת על ה-socket פעיל למשך 10 דקות נוספות.
בדיקת טעינה של הפונקציה
כדי למדוד כמה חיבורים הפונקציה מבצעת בממוצע, צריך לפרוס אותה כפונקציית HTTP ולהשתמש במסגרת לבדיקת ביצועים כדי להפעיל אותה ב-QPS מסוים. אפשרות אחת היא Artillery, שאפשר להפעיל אותה באמצעות שורה אחת:
$ artillery quick -d 300 -r 30 URL
הפקודה הזו מאחזרת את כתובת ה-URL הנתונה ב-30 QPS למשך 300 שניות.
אחרי שמבצעים את הבדיקה, בודקים את השימוש במכסת החיבורים בדף המכסות של Cloud Run functions API במסוף Google Cloud . אם השימוש הוא באופן עקבי בסביבות 30 (או כפולה של 30), אתם יוצרים חיבור אחד (או כמה חיבורים) בכל הפעלה. אחרי שמבצעים אופטימיזציה של הקוד, אמורות להתרחש כמה (10-30) התחברויות רק בתחילת הבדיקה.
אפשר גם להשוות את עלות השימוש במעבד לפני ואחרי האופטימיזציה בתרשים של מכסת השימוש במעבד באותו דף.