Cloud Scheduler כולל כותרות HTTP של זיהוי משימות וזמן תזמון בבקשות HTTP ו-App Engine, שמאפשרות לבטל כפילויות של הפעלות חוזרות ולהפוך את ה-handlers של היעד לאידמפוטנטיים. מכיוון ש-Cloud Scheduler מספק מסירה של לפחות פעם אחת, ביטול כפילויות של בקשות מונע מה-handler או מממשקי ה-API של Google Cloud במורד הזרם לעבד את אותה הפעלה מתוזמנת יותר מפעם אחת, כשמתרחשות הפסקות זמניות ברשת או כשנגמר הזמן הקצוב לתפוגה.
איך פועלת מניעת כפילויות בבקשות
בקשות כפולות לאותה הרצה מתוזמנת יכולות להתרחש במצבים הבאים:
- יעדים שפועלים לאורך זמן: לשירות היעד לוקח יותר זמן להגיב מאשר המועד האחרון לניסיון (
attemptDeadline) של המשימה, מה שגורם ל-Cloud Scheduler להגיע לזמן הקצוב לתפוגה ולנסות שוב את המשימה בזמן שהניסיון הראשון עדיין פועל. ליעדי HTTP, ערך ברירת המחדל שלattemptDeadlineהוא 3 דקות, ואפשר להגדיר אותו עד 30 דקות. אם זמן הביצוע יכול לחרוג מ-30 דקות, צריך להתאים אתattemptDeadlineלזמן הקצוב לתפוגה של היעד, או לעבד עבודה ארוכה באופן אסינכרוני. - אישורי רשת שאבדו או ניסיונות חוזרים של שרת proxy זמני: היעד מקבל ומעבד את הבקשה, אבל הפרעה זמנית ברשת או ניתוק של שרת ה-proxy מונעים מ-Cloud Scheduler לקבל את תגובת ה-HTTP
2xx. גם אם מגדירים אתretryCountלערך0, מסירה לפחות פעם אחת פירושה שיעד עדיין יכול לקבל בקשות כפולות במהלך כשלים זמניים בהעברה.
כדי להפוך את ה-handler של המשימה לאידמפוטנטי ולבטל כפילויות של בקשות חוזרות, משתמשים בכותרות ה-HTTP ש-Cloud Scheduler כולל באופן אוטומטי בבקשות ליעדי HTTP ו-App Engine:
| כותרת | תיאור |
|---|---|
X-CloudScheduler-JobName |
מזהה המשימה הקצר (JOB_ID) מ-projects/PROJECT_ID/locations/LOCATION_ID/jobs/JOB_ID. |
X-CloudScheduler-ScheduleTime |
במשימות שצוינו בפורמט הכותרת הזו לא מובטחת למשימות שמשתמשות בפורמט התזמון החלופי של |
דרישת אימות בנקודת הקצה (endpoint) של היעד
הכותרות של X-CloudScheduler-* לא מאומתות, וכל גורם ששולח קריאה יכול להגדיר אותן. אל תשתמשו בכותרות האלה לאימות. במקום זאת, צריך לאבטח את נקודת הקצה בהתאם לסוג היעד:
- יעדי HTTP: מגדירים את המשימה כך שתשלח אסימון OIDC או OAuth, ודורשים אימות בנקודת הקצה. למידע נוסף, ראו שימוש באימות עם יעדי HTTP.
- יעדים של App Engine: הגבלת הגישה ל-handler על ידי הגדרת
login: adminבקובץapp.yamlוהגדרת כללי חומת האש של App Engine.
שימוש בחיפושי כותרות לא תלויי-רישיות
האותיות הרישיות בכותרות יכולות להשתנות בין גרסאות HTTP ותשתיות שונות (לדוגמה, X-Cloudscheduler-Jobname ב-HTTP/1.1 או אותיות קטנות x-cloudscheduler-jobname ו-x-cloudscheduler-scheduletime בסביבות HTTP/2 כמו Cloud Run). תמיד צריך להשתמש בחיפושי כותרות לא תלויי-רישיות ב-handler.
הפגת עמימות של מזהי משימות בפרויקטים או באזורים שונים
Cloud Scheduler לא שולח באופן אוטומטי כותרות של מזהה פרויקט או אזור. נקודת קצה (endpoint) אחת של handler יכולה לקבל בקשות מכמה פרויקטים או אזורים שחולקים את אותו JOB_ID. במקרה כזה, מעבירים את מזהה הפרויקט או האזור ב-HttpTarget.headers, ב-AppEngineHttpTarget.headers או בנתיב כתובת ה-URL או בפרמטרים של השאילתה, וכוללים את הערכים האלה במפתח ביטול הכפילויות.
ביטול כפילויות של בקשות ב-HTTP handler
לפני שמטמיעים את ביטול הכפילויות של הבקשות ב-handler, צריך לבצע את הפעולות הבאות:
- מגדירים את לוח הזמנים של המשימה בפורמט
unix-cron(לדוגמה,*/5 * * * *במקוםevery 5 minutes) כדי ש-Cloud Scheduler ישלח את הכותרתX-CloudScheduler-ScheduleTime. - מגדירים מאגר נתונים קבוע שתומך בטרנזקציות או בכתיבה מותנית אטומית, כמו Firestore, Spanner או Memorystore.
מגדירים מדיניות של אורך חיים (TTL) באוסף או בטבלה (לדוגמה, בשדה
expire_at) כדי למחוק רשומות ישנות של ביטול כפילויות אחרי חלון הניסיון החוזר של המשימה (maxRetryDuration). מדיניות ה-TTL של Firestore מוחקת רשומות שפג תוקפן באופן אסינכרוני, בדרך כלל תוך 24 עד 72 שעות. כדי לאפשר לניסיונות החוזרים להחזיר את ההרצות ממכונות שקרסו לפני ניקוי ה-TTL, צריך גם לאחסן חותמת זמן של תפוגת החכירה (כמוlease_expires, שמוגדרת לפחות לזמן הקצוב לתפוגה של הבקשה של היעד ו-attemptDeadline) ולהעריך אותה בטרנזקציה.
כדי לעבד כל הפעלה מתוזמנת פעם אחת בלבד ב-HTTP handler:
- קוראים את
X-CloudScheduler-JobNameו-X-CloudScheduler-ScheduleTimeמכותרות ה-request header הנכנסת ומגבבים אותם למפתח ביטול כפילויות באורך קבוע (run_id). - מריצים עסקה במאגר הנתונים כדי לבדוק את הרשומה של מפתח ביטול הכפילויות ולתבוע עליה בעלות:
- אם הרשומה קיימת והערך של
statusהואcompleted, מחזירים מיד קוד סטטוס HTTP200 OK(או204 No Content) בלי להריץ את הלוגיקה של המשימה, כדי ש-Cloud Scheduler יפסיק לנסות שוב. - אם הרשומה קיימת, הערך של
statusהואin_progressוההשכרה (lease_expires) לא הסתיימה, מחזירים קוד סטטוס של שגיאה שניתן לנסות שוב, כמו409 Conflict, כדי ש-Cloud Scheduler ימשיך להפעיל את מדיניות הניסיונות החוזרים במקרה שהניסיון הנוכחי ייכשל. הימנעו מהחזרת503 Service Unavailableמיעדי App Engine, כי App Engine מתייחס ל-503כשגיאת מערכת ומנסה שוב לשלוח את הבקשה בלי לספור אותה כחלק מ-retryCount. - אם הרשומה לא קיימת או שתוקף ההשכרה שלה פג, כותבים את הרשומה עם
statusשמוגדר ל-in_progress, מזהה ניסיון ייחודי (attempt_id), חותמת זמן של תפוגת ההשכרה (lease_expires) וחותמת זמן של תפוגת TTL (expire_at).
- אם הרשומה קיימת והערך של
אחרי שתובעים בעלות על הרשומה, מריצים את הלוגיקה של המשימה. בעסקה, מוודאים שהערך
attempt_idשל הרשומה עדיין תואם לניסיון הנוכחי לפני שמשנים את הרשומה:- אם הלוגיקה של המשימה מצליחה, מעדכנים את
statusל-completedומחזירים קוד סטטוס HTTP200 OK. - אם לוגיקת המשימה נכשלת עם שגיאה שאפשר לנסות שוב, מחקו את רשומת ביטול הכפילויות לפני החזרת קוד סטטוס
5xx, כדי ש-Cloud Scheduler יוכל לנסות שוב את ההרצה בלי להמתין עד שתוקף ההשכרה יפוג.
- אם הלוגיקה של המשימה מצליחה, מעדכנים את
דוגמה ל-HTTP handler
בדוגמה הבאה ב-Python (ל-Cloud Run או לפונקציות Cloud Run) נעשה שימוש בטרנזקציה של Firestore עם השכרה כדי לתבוע הרצות ולבטל כפילויות של בקשות חוזרות למשימות.
הדוגמה הזו לא מוכנה לייצור. לפני הפריסה, צריך לבדוק את הקוד ולהתאים אותו לצרכים שלכם:
מניעת בקשות כפולות ליעדי API של Google Cloud
כש-Cloud Scheduler מפעיל נקודת קצה ל-API של Google Cloud (*.googleapis.com) ישירות דרך HTTP, ה-API של היעד מתעלם מהכותרות X-CloudScheduler-JobName ו-X-CloudScheduler-ScheduleTime. לדוגמה, קריאה ל-workflowexecutions.googleapis.com כדי להתחיל הרצה של Workflows, ל-run.googleapis.com כדי להריץ משימה של Cloud Run או ל-cloudbuild.googleapis.com כדי להריץ טריגר לפיתוח גרסת Build של Cloud Build, תתחיל הרצה חדשה בכל ניסיון חוזר.
כדי לשמור על אמינות בסביבת הייצור, צריך לאכוף אידמפוטנטיות בשירות שנמצא הכי נמוך בשרשרת ומשנה את המצב. אם מבטלים כפילויות של בקשות רק בשכבה במעלה הזרם, חלון הניסיונות החוזרים מצטמצם. אבל אם בקשה במורד הזרם מצליחה ותגובת ה-HTTP אובדת ברשת, המתקשר במעלה הזרם לא יכול לקבוע אם הפעולה הושלמה, ויכול להיות שהוא עדיין ינסה שוב לשלוח את הבקשה.
כדי למנוע הפעלות כפולות כשמתזמנים יעדי API ב- Google Cloud , השתמשו בגישות הבאות יחד כדי להשיג אידמפוטנטיות מקצה לקצה:
ניתוב בקשות דרך שרת proxy אידמפוטנטי: במקום לשלוח קריאה לנקודת הקצה (endpoint) של
*.googleapis.comישירות מ-Cloud Scheduler, אתם יכולים לפרוס שירות Cloud Run או שרת proxy של פונקציית Cloud Run משלכם, ולהגדיר את Cloud Scheduler כך שיפעיל את שרת ה-proxy הזה. בשרת ה-proxy, מטמיעים את דפוס התביעה (claim) האטומי של טרנזקציות ב-Python וב-Firestore שמתואר במסמך הזה, בקטע ביטול כפילויות של בקשות ב-HTTP handler.שרת ה-proxy קורא את
X-CloudScheduler-JobNameואתX-CloudScheduler-ScheduleTime, מסיר כפילויות של בקשות חוזרות ומעביר את מפתח הסרת הכפילויות כאסימון אידמפוטנטיות במטען הייעודי (payload) של הבקשה (לדוגמה, בשדהargumentכשמתחילים הפעלה של Workflows).דורשים אימות בשרת ה-proxy (לדוגמה, פורסים באמצעות
--no-allow-unauthenticated) ומגדירים את המשימה של Cloud Scheduler כך שתאמת באמצעות אסימון OIDC. מריצים את שרת ה-proxy בחשבון שירות ייעודי עם תפקידי ניהול הזהויות והרשאות הגישה (IAM) המינימליים הנדרשים (כמוroles/workflows.invokerבתהליך העבודה של היעד ו-roles/datastore.userלרשומות של ביטול כפילויות).אכיפת אידמפוטנטיות בשירותים במורד הזרם: משתמשים באסימון האידמפוטנטיות כדי לבצע בדיקה אטומית או טרנזקציונלית במאגר נתונים כמו Firestore, לפני שמריצים שלבים שמשנים את המצב. מעבירים את האסימון לכל נקודות הקצה של HTTP או לפונקציות Cloud Run שהשירות מפעיל, כדי שגם היעדים האלה יוכלו לבטל כפילויות של ניסיונות חוזרים. בפוסט בבלוג Google Cloud, ביצוע קריאות ל-API פעם אחת בדיוק כשמשתמשים ב-Workflows, תוכלו לקרוא דוגמה מקצה לקצה לשימוש ב-Workflows, בטרנזקציות של Firestore ובפונקציות של Cloud Run.