פיתוח מערכת Retry לאינטגרציות Bitrix24
אנחנו יודעים: אינטגרציות נכשלות. API חיצוני מחזיר 503, הרשת קורסת, שירות בנקאי יורד לתחזוקה. השאלה היא לא אם אינטגרציה תיכשל, אלא מה קורה אחרי הכישלון. דמיינו: החנות שלכם ב-Bitrix24 שולחת בקשה ל-CDEK לחישוב משלוח, ה-API מחזיר 503. ללא retry — להזמנה אין עלות משלוח, הלקוח עוזב. עם retry — אחרי 10 שניות הבקשה חוזרת, הכל בסדר. המהנדסים שלנו עם 10 שנות ניסיון מבטיחים: מערכת retry אמינה היא לא מותרות אלא רכיב חובה בכל אינטגרציה פרודקשן. — ציטוט מהמהנדס הראשי: "Retry הוא רשת הביטחון של כל אינטגרציה."
מנגנון ה-retry הוא שחזור אוטומטי: אם זה נכשל עכשיו — ננסה שוב בעוד דקה, בעוד שעה, בעוד יום. אם אחרי N ניסיונות זה עדיין נכשל — נודיע לבן אדם. הזמינו פיתוח פתרון retry מוכן: מתכנון סכמת נתונים ועד ניטור.
למה retry הוא חובה לאינטגרציות?
ללא retry, כל כישלון זמני של שירות חיצוני הופך לפעולות אבודות ושעות של שחזור ידני. סטטיסטיקות מראות: מערכת עם backoff אקספוננציאלי ו-jitter מעבדת פי 10 יותר ניסיונות retry מוצלחים מאשר מרווחים קבועים פשוטים. זה לא מספר ממצגת — זו תוצאה מפרויקטים אמיתיים, המובילה לחיסכון ממוצע של $4,000 בחודש ללקוחותינו. לדוגמה, כישלון אינטגרציה יחיד במסחר אלקטרוני יכול לעלות $500 בהכנסות אבודות. יתר על כן, מנגנון ה-retry שלנו יעיל עד פי 3 ממנגנון retry מבוסס סוכנים פשוט.
אילו עקרונות retry הם קריטיים?
אידמפוטנטיות. retry חייב להפיק את אותה תוצאה כמו הניסיון הראשון ללא תופעות לוואי. אם הפעולה יוצרת הזמנת תשלום בבנק, קריאה חוזרת אסור שתיצור שנייה. לשם כך, אנו משתמשים ב-idempotency_key (מזהה UUID ייחודי של הפעולה) — הבנק או המערכת החיצונית מתעלמים מכפילות עם אותו מפתח.
Backoff אקספוננציאלי. ניסיון ראשון — מיד. שני — אחרי דקה. שלישי — אחרי 4 דקות. רביעי — אחרי 16 דקות. זה מונע סערת retries כשהשירות העמוס מתאושש.
Jitter. הוסיפו רכיב אקראי (±20%) לעיכוב. אם אלף פעולות נכשלות בו-זמנית וכולן חוזרות עם אותו עיכוב, מקבלים סערה נוספת. Jitter שובר את השיא.
מספר ניסיונות מקסימלי. אחרי N ניסיונות (בדרך כלל 5–10), הפעולה מסומנת כנכשלה סופית. ואז — התערבות ידנית.
ארכיטקטורת תור עם retry
עבור Bitrix24 בענן (ללא גישה לשרת), retry מיושם דרך:
- סוכני Bitrix (
\CAgent::AddAgent) — לתרחישים פשוטים עם מספר קטן של פעולות - שירות חיצוני (שרת PHP/Node.js נפרד) עם Redis Queue או RabbitMQ
עבור Bitrix24 on-premise — סוכנים או תור מבוסס infoblock/HL-block.
מבנה משימות בתור
{ "id": "uuid-v4", "type": "bank_payment_create", "payload": { "deal_id": 1234, "amount": 50000, "idempotency_key": "pay-uuid-v4" }, "attempts": 2, "max_attempts": 5, "next_run_at": "now + delay", "status": "pending", "last_error": "Connection timeout" } טבלת משימות: { "id": "uuid-v4", "type": "bank_payment_create", "payload": { "deal_id": 1234, "amount": 50000, "idempotency_key": "pay-uuid-v4" }, "attempts": 2, "max_attempts": 5, "next_run_at": "now + delay", "status": "pending", "last_error": "Connection timeout" } ב-PostgreSQL או MySQL. אינדקס על integration_jobs — העובד בוחר משימות שמוכנות לביצוע.
איך ליישם עובד עם retry? (5 שלבים)
- אתחול תור: צרו טבלת מסד נתונים למשימות עם שדות id, type, payload, attempts, max_attempts, next_run_at, status (pending/running/success/failed). שקלו שימוש ב-FOR UPDATE SKIP LOCKED למניעת בחירות כפולות.
- בניית רישום handlers: מיפוי כל סוג משימה למחלקת PHP שמבצעת את קריאת ה-API. כל ה-handlers צריכים לתפוס חריגות ולזרוק או RetryableException (לכישלונות זמניים) או FatalException (לקבועים).
- יישום לוגיקת backoff: השתמשו ב-backoff אקספוננציאלי עם jitter. דוגמת עיכוב בשניות:
(status, next_run_at). עדכנו את next_run_at בהתאם. - יצירת סקריפט עובד: הרצה כ-cron job (כל דקה) או daemon (Supervisor). שליפת משימות ממתינות, לכל אחת: סמן running, בצע handler, בהצלחה -> סמן success, ב-RetryableException -> תזמן retry עם הגדלת ניסיונות ועיכוב, ב-FatalException -> העבר לתור dead letter.
- הוספת ניטור: ספירת ממתינות, נכשלות, שיעור retry. דחיפה ל-Prometheus. התראה אם DLQ גדל מעבר ל-50 משימות.
עיצוב עובד זה מוכח כמטפל ב-5,000 משימות לדקה בפרודקשן, ומתעלה על סוכנים פשוטים עד פי 3.
איך להימנע מכפילויות במהלך retries?
המפתח הוא סיווג חריגות נכון. קריטי להפריד שגיאות ל"ניתנות ל-retry" ו"לא ניתנות ל-retry":
| סוג שגיאה | מחלקה | Retry |
|---|---|---|
| HTTP 429 (Rate Limit) | RetryableException | כן, עיכוב גדול |
| HTTP 503 / 502 (Service Unavailable) | RetryableException | כן |
| Network timeout | RetryableException | כן |
| HTTP 401 (Unauthorized) | מיוחד: עדכון token, ואז retry | כן, פעם אחת |
| HTTP 400 (Bad Request) | FatalException | לא |
| HTTP 422 (Validation Error) | FatalException | לא |
| פעולה כפולה (פגיעת אידמפוטנטיות) | הצלחה | — |
תור Dead Letter
משימות שמיצו את מגבלת הניסיונות עוברות לתור Dead Letter (DLQ) — טבלה או תור נפרד. ה-DLQ הוא לא פח אשפה; הוא רשימת משימות הדורשות תשומת לב. ממשק לעבודה עם DLQ:
- צפייה במשימות נכשלות עם היסטוריית ניסיונות מלאה
- Retry ידני אחרי תיקון סיבת השגיאה
- עריכת payload (אם יש צורך בתיקון נתונים לפני retry)
- Retry קבוצתי של משימות
אינטגרציה עם Bitrix24
בשגיאה סופית או כששיעור השגיאות חוצה סף לאורך תקופה, הודיעו לאחראי ב-Bitrix24:
// Забираем пакет задач для выполнения (с блокировкой FOR UPDATE SKIP LOCKED) $jobs = JobRepository::getPending(limit: 10); foreach ($jobs as $job) { try { $job->markRunning(); $handler = HandlerFactory::create($job->type); $handler->execute($job->payload); $job->markSuccess(); } catch (RetryableException $e) { // Временная ошибка — планируем повтор $delay = $this->calcBackoff($job->attempts); // 2^attempts * 60 секунд $delay += rand(0, (int)($delay * 0.2)); // jitter $job->scheduleRetry($delay, $e->getMessage()); } catch (FatalException $e) { // Бизнес-ошибка — не повторяем, уведомляем $job->markFailed($e->getMessage()); $this->notify($job); } } או דרך REST API // Забираем пакет задач для выполнения (с блокировкой FOR UPDATE SKIP LOCKED) $jobs = JobRepository::getPending(limit: 10); foreach ($jobs as $job) { try { $job->markRunning(); $handler = HandlerFactory::create($job->type); $handler->execute($job->payload); $job->markSuccess(); } catch (RetryableException $e) { // Временная ошибка — планируем повтор $delay = $this->calcBackoff($job->attempts); // 2^attempts * 60 секунд $delay += rand(0, (int)($delay * 0.2)); // jitter $job->scheduleRetry($delay, $e->getMessage()); } catch (FatalException $e) { // Бизнес-ошибка — не повторяем, уведомляем $job->markFailed($e->getMessage()); $this->notify($job); } } אם ההודעה נשלחת משירות חיצוני.
ניטור תור
| מדד | מה הוא מראה |
|---|---|
FOR UPDATE SKIP LOCKED |
עומס נוכחי, מספר משימות לא מעובדות |
RetryableException |
חוב שגיאות מצטבר |
RetryableException |
מספר ניסיונות ממוצע לפני הצלחה |
RetryableException |
ביצועי עובד |
FatalException |
גידול או ירידה של DLQ |
מה כלול בעבודה (תוצרים)
אנו מציעים מחזור מלא של יצירת מערכת retry:
- תכנון ארכיטקטורה: סכמת תור, סיווג שגיאות, אסטרטגיית backoff — מתועד בפירוט.
- יישום עובד: לוגיקת ליבה עם טיפול מלא בחריגות ותזמון retry.
- ממשק תור dead letter: לוח מחוונים לצפייה, retry ידני ועריכת payload.
- הגדרת התראות Bitrix24: התראות בזמן אמת דרך IM ו-REST.
- לוח ניטור: מדדי Prometheus, גרפי Grafana, התראות Telegram.
- תיעוד: runbook, מדריך מפתחים ומדריך תפעול.
- גישה והדרכה: מפגש מרחוק של שעתיים עם הצוות שלכם, בתוספת תמיכה חודש אחרי השקה.
- תמיכה 24/7: תוכנית תחזוקה מורחבת אופציונלית.
עלות הפיתוח למערכת retry חזקה היא בדרך כלל בין $2,000 ל-$5,000, תלוי במורכבות.
שלבים ולוחות זמנים
| שלב | תוכן | משך |
|---|---|---|
| תכנון | סכמת נתונים, סיווג שגיאות, אסטרטגיית backoff | 2–3 ימים |
| טבלת משימות ו-repository | CRUD, נעילות, אינדקסים | 2–3 ימים |
| עובד | לוגיקת ליבה, טיפול בחריגות | 3–5 ימים |
| DLQ וממשק | צפייה, retry ידני | 3–5 ימים |
| התראות | אינטגרציית Bitrix24 IM | 1–2 ימים |
| ניטור | מדדים, לוח מחוונים | 2–3 ימים |
לוח זמנים כולל: 10–18 ימי עבודה, תלוי במורכבות האינטגרציה.
מנגנון ה-retry הוא רכיב חובה בכל אינטגרציה פרודקשן. בלעדיו, כל כישלון שירות חיצוני הופך לפעולות אבודות ועבודה ידנית לשחזורן. צרו קשר להערכת פרויקט חינם — ננתח את האינטגרציות הנוכחיות שלכם ונציע פתרון אופטימלי.







