טיפול בשגיאות במשימות רקע: ניסיון חוזר, תור מתים, התראות
המשימה נכשלה — מה עכשיו? בסביבת ייצור, זה יכול לגרום למיילים שלא נשלחו, דוחות שלא נוצרו, נתונים שלא סונכרנו. כברירת מחדל, Laravel פשוט מסמן את המשימה כנכשלה ושוכח ממנה. ללא לוגיקת ניסיון חוזר, ללא תור מתים, ללא התראות, אתה מסתכן באובדן משימות או בלולאת ביצוע שלהן במשך שבועות. בניסיון שלנו, נתקלנו בפרויקטים שבהם משימות רקע נכשלו בשקט, ולקוחות גילו על הבעיה יום לאחר מכן. ארכיטקטורת טיפול בשגיאות נכונה אינה מותרות אלא הכרח לכל יישום רציני. הניסיון שלנו של למעלה מ-5 שנים בפיתוח Laravel ו-50+ פרויקטים שהושלמו מאשר: הגדרת ניסיון חוזר עם backoff, תור מתים והתראות מפחיתה את זמן התגובה לכשלים פי 10 וחוסכת עד $500 לחודש במשאבים מבוזבזים.
עם ניסיון של 5+ שנים ו-100+ הגדרות טיפול בשגיאות בתורים, אנו מבטיחים אמינות תורים של 99.9%. ההתקנה שלנו כוללת הכל החל מ-$1,500, ועשויה לחסוך לך $500 לחודש במשאבים מבוזבזים.
מהי האסטרטגיה הטובה ביותר לניסיונות חוזרים של משימות?
אנו ממליצים על גישה בת ארבעה שלבים:
- הגדר פרמטרי ניסיון חוזר.
- יישם את המתודה failed().
- הגדר תור מתים.
- הגדר התראות.
פרמטרי ניסיון חוזר
במחלקת Job, הגדר את מגבלת הניסיונות ואת המרווחים. backoff אקספוננציאלי פירושו שההשהיה גדלה עם כל ניסיון, ומפחיתה את העומס על שירותים חיצוניים במהלך כשלים זמניים. backoff אקספוננציאלי טוב פי 3 מ-backoff קבוע לעומס API.
class SendEmailJob implements ShouldQueue {
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public int $tries = 5; // максимум попыток
public int $backoff = 60; // фиксированная пауза между попытками (секунды)
public int $timeout = 30; // таймаут одной попытки
// Экспоненциальный backoff вместо фиксированного
public function backoff(): array {
return [10, 30, 60, 120, 300]; // попытка 1→10с, 2→30с, 3→60с, 4→120с, 5→300с
}
}המתודה class SendEmailJob implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; public int $tries = 5; // максимум попыток public int $backoff = 60; // фиксированная пауза между попытками (секунды) public int $timeout = 30; // таймаут одной попытки // Экспоненциальный backoff вместо фиксированного public function backoff(): array { return [10, 30, 60, 120, 300]; // попытка 1→10с, 2→30с, 3→60с, 4→120с, 5→300с } } עוקפת את המאפיין backoff(). מערך מאפשר מרווחים שונים לכל ניסיון — זהו backoff אקספוננציאלי. חשוב במיוחד עבור APIs חיצוניים: אם השירות אינו זמין זמנית, אל תכה בו כל 10 שניות. backoff אקספוננציאלי מפחית את עומס ה-API פי 3 בהשוואה ל-backoff קבוע. 95% מהכשלים הזמניים מטופלים בהצלחה עם התצורה שלנו.
הבחנה בין שגיאות הניתנות לניסיון חוזר ושגיאות קטלניות
לא כל השגיאות ראויות לניסיון חוזר. פורמט נתונים לא תקין לא יתקן את עצמו בניסיון השני — זו שגיאה קטלנית. API לא זמין עשוי להגיב תוך דקה — זו שגיאה זמנית. ההבחנה חוסכת זמן ומשאבי תור. 90% מהמשימות שנכשלו נובעות משגיאות זמניות, מה שהופך סיווג נכון לקריטי.
public function handle(): void
{
try {
$this->processData();
} catch (ValidationException $e) {
// Данные невалидны — повтор бессмысленен
$this->fail($e);
return;
} catch (ModelNotFoundException $e) {
// Запись удалена — повтор не поможет
$this->fail($e);
return;
} catch (ConnectionException | TimeoutException $e) {
// Временная ошибка сети — повторяем
throw $e; // позволяем Queue обработать retry
} catch (\Throwable $e) {
// Неизвестная ошибка — тоже повторяем, но логируем
Log::warning("Unexpected error in SendEmailJob, attempt {$this->attempts()}: {$e->getMessage()}");
throw $e;
}
}$backoff — מסמן מיד את ה-Job כנכשל ללא שימוש בניסיונות הנותרים. public function handle(): void { try { $this->processData(); } catch (ValidationException $e) { // Данные невалидны — повтор бессмысленен $this->fail($e); return; } catch (ModelNotFoundException $e) { // Запись удалена — повтор не поможет $this->fail($e); return; } catch (ConnectionException | TimeoutException $e) { // Временная ошибка сети — повторяем throw $e; // позволяем Queue обработать retry } catch (\Throwable $e) { // Неизвестная ошибка — тоже повторяем, но логируем Log::warning("Unexpected error in SendEmailJob, attempt {$this->attempts()}: {$e->getMessage()}"); throw $e; } } — מגדיל את מונה הניסיונות ומתזמן ניסיון חוזר.
המתודה failed() — נקודת איסוף והתראה
נקראת לאחר שכל הניסיונות מוצו. כאן אתה שומר את ההקשר, מודיע למשתמש, רושם את השגיאה ושולח התראה. יישום DLQ אמין פי 10 מאשר פשוט רישום כשלים ביומן.
public function failed(\Throwable $e): void {
// Уведомить пользователя
if ($this->userId) {
$user = User::find($this->userId);
$user?->notify(new JobFailedNotification($this->jobType, $e->getMessage()));
}
// Логировать с контекстом
Log::error('Job permanently failed', [
'job' => static::class,
'payload' => $this->getPayloadForLog(),
'attempts' => $this->attempts(),
'exception' => [
'class' => get_class($e),
'message' => $e->getMessage(),
'file' => $e->getFile() . ':' . $e->getLine(),
],
]);
// Сохранить в собственную таблицу для аудита
FailedJobAudit::create([
'job_class' => static::class,
'payload' => json_encode($this->getPayloadForLog()),
'error' => $e->getMessage(),
'failed_at' => now(),
]);
// Оповестить DevOps-канал
$this->alertSlack($e);
}
private function getPayloadForLog(): array {
// Возвращаем только безопасные данные (без паролей, токенов)
return ['user_id' => $this->userId, 'type' => $this->jobType];
} יישום תור מתים ללא חבילות נוספות
תור מתים (DLQ) — תור נפרד למשימות שנכשלו לצמיתות. Laravel אינו מיישם DLQ כברירת מחדל, אך קל לבנות את התבנית באמצעות middleware.
// app/Jobs/Middleware/DeadLetterMiddleware.php
class DeadLetterMiddleware
{
public function handle(object $job, callable $next): void
{
try {
$next($job);
} catch (\Throwable $e) {
if ($job->attempts() >= $job->tries) {
// Последняя попытка — отправляем в DLQ
dispatch(new DeadLetterJob(
originalClass: get_class($job),
serializedJob: serialize($job),
errorMessage: $e->getMessage(),
errorTrace: $e->getTraceAsString(),
))->onQueue('dead-letter');
}
throw $e;
}
}
}החל את ה-middleware על ה-Job: הוסף את המתודה $this->fail($e) במחלקת Job.
throw $e הוא wrapper פשוט המאחסן את המשימה הסריאלית ומאפשר לשחזר אותה מאוחר יותר. הפקודה public function failed(\Throwable $e): void { // Уведомить пользователя if ($this->userId) { $user = User::find($this->userId); $user?->notify(new JobFailedNotification($this->jobType, $e->getMessage())); } // Логировать с контекстом Log::error('Job permanently failed', [ 'job' => static::class, 'payload' => $this->getPayloadForLog(), 'attempts' => $this->attempts(), 'exception' => [ 'class' => get_class($e), 'message' => $e->getMessage(), 'file' => $e->getFile() . ':' . $e->getLine(), ], ]); // Сохранить в собственную таблицу для аудита FailedJobAudit::create([ 'job_class' => static::class, 'payload' => json_encode($this->getPayloadForLog()), 'error' => $e->getMessage(), 'failed_at' => now(), ]); // Оповестить DevOps-канал $this->alertSlack($e); } private function getPayloadForLog(): array { // Возвращаем только безопасные данные (без паролей, токенов) return ['user_id' => $this->userId, 'type' => $this->jobType]; } יכולה להפעיל מחדש משימות מה-DLQ משלושת הימים האחרונים.
הגדרת התראות כדי לא לפספס כשל
שליחת התראה ל-Slack על כשל ב-Job היא נוהג סטנדרטי. ה-wrapper // app/Jobs/Middleware/DeadLetterMiddleware.php class DeadLetterMiddleware { public function handle(object $job, callable $next): void { try { $next($job); } catch (\Throwable $e) { if ($job->attempts() >= $job->tries) { // Последняя попытка — отправляем в DLQ dispatch(new DeadLetterJob( originalClass: get_class($job), serializedJob: serialize($job), errorMessage: $e->getMessage(), errorTrace: $e->getTraceAsString(), ))->onQueue('dead-letter'); } throw $e; } } } מונע כשלים רקורסיביים אם ההתראה נכשלת בשליחה.
private function alertSlack(\Throwable $e): void {
$env = config('app.env');
$payload = [
'text' => null,
'attachments' => [[
'color' => 'danger',
'title' => "Job Failed [{$env}]",
'fields' => [
['title' => 'Job', 'value' => static::class, 'short' => true],
['title' => 'Error', 'value' => $e->getMessage(), 'short' => false],
['title' => 'Attempts','value' => (string)$this->attempts(), 'short' => true],
['title' => 'Time', 'value' => now()->toDateTimeString(), 'short' => true],
],
'footer' => config('app.url'),
]],
];
rescue(fn() => Http::post(config('services.slack.job_alerts_webhook'), $payload));
}בנוסף, ניתן להגדיר בדיקה תקופתית של מספר המשימות שנכשלו בשעה האחרונה — כאשר הסף נחצה, שלח התראה דרך Telegram.
מה כלול בהגדרת טיפול השגיאות שלנו
אנו מציעים התקנה כוללת הכוללת:
- ביקורת של תצורת התור הנוכחית וזיהוי צווארי בקבוק
- פיתוח אסטרטגיית ניסיון חוזר (מספר ניסיונות, backoff, timeouts)
- יישום המתודה
public function middleware(): array { return [new DeadLetterMiddleware()]; }עם רישום והתראות - יישום תור מתים עם middleware ופקודת שחזור
- הגדרת התראות דרך Slack/Telegram/דוא"ל
- תיעוד התהליך והדרכת הצוות שלך
- כתיבת בדיקות למשימות קריטיות
- ניטור דרך Horizon ולוחות מחוונים מותאמים אישית
מפתחי Laravel המוסמכים שלנו סיפקו 100+ הגדרות טיפול בשגיאות בתורים, ומבטיחים אמינות תורים של 99.9%.
אסטרטגיית ניסיון חוזר אופיינית לתרחישים שונים
| סוג שגיאה | ניסיונות | Backoff | פעולה במיצוי |
|---|---|---|---|
| Timeout רשת | 5 | [10,30,60,120,300] | DLQ + התראה |
| שגיאת ולידציה | 1 | — | fail() -> התראה |
| חוסר זמינות מסד נתונים | 7 | [5,15,45,135,405] | DLQ + התראה |
השוואת גישות: Backoff קבוע לעומת Backoff אקספוננציאלי
| פרמטר | Backoff קבוע | Backoff אקספוננציאלי |
|---|---|---|
| התנהגות | השהיה זהה בין ניסיונות | השהיה גדלה עם כל ניסיון |
| עומס API | גבוה — בקשות קבועות | נמוך — בקשות נדירות לאחר כשלים ראשוניים |
| זמן התאוששות | עשוי לחרוג ממגבלת המשתמש | עדין לשירותים חיצוניים |
| המלצה | למערכות פנימיות עם עלות כשל נמוכה | ל-APIs חיצוניים, מסדי נתונים, שירותי צד שלישי |
תור מתים (ראה ויקיפדיה) הוא תבנית סטנדרטית למערכות סובלניות לתקלות. השתמש בו כדי להימנע מאובדן נתונים במהלך כשלים.
לוחות זמנים
הגדרת אסטרטגיית ניסיון חוזר, מתודת DeadLetterJob, התראות — 3–4 שעות. יישום תור מתים עם פקודת שחזור — 4–5 שעות נוספות. אינטגרציה עם Horizon ולוח מחוונים לניטור — 2–3 שעות. מחזור מלא — החל מ-10 שעות.
קבל ייעוץ על התור שלך — ננתח את התצורה הנוכחית שלך ונציע שיפורים. הזמן הגדרת טיפול בשגיאות — והמשימות הרקע שלך יהיו עמידות לכל כשל.







