הפעלת ניסיונות חוזרים של פונקציות מבוססות-אירועים (דור ראשון)
במאמר הזה מוסבר איך להפעיל ניסיון חוזר של פונקציות מבוססות-אירועים. אי אפשר להגדיר ניסיון חוזר אוטומטי לפונקציות HTTP.
סמנטיקה של ניסיון חוזר
פונקציות Cloud Run מספקות ביצוע של פונקציה מבוססת-אירועים לפחות פעם אחת עבור כל אירוע שנפלט ממקור אירועים. כברירת מחדל, אם הפעלה של פונקציה מסתיימת בשגיאה, הפונקציה לא מופעלת שוב והאירוע מושמט. כשמפעילים ניסיונות חוזרים בפונקציה מבוססת-אירועים, פונקציות Cloud Run מנסות שוב להפעיל פונקציה שנכשלה עד שהיא מסתיימת בהצלחה או עד שחלון הניסיונות החוזרים מסתיים.
חלון הניסיון החוזר הזה יפוג אחרי 7 ימים. פונקציות Cloud Run מבצעות ניסיון חוזר של פונקציות חדשות מבוססות-אירועים באמצעות אסטרטגיית השהיה מעריכית לפני ניסיון חוזר, עם השהיה הולכת וגדלה של בין 10 ל-600 שניות. המדיניות הזו חלה על פונקציות חדשות בפעם הראשונה שפורסים אותן. השינוי לא חל באופן רטרואקטיבי על פונקציות קיימות שפריסתן הראשונה הייתה לפני שהשינויים שמתוארים בנתוני הגרסה האלה נכנסו לתוקף, גם אם תפרסו מחדש את הפונקציות.
אם לא מפעילים ניסיונות חוזרים לפונקציה (ברירת המחדל), הפונקציה תמיד מדווחת שהיא בוצעה בהצלחה, ויכול להיות שיופיעו קודי תגובה 200 OK ביומנים שלה. זה קורה גם אם הפונקציה נתקלה בשגיאה. כדי להבהיר מתי הפונקציה נתקלת בשגיאה, חשוב לדווח על שגיאות בצורה מתאימה.
למה פונקציות מבוססות-אירועים לא מצליחות להסתיים
במקרים נדירים, יכול להיות שפונקציה תצא מוקדם מדי בגלל שגיאה פנימית, ובאופן אוטומטי יכול להיות שיהיה ניסיון חוזר להפעיל את הפונקציה או שלא יהיה ניסיון כזה.
בדרך כלל, פונקציה מבוססת-אירועים עלולה להיכשל בהשלמה בגלל שגיאות שמוחזרות בקוד הפונקציה עצמה. הסיבות האפשריות לכך:
- הפונקציה מכילה באג וזמן הריצה יוצר חריגה.
- הפונקציה לא יכולה להגיע לנקודת קצה של שירות, או שהזמן הקצוב לתפוגה שלה מסתיים בזמן הניסיון לעשות זאת.
- הפונקציה יוצרת בכוונה חריגה (לדוגמה, כשפרמטר לא עובר אימות).
- פונקציית Node.js מחזירה הבטחה שנדחתה, או מעבירה ערך שאינו
nullלפונקציית קריאה חוזרת.
בכל אחד מהמקרים האלה, הפונקציה מפסיקה את ההרצה כברירת מחדל והאירוע נמחק. כדי לנסות שוב להפעיל את הפונקציה כשמתרחשת שגיאה, אפשר לשנות את מדיניות ברירת המחדל לניסיון חוזר על ידי הגדרת המאפיין retry on failure. כתוצאה מכך, המערכת מנסה שוב ושוב לשלוח את האירוע עד שהפונקציה מסתיימת בהצלחה או עד שפסק הזמן לניסיון החוזר מסתיים.
הפעלה או השבתה של ניסיונות חוזרים
כדי להפעיל או להשבית ניסיונות חוזרים, אפשר להשתמש בכלי שורת הפקודה gcloud או במסוף Google Cloud . כברירת מחדל, הניסיונות החוזרים מושבתים.
הגדרת ניסיונות חוזרים באמצעות כלי שורת הפקודה gcloud
כדי להפעיל ניסיונות חוזרים באמצעות כלי שורת הפקודה gcloud, צריך לכלול את הדגל --retry כשפורסים את הפונקציה:
gcloud functions deploy FUNCTION_NAME --retry FLAGS...
כדי להשבית את הניסיונות החוזרים, צריך לפרוס מחדש את הפונקציה בלי הדגל --retry:
gcloud functions deploy FUNCTION_NAME FLAGS...
הגדרת ניסיונות חוזרים מהמסוף
אם יוצרים פונקציה חדשה:
- במסך Create Function (יצירת פונקציה), בקטע Trigger (טריגר), בוחרים את סוג האירוע שישמש כטריגר לפונקציה.
- מסמנים את התיבה Retry on failure (ניסיון חוזר במקרה של כשל) כדי להפעיל ניסיונות חוזרים.
אם אתם מעדכנים פונקציה קיימת:
- בדף Cloud Run functions Overview, לוחצים על שם הפונקציה שרוצים לעדכן כדי לפתוח את המסך Function details, ואז בוחרים באפשרות Edit מסרגל התפריטים כדי להציג את החלונית Trigger.
- מסמנים את התיבה Retry on failure או מבטלים את הסימון שלה כדי להפעיל או להשבית ניסיונות חוזרים.
שיטות מומלצות
בקטע הזה מתוארות שיטות מומלצות לשימוש בניסיונות חוזרים.
שימוש בניסיון חוזר לתיקון שגיאות חולפות
מכיוון שהפונקציה מנסה לפעול שוב ושוב עד שהיא מצליחה, צריך לבצע בדיקות כדי לוודא שאין בקוד שגיאות קבועות כמו באגים, לפני שמפעילים ניסיונות חוזרים. השימוש בנסיונות חוזרים מומלץ לטיפול בכשלים לסירוגין או זמניים, שיש סיכוי גבוה שייפתרו בניסיון חוזר, כמו נקודת קצה של שירות לא יציב או פסק זמן.
הגדרת תנאי סיום כדי להימנע מלולאות אינסופיות של ניסיונות חוזרים
מומלץ להגן על הפונקציה מפני לולאות חוזרות בלתי פוסקות כשמשתמשים בניסיונות חוזרים. כדי לעשות את זה, צריך לכלול תנאי סיום מוגדר היטב, לפני שהפונקציה מתחילה לעבד. שימו לב שהטכניקה הזו פועלת רק אם הפונקציה מתחילה לפעול בהצלחה ויכולה להעריך את תנאי הסיום.
גישה פשוטה ויעילה היא להשליך אירועים עם חותמות זמן ישנות יותר מזמן מסוים. כך אפשר להימנע מהפעלות מוגזמות כשיש כשלים מתמשכים או כשהם נמשכים יותר זמן מהצפוי.
לדוגמה, קטע הקוד הזה מבטל את כל האירועים שהתרחשו לפני יותר מ-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 כזה, עליכם להשתמש במזהה האירוע כמפתח האידמפוטנטיות.
- אידמפוטנטיות פועלת היטב עם מסירה אחת לפחות, כי היא מאפשרת לנסות שוב בצורה בטוחה. לכן, שיטה מומלצת כללית לכתיבת קוד מהימן היא לשלב בין אידמפוטנטיות לבין ניסיונות חוזרים.
- חשוב לוודא שהקוד שלכם הוא אידמפוטנטי באופן פנימי. לדוגמה:
- מוודאים שהמוטציות יכולות לקרות יותר מפעם אחת בלי לשנות את התוצאה.
- שליחת שאילתה למצב מסד הנתונים בעסקה לפני שינוי המצב.
- חשוב לוודא שכל תופעות הלוואי הן אידמפוטנטיות.
- הטלת בדיקה טרנזקציונלית מחוץ לפונקציה, ללא תלות בקוד. לדוגמה, אפשר לשמור את הסטטוס במקום כלשהו כדי לתעד שמזהה אירוע מסוים כבר עבר עיבוד.
- טיפול בקריאות כפולות לפונקציות מחוץ לפס. לדוגמה, אפשר להגדיר תהליך ניקוי נפרד שמנקה אחרי קריאות כפולות לפונקציות.
הגדרת מדיניות הניסיון החוזר
בהתאם לצרכים של הפונקציה, יכול להיות שתרצו להגדיר את מדיניות הניסיון החוזר ישירות. כך תוכלו להגדיר כל שילוב של האפשרויות הבאות:
- לקצר את חלון הניסיון החוזר מ-7 ימים ל-10 דקות בלבד.
- משנים את זמן ההשהיה המינימלי והמקסימלי באסטרטגיית הניסיון החוזר עם השהיה אקספוננציאלית.
- משנים את אסטרטגיית הניסיון החוזר לניסיון חוזר מיידי.
- מגדירים נושא להודעות ללא מוצא.
- מגדירים מספר מקסימלי ומספר מינימלי של ניסיונות מסירה.
כדי להגדיר את מדיניות הניסיון החוזר:
- כתיבת פונקציית HTTP.
- משתמשים ב-Pub/Sub API כדי ליצור מינוי ל-Pub/Sub, ומציינים את כתובת ה-URL של הפונקציה כיעד.
מידע נוסף על הגדרה ישירה של Pub/Sub זמין במאמר טיפול בכשלים במסמכי התיעוד של Pub/Sub.
השלבים הבאים
- פריסת פונקציות Cloud Run
- קריאה לפונקציות של טריגר Pub/Sub.
- הפעלת פונקציות של טריגר Cloud Storage.
- מדריך לשימוש בפונקציות Cloud Run עם Pub/Sub
- מדריך לשימוש בפונקציות Cloud Run עם Cloud Storage