- Laravel מציעה API מאוחד לתורים עם מספר מנהלי התקנים (מסד נתונים, Redis, SQS...) והפרדה בין חיבורים לתורים לוגיים.
- משימות מאפשרות להעביר משימות כבדות לרקע, עם תמיכה בסידור מודלים, תוכנות ביניים, משימות חד-פעמיות, הצפנה, אצוות ומחרוזות.
- ניהול העובדים מתבצע באמצעות queue:work, Supervisor ופקודות כגון queue:pause/queue:continue, השולטות בניסיון חוזר, פסקי זמן וסדרי עדיפויות.
- ישנה מערכת אקולוגית שלמה לכשלים, ניטור ובדיקות (failed_jobs, queue:retry, queue:monitor, Queue::fake) שהופכת תורים לאמינים בייצור.
עבודה עם תורים ב-Laravel היא כמעט חובה בכל פרויקט רציני: שליחת מיילים, עיבוד קבצי CSV, יצירת קבצי PDF, שינוי גודל תמונות, קריאה ל-API חיצוניים... אם כל זה נעשה בבקשת ה-HTTP הראשית, האתר הופך לאיטי, מופיעות 500 שגיאות, והמשתמש בסופו של דבר בוהה נואשות במסך ריק.
החדשות הטובות הן ש-Laravel מציעה תשתית תורים חזקה וגמישה המאפשרת לך להאציל משימות כבדות לתהליכים ברקע, לנהל אותם לפי סדר עדיפויות, לקבץ אותם לקבוצות, לנטר אותם, לנסות אותם שוב כשהם נכשלים, ואפילו להשהות זמנית עובדים לצורך תחזוקה. בואו נבחן מקרוב (ולא נשאיר דבר מחוץ לרשימה) כיצד להפיק ממנה את המרב, כולל כיצד להשהות, לחדש ולשלוט בהתנהגותה בייצור.
מושגים בסיסיים: חיבורים, תורים ומנהלי התקנים
ב-Laravel, לפני שעוצרים משהו, צריך להבין את ההבדל בין חיבורי תור ותורים עצמם. בתוך config/queue.php יש לך מערך connections היכן שאתה מגדיר כל backend: מסד נתונים, redis, beanstalkd, sqs, סינכרון, null, גיבוי לגיבוי, rabbitmq (אם תוסיפו את זה) וכו'.
כל חיבור יכול להכיל כמה תורים לוגיים (שמות כמו default, emails, high, low, importsהתכונה queue כל חיבור מציין מהו תור ברירת המחדל שאליו יגיעו העבודות אם לא צוין אחרת. onQueue()כך תוכלו לעשות דברים כמו:
- תור מהיר עבור מיילים או עסקאות קצרות.
- עוד עדיפות נמוכה עבור משימות ארוכות כגון עיבוד סרטונים או דוחות גדולים.
- תורים מחולקים לפי סוג משימה:
imports,notificaciones,reportes, וכו '
כאשר אתה מפעיל את העובד עם php artisan queue:workכברירת מחדל, הוא מאזין לתור שצוין כ- בְּרִירַת מֶחדָל על החיבור שנבחר. אם ברצונך לתעדף, תוכל להעביר מספר תורים מופרדים בפסיקים:
php artisan queue:work redis --queue=high,default,low
בדרך זו, העובד מעבד תחילה את ה-high , לאחר מכן את ה-default , ורק לאחר מכן את ה-low , וזה קריטי כאשר המערכת עמוסה ואינך רוצה שדוח של 30 דקות יחסום מיילים בעלי עדיפות גבוהה.
מנהלי תורים ודרישות מוקדמות
Laravel מציעה API מאוחד עבור מספר מנהלי התקנים של תורים , אך לכל אחד מהם תכונות ותלויות ספציפיות משלו. בחירת הדרייבר הנכון היא קריטית לפני שדנים בהשהיית עובדים.
מסד נתונים של נהגים
הנהג מסד נתונים הוא מאחסן את העבודות בטבלת SQL (בדרך כלל jobsקל להגדיר אותו ומושלם לפרויקטים קטנים או סביבות פיתוח שבהן לא רוצים להתעסק עם Redis או SQS.
שלבים בסיסיים:
- צור את ההגירה:
php artisan queue:table(או בגרסאות חדשות יותר, זה מגיע כcreate_jobs_tableבְּרִירַת מֶחדָל). - הפעלת הגירות:
php artisan migrate. - הגדר QUEUE_CONNECTION=מסד נתונים ב
.envולאשר בconfig/queue.phpכי'default' => env('QUEUE_CONNECTION','database').
זה נוח וחזק מספיק, אבל עם הרבה תורים והרבה תעבורה זה יכול להפוך לצוואר בקבוק , מכיוון שהכל עובר דרך אותו מסד נתונים יחסי.
נהג רדיס
הדרייבר של Redis הוא סוס העבודה הסטנדרטי בייצור. הוא מהיר, תומך בתכונות מתקדמות (נעילה, הגבלת קצב, נעילות אטומיות, Horizon וכו'), וניתן להרחבה בקלות רבה.
נקודות חשובות:
- עליך להגדיר את חיבור Redis ב-
config/database.php. - האפשרויות
serializerycompressionמרדיס הם אינם תואמים עם נהג התור. - אם אתה משתמש אשכול Redisשמות התורים חייבים לכלול תגית גיבוב, לדוגמה
{default}כך שכל המפתחות בתור הזה ייפלו לאותו משבצת. - אפשרות
block_forזה מאפשר לעובד לחסום בזמן שהוא ממתין לעבודות למשך מספר שניות, במקום לבצע תשאול רציף: חוסך במעבד ומפחית שאילתות לשרת Redis.
לדוגמה:
'redis' => ,
אם תשים block_for = 0העובד נשאר חסום ללא הגבלת זמן עד שתהיה משרה פנויה, דבר שכדאי לזכור אם ברצונך שהוא יגיב במהירות לאותות כמו SIGTERM או השהיית פקודות.
דרייברים אחרים: SQS, Beanstalkd, null, failover…
בנוסף למסדי נתונים ו-redis, Laravel תומכת במערכות נוספות :
- SQS של אמזוןתורים מנוהלים ב-AWS, עם תמיכה ב- FIFOקבוצות הודעות, ביטול כפילויות והגדרות נראות מתקדמות. דורש את ערכת פיתוח התוכנה (SDK).
aws/aws-sdk-php. - גבעול שעועיתמהיר וקל מאוד, באמצעות החבילה
pda/pheanstalk. - לסנכרןמבצע את המשימות מייד באותו תהליך. שימושי בפיתוח או בדיקות, אבל בייצור זה כמעט כמו לא להשתמש בתורים בכלל.
- ריק: מבטל משימות ברגע שהן "נשלחו". משמש להשבתה מוחלטת של מערכת התורים.
- כשלזה מאפשר לך להגדיר מספר חיבורים, ואם העיקרי נכשל, Laravel ינסה לדחוף את המשימה לחיבור הבא ברשימה. מושלם לסביבות שבהן ה- זמינות גבוהה הוא קריטי.
ברמה של השהייה וחידוש פעולתם של עובדים, ההיגיון כמעט זהה ללא קשר לדרייבר, מכיוון שהוא מנוהל מפקודות Artisan וממחלקת Worker , ולא מקצה האחורי של התור.
יצירת משימות וסידור מודלים
Un עבודה ב-Laravel, זוהי פשוט מחלקה שמקיפה משימה שיש לבצע בתור. היא נוצרת בדרך כלל באמצעות Artisan ונשמרת ב- app/Jobs.
כדי ליצור אחד:
php artisan make:job ProcessPodcast
המחלקה שנוצרה מכשירים ShouldQueue ובדרך כלל משתמש בתכונות Dispatchable, InteractsWithQueue, Queueable y SerializesModelsהלב נמצא בשיטה handle()וזה מה שהעובד עושה כשהעבודה מגיעה.
תכונה חזקה במיוחד היא סידור אוטומטי של מודלים של Eloquent . אם מקבלים מודל בבנאי ה-Job, Laravel לא מוסיף את כל האובייקט לתור, אלא רק את המזהה שלו. כאשר ה-job מעובד, הוא בונה מחדש את המודל ממסד הנתונים עם הקשרים שלו טעונים.
דוגמה אופיינית (בפסאודו-קוד, כדי להימנע מחזרה מילה במילה):
- הקבלן מקבל פודקאסט $פודקאסט.
- התכונה
SerializesModelsהוא אחראי על שמירת תעודת הזהות. - En
handle(AudioProcessor $processor)אתה עושה את העבודה הקשה (עיבוד אודיו, למשל).
אם אינך רוצה שהקשרים במודל יהיו מסודרים בסידור (כך שהמטען לא יהיה עצום), תוכל להשתמש ב- $model->withoutRelations() או ב-PHP 8 התכונה # על הנכס שקודם על ידי הקבלן.
הזרקת תלות בשיטת הידית
יתרון נוסף הוא שאתה יכול הזרק שירותים ישירות לתוך שיטת הידית (כאילו היה זה בקר), מכיוון שגם Laravel משתמשת במכולת השירות ב-Jobs. לדוגמה, ניתן לבקש AudioProcessorלקוחות HTTP, מאגרים וכו', והמסגרת דואגת לפתור אותם.
אם אתם זקוקים לשליטה עדינה במיוחד במה שנקרא ה- handleאתה יכול להשתמש Container::bindMethod() אצל ספק שירות ולהגדיר את לוגיקת ההזרקה בעצמך, אבל ברוב המקרים אין צורך לסבך את הדברים כל כך.
תוכנת ביניים לעבודה
בדיוק כמו שלנתיבים יש תוכנה ביניים, גם לעבודות יכולות להיות תוכנה ספציפית שרצים לפני ואחרי העבודה. זה מפחית מאוד את כמות הקוד החוזר על עצמו בשיטות handle() וזה שימושי במיוחד עבור:
- הגבלת קצב עם רדיס.
- להימנע חפיפות של עבודות הנוגעות לאותו משאב.
- חריגים רגילים עבור ממשקי API לא יציבים.
- דילוג על משימות בתנאים מסוימים (
Skipתוכנת ביניים).
תוכנת ביניים ממוקמת בדרך כלל ב app/Jobs/Middleware ומוחזרים מהמתודה middleware() מתוך איוב. דוגמה רעיונית:
public function middleware(): array { return ;
סוגים מיוחדים של משימות: ייחודיות, מוצפנות ונעולות
Laravel מאפשר לך לסמן משימות מסוימות כ... ייחודי כדי להבטיח שלא יהיו שני מקרים זהים בתור בו זמנית. זה מושג על ידי יישום הממשק ShouldBeUnique o ShouldBeUniqueUntilProcessingותומך בהגדרת מפתח uniqueId() וזמן uniqueFor.
זה אידיאלי לדברים כמו:
- חישוב מחדש של אינדקס החיפוש של מוצר: פעם אחת בלבד לכל מוצר עד שזה ייגמר.
- סנכרון עם שירותים חיצוניים שאינם מאפשרים בקשות בו-זמניות עבור אותו משאב.
מתחת, Laravel משתמש ב- מנעול אטומי של מטמון (Redis, memcached, מסד נתונים וכו'). ניתן גם לבחור את מנהל ההתקן של המטמון עבור מנעול זה באמצעות uniqueVia().
בנוסף, יש את הממשק ShouldBeEncrypted, שבאמצעותו אתה מציין שהעבודה המלאה היא הזינו את הקוד שלכם לפני הכניסה לתורשימושי מאוד בעת העברת נתונים רגישים ואינך רוצה שהם יעברו בטקסט רגיל דרך Redis, SQS או מסד הנתונים.
שיגור, עיכוב וביצוע משימות במצבים שונים
לאחר שהמשימה שלך מוגדרת, אתה מפעיל אותה עם Job::dispatch() מבקרים, מאזיני אירועים, פקודות Artisan, או אפילו סגירות. הפרמטרים שאתה מעביר אל dispatch הם הולכים ישירות אל בונה איוב.
וריאציות מעניינות:
dispatchIf()ydispatchUnless()כדי להתנות את המשלוח בשורה אחת.delay()כך שלא ניתן לעבד את העבודה עד תאריך/שעה ספציפיים.dispatchAfterResponse()oafterResponse()endispatch()סגירות, אשר דוחות את הביצוע עד לשליחת תגובת HTTP, אך בתוך אותו תהליך PHP.dispatchSync()להריץ את ה-Job באופן סינכרוני (מבלי לעבור דרך התור) אך להשתמש שוב באותו מחלקה.- קשרים מיוחדים כגון
deferredobackgroundלביצוע לאחר התגובה, באותו תהליך או בתהליך PHP חדש.
לבסוף, ניתן לציין חיבור ותור באמצעות שיטות משורשרות onConnection() y onQueue(), בין אם בעת השיגור ובין אם בתוך בונה העבודות.
בקרה עדינה: ניסיונות חוזרים, ביטול פעולות, פסקי זמן וטיפול בשגיאות
חלק גדול מהעבודה עם תורים ב-Laravel הוא להחליט כמה פעמים לנסות מחדש עבודה , כמה זמן להמתין בין ניסיון, ומתי להתייחס אליה כאל כישלון.
אפשרויות זמינות:
--triesenqueue:workכדי לקבוע את מספר הניסיונות המרבי עבור כל העבודות של אותו עובד (אלא אם כן המשימה מגדירה ניסיון משלה)$triesotries()).$triesאו שיטהtries()במחלקה Job לבקרה לכל עבודה.retryUntil()לומר: "נסה שוב כמה שתרצה עד שהפעם הזו, אחרי זה, זה כישלון." אם זה עולה בקנה אחד עםtries, פקודהretryUntil.$maxExceptionsלניתוק כאשר הבעיה נובעת מחריגים חוזרים ולא מטופלים.$timeouto--timeoutכדי לציין כמה שניות משימה יכולה לפעול לפני שה-worker הורג את תהליך הצאצא.$failOnTimeoutכדי לסמן את המשימה ככשלה במקרה של פסק זמן.$backoffהשיטהbackoff()כדי להגדיר כמה זמן יש להמתין בין ניסיונות חוזרים, אפילו עם ערכים אקספוננציאליים.
אם עבודה זורקת חריג שלא נתפס, Laravel... משחרר בחזרה לתוך הזנב עד שימוצו כל הניסיונות שהוגדרו. ניתן לשלוט בכך באופן ידני באמצעות release($segundos) o fail($exception) כאשר ברצונך לסמן במכוון עבודה ככשלה.
משימות שנכשלו, טבלת failed_jobs וגיזום
כאשר עבודה ממצה את מאמציו, היא נחשבת לאדם עבודה כושלת ולראוול מכניס אותו לטבלה failed_jobs (או ב-DynamoDB אם תגדיר את זה ככה). טבלה זו מאחסנת את ה-UUID, החיבור, התור, מטען העבודה והחריגה שנזרקה.
פקודות שימושיות:
php artisan queue:failedכדי לרשום את אלה שנכשלו.php artisan queue:retry <id|all>כדי לנסות אותם שוב.php artisan queue:forget <id>למחוק אחד.php artisan queue:flushכדי למחוק הכל (עם אפשרות לשעות בגרסאות חדשות יותר).php artisan queue:prune-failed --hours=48כדי לגזום רשומות ישנות באופן אוטומטי.
ב-Jobs ניתן להגדיר שיטה failed(Throwable $exception) לניקוי נוסף: הודע למשתמש, בטל פעולות חלקיות, כתיבת יומני רישום מיוחדים וכו'. שים לב שנוצר מופע חדש של המשימה כדי לקרוא ל- failed()לכן, אין להסתמך על שינויי רכוש שבוצעו ב handle().
אצווה של משימות: אצוות של משימות ושילובן עם מחרוזות
כאשר אתם צריכים להפעיל מאות או אלפי עבודות קשורות (לדוגמה, ייבוא קובץ CSV ענק בקטעים, עיבוד קבצים בקבוצות, ניקוי נתונים וכו'), מומלץ להשתמש בפונקציונליות ה-batch של Laravel .
זרימה בסיסית:
- ליצור את הטבלה
job_batchesעםphp artisan queue:batches-table && php artisan migrate. - סמנו את העבודות שיהיו חלק מאצוות באמצעות המאפיין
Batchable. - להשתמש
Bus::batch()לשלוח קבוצת משימות, עם קריאה חוזרתthen,catchyfinally. - גש לאצווה מכל משימה באמצעות
$this->batch()ולבדוק אם זה בוטל. - אפשרות להוסיף עוד עבודות לאצווה מתמשכת עם
$this->batch()->add().
קבוצות משתלבות בצורה נהדרת עם שרשראות : ניתן לכלול שרשראות בתוך קבוצה אחת או קבוצות כאלמנטים של שרשרת גדולה יותר. זה שימושי במיוחד כשרוצים שמספר שרשראות יפעלו "במקביל" ויפעילו משהו לאחר שכולן הושלמו.
ביצוע עובדים, סדרי עדיפויות ופריסה
כל הקסם של התורים קורה בגלל שיש לך עובד אחד או יותר מריץ את הפקודה queue:work (o queue:listen(אם כי האחרון פחות יעיל). תהליכים אלה הם דמונים ארוכי טווח שמאזינים לתורים שלהם.
אפשרויות מפתח של queue:work:
- קשר:
php artisan queue:work redis,queue:work database, וכו ' - קולה:
--queue=high,default,lowלתעדף. - -אַחַד עָשָׂר: מעבד עבודה בודדת ויוצא.
- מקסימום עבודות: מעבד X משימות ומסיים (כדי להפעיל מחדש עובדים מעת לעת ולפנות זיכרון).
- –זמן מקסימלי: מעבד במשך N שניות ולאחר מכן מסתיים.
- -לִישׁוֹןשניות של המתנה כאשר אין עבודות זמינות.
- –עצור-כאשר-ריקמרוקן את התור ונסגר בצורה נקייה (אידיאלי בסביבות Docker או במשימות חד פעמיות).
- –פסק זמןשניות של פסק זמן לכל משימה, כפי שהזכרנו קודם.
- –כוח: לעבד משימות גם אם האפליקציה נמצאת במצב תחזוקה.
מכיוון שתהליכים אלה פועלים זמן רב, בתהליכי ייצור, כמעט תמיד משתמשים במנטר תהליכים כמו Supervisor (בלינוקס) כדי להפעיל את X workers, להפעיל אותם מחדש אם הם קורסים ולנהל יומני רישום. בלוק תצורה טיפוסי של Supervisor עבור Laravel מגדיר:
- הפקודה עם
php artisan queue:work ...בֵּטוֹן. - numprocs כדי לציין כמה עובדים להשיק במקביל.
- נתיבי יומן, משתמש, הפעלה מחדש אוטומטית וכו'.
השהייה וחידוש תורים ב-Laravel
בואו נגיע למה שלעתים קרובות חשוב בתחזוקה: כיצד להשהות עובדים בתור מבלי לעצור אותם בפתאומיות ומבלי להשאיר משימות לא גמורות. Laravel מציעה מספר מנגנונים, חלקם חדשים יותר, חלקם קלאסיים, כדי לשלוט בכך.
השהייה: אותות תור: השהייה והפרעה
בגרסאות האחרונות, Laravel מוסיפה פקודות ספציפיות כדי להשהות ולחדש את ביצוע ה- workers:
php artisan queue:pause: מורה למערכת להפסיק לקבל עבודות חדשות, אך לתת לאלו שכבר נמצאות בתהליך להסתיים.php artisan queue:continue: ממשיך לעבד משימות חדשות מהנקודה בה הפסיק.
פקודות אלו פועלות על ידי שליחת אותות ל-workers דרך מנגנון המטמון בו משתמש Laravel באופן פנימי. לכן חשוב שיהיה מנהל התקן מטמון מוגדר כהלכה (memcached, redis, database, file וכו') גם אם תור המטמון בפועל הוא משהו אחר.
חשוב: השהיית תור או עובד אינה מבטלת או מוחקת משימות שכבר נמצאות בתור ; היא פשוט מונעת הסרה של משימות נוספות מהתור כל עוד ההשהיה נמשכת.
סקר של שיבושים והשפעה על ביצועים
בכל פעם שעובד מסיים עבודה, Laravel בודק את המטמון עבור אותות הפעלה מחדש, השהיה או המשך . עלות זו זניחה, אך היא קיימת. אם אתם ממוקדים מאוד בביצועים ויודעים שלא תצטרכו להשהות או להפעיל מחדש עובדים מפקודות, תוכלו להשבית את הסקר הזה על ידי קריאה ל:
Queue::withoutInterruptionPolling();
או אפילו להתאים מאפיינים סטטיים של Illuminate\Queue\Worker כדי להשבית נגיעות ספציפיות (ניתן להפעלה מחדש, ניתן להשהייהעם זאת, אם תעשה זאת ולאחר מכן תנסה להשתמש queue:pause o queue:restartהעובדים הם לא יגלוזוהי החלטה עיצובית שיש לשקול אותה בכובד ראש.
השהיה ידנית באמצעות Supervisor או Docker
בנוסף לפקודות של Laravel עצמה , בסביבות ייצור, תהליכים לעיתים קרובות "מושהים" בעקיפין:
- עצירת התוכנית ב-Supervisor (
supervisorctl stop laravel-worker:*), אשר הורג או עוצר את העובדים. - שימוש –עצור-כאשר-ריק בפריסות כדי לאפשר לעובדים לסיים את מה שנמצא בתור ואז להיסגר.
- ב-Docker, מתן אפשרות למכולה לעבד עם
--stop-when-emptyואז זה נכבה.
ההבדל עם queue:pause על ידי עצירה מוחלטת של התהליכים, תפסיק לעבד משימות עד שהעובדים יפעלו מחדש. הפסקה פנימית ניתן לשמור את התהליכים בזיכרון, תוך כדי המתנה לפקודה continueזה שימושי לפריסות מהירות של ביטול פעולות או לתקופות תחזוקה קצרות.
תנועות מסד נתונים ו- after_commit
טעות קלאסית: שליחת משימות בתוך תנועות מסד נתונים . אם לא תיזהרו, ה-worker עלול לבצע את המשימה לפני ביצוע ה-commit, והמודל או הנתונים שאתם מצפים להם עשויים עדיין לא להיות במסד הנתונים.
כדי להימנע מהריקוד הזה, יש לך שתי אפשרויות:
- להפעיל
'after_commit' => trueבתצורת חיבור התור. לפיכך, כל משימה שנשלחת במסגרת טרנזקציה נדחית באופן פנימי עד לאישורה. - השתמש בשיטות לפי משימה
afterCommit()obeforeCommit()בעת שיגור, כדי לאלץ את ההתנהגות רק בעבודות ספציפיות.
אם העסקה מתבטלת, העבודות שנשלחו במהלכה נמחקות מבלי להיכנס לתור, וזה בדיוק מה שבדרך כלל רצוי כדי לשמור על עקביות.
ניטור, ניקוי תורים ובדיקות
ברגע שתורים פועלים ברצינות, לא מספיק פשוט לזרוק משרות ולשכוח מזה; צריך לנטר ולבדוק איך הכל מתנהג.
כלים ופקודות שימושיות:
queue:clearכדי לרוקן תור ספציפי (SQS, Redis, מסד נתונים).queue:monitorכדי לנטר את מספר העבודות בתורים מסוימים ולהפעיל את האירועQueueBusyאם הם חורגים מסף מסוים.- אירועי עבודה כמו
Queue::before,Queue::after,Queue::loopingyQueue::failingכדי לחבר לוגיקה מותאמת אישית (יומנים, מדדים, החזרות למצב אחר וכו'). - זיופים בבדיקות באמצעות
Queue::fake()yBus::fake()כדי לוודא שעבודות, מחרוזות או אצוות נשלחו מבלי לבצע אותן בפועל.
בבדיקות ניתן לציין דברים כמו "משימה זו הועברה לתור X פעמים" , "שרשרת עם עבודות אלה נשלחה בסדר זה" או "אף משימה מסוימת לא הועברה לתור", מה שמספק אבטחה רבה בזרימות מורכבות.
עם כל המערכת האקולוגית הזו - מנהלי התקנים כמו Redis או SQS, משימות מעוצבות היטב, תוכנות ביניים לכוונון עדין, אצוות, שרשראות, תצורת ניסיונות חוזרים, עובדים המנוהלים על ידי מפקח ופקודות כמו queue:pause y queue:continue— Laravel מאפשרת לך לשלוט בעיבוד רקע עד למילימטר, משליחת דוא"ל טיפוסית ועד לארכיטקטורות תור מבוזרות עם גיבוי, ומבטיחה שהאפליקציה שלך תמשיך להגיב במהירות בשכבת ה-HTTP בזמן שעבודה כבדה מתבצעת מתחת מבלי לחסום את המשתמש.