תבנית היוצא
Outbox הוא תבנית ארכיטקטונית שבה שירות דומיין כותב שינוי עסקי ואת האירוע המקביל בעסקה מקומית אחת למאגר שלו. פרסום האירוע לאוטובוס/תור החיצוני מבוצע באופן אסינכרוני על ידי תהליך מאובטח נפרד (publer) שקורא את טבלת ה-outbox ומוסר את הרשומות. גישה זו מבטלת את המרוץ ”קודם לבסיס הנתונים, אחר כך לאוטובוס” ומספקת משלוח אמין גם במקרה של כשלים.
1) מתי ליישם
בכושר:- מיקרו-רווחים ומונוליתיים מודולריים עם אירועים בין ההקשרים.
- יש לוודא כי ”המדינה תקבע ↔ לא ניתן יהיה לאבד את האירוע”.
- אנחנו צריכים אימפוטנציה ומשלוח מחדש מבוקר.
- עסקאות גלובליות קשות על מספר משאבים הן קריטיות (טובות יותר מ-TCC/סאגות עם חוזים מפורשים).
- אין מקור מוקדש של אמת (מדינה אינה מאוחסנת במקום שבו האירוע נוצר).
2) מטרות ומאפיינים
כתיבה אטומית: תקליט דומיין + outbox - בעסקה אחת.
לפחות פעם אחת: אנחנו מאפשרים חזרה, להוציא הפסד.
חוסר אונים צרכני: הגנה מפני לוקח בצד המנוי.
יעיל בדיוק-פעם אחת: הושג על ידי השילוב של outbox + idempotent הצרכן + dedup.
טלמטריה ברורה, תאמת עסקאות ואירועים.
3) סכימת נתונים (דוגמה)
sql
-- Domain table (example: orders)
CREATE TABLE orders (
id UUID PRIMARY KEY,
tenant_id TEXT NOT NULL,
status TEXT NOT NULL,
total_amount NUMERIC(12,2) NOT NULL,
updated_at TIMESTAMP NOT NULL DEFAULT now()
);
-- Outbox
CREATE TABLE outbox (
id UUID PRIMARY KEY, -- event_id aggregate_type TEXT NOT NULL, -- 'order'
aggregate_id UUID NOT NULL, -- order_id tenant_id TEXT NOT NULL,
type TEXT NOT NULL, -- 'OrderCreated'
payload JSONB NOT NULL, -- serialized headers event JSONB NOT NULL DEFAULT '{}':: jsonb,
occurred_at TIMESTAMP NOT NULL, -- time in domain transaction available_at TIMESTAMP NOT NULL, -- earliest publish time (backoff)
published_at TIMESTAMP, - is filled by the attempts INT NOT NULL DEFAULT 0,
error TEXT
);
CREATE INDEX ON outbox (available_at) WHERE published_at IS NULL;
CREATE INDEX ON outbox (tenant_id, available_at) WHERE published_at IS NULL;
4) שכבת יישום
pseudo begin tx domainChange () # INSERT/UPDATE in domain table insert into outbox (event) # event with aggregate/tenant commit tx keys
אם ההתחייבות היא מוצלחת, האירוע בתיבת היוצא מובטח להתקיים. אם הבקשה תיפול לאחר התחייבות, המוציא לאור יתעדכן.
5) מוציא לאור (קורא # מוציא לאור)
משימות:- לקרוא אירועים שלא פורסמו באופן מחזורי ("published _ at IS NULL" ו- "view _ at <= עכשיו (), חבורות.
- נסה לפרסם לאוטובוס/תור; אם מצליח, מארק ”פורסם _ at”.
- במקרה של שגיאה - הגדל "ניסיונות", שים "זמין _ at' לעתיד (גיבוי מעריכי), כתוב" שגיאה ".
- כבד את המגבלות על הדיירים/מפתחות (הגינות), אל תחסום את המוצר.
pseudo loop:
events = select from outbox where published_at is null and available_at <= now()
order by occurred_at limit BATCH_SIZE for update skip locked
for e in events:
try:
broker. publish(topicFor(e), serialize(e. payload), headers(e))
markPublished(e. id, now())
except Retryable:
backoff = computeBackoff(e. attempts)
reschedule(e. id, now()+backoff, attempts+1, last_error)
except NonRetryable:
moveToDLQ (e) or markError (e) # by sleep (POLL_INTERVAL) policy
6) אידמפוטנטיות ושכפול
בצד הצרכני (Inbox/Idempotency Store):sql
CREATE TABLE inbox (
consumer_name TEXT,
event_id UUID,
processed_at TIMESTAMP NOT NULL,
PRIMARY KEY (consumer_name, event_id)
);
אלגוריתם: בעת קבלת אירוע, תחילה נסה ”להכניס” בתיבת הדואר הנכנס; אם יש סכסוך מרכזי, האירוע כבר טופל. הבא הוא היגיון עסקי.
בצד המוציא לאור: "Idempotency-Key" בכותרות (לדוגמה, "event _ id') כך שהאוטובוס/ברוקר/פרוקסי יכול לסנן שכפולים.
7) סדר וסיבתיות
סדר מקומי על ידי 'agregate _ id' מסופק על ידי מיון' occried _ at 'ופרסום ”על ידי מפתח”.
עבור רישום אוטובוסים עם מחיצה - מחיצה עם מפתח ”agregate _ id'/” terant _ id' כך שהאירועים של צבירה אחת הם באותה החלוקה.
אם הסדר קריטי, הימנע ממרוצי הוצאה לאור חד-מרכזיים.
8) CDC (לכידת נתונים שינוי)
במקום מוציא לאור פעיל, אתה יכול להשתמש ב-CDC: המנוע קורא את יומן ההעברות של בסיס הנתונים ומתרגם את קווי ה-outbox לאוטובוס. חסרונות - סיבוך של פעולה וקשר לפרטים של DBMS. שתי הגישות תקפות; לבחור לפי כשירות ו-SLO.
9) שגיאות, DLQ ו ־ Redrive
tryable (רשת, גבולות) - להגדיל את 'attempts', לדחות את 'avable _ at' (גיבוי מעריכי + jitter).
Non-Retryable (תוכנית/חוזה לא תקפים) - הועבר ל-DLQ/Dead-Letter Topice עם metadata עשיר.
Safe Redrive: Batches, Rate-Limit, Validation of the Scheme, עדיפות מתחת לתעבורת הייצור.
10) עמידות מרובה ומגבלות
תגיות נדרשות: "דייר _ id'," תוכנית "," אזור "- בתיבת חוץ. כותרות ".
הגינות לכל דייר: המו "ל מפיץ את" חלונות "הפרסומים ואת מגבלות הניסיונות לדיירים.
תושבות: לאחסן תיבות יוצא באותו אזור כמו נתוני תחום; פרסומים בין-אזוריים - סיכומים/אגרגטים בלבד.
11) בטיחות וציות
מהדורת PII במטען/כותרות על מדיניות דייר/אזור.
חתימה/הצפנה של המטען אם האוטובוס זר.
ביקורת כל מעברי מצב: נוצר, פורסם, שגיאה, שרטוט מחדש.
12) יכולת תצפית
מדדים:- lag ('עכשיו - occurred_at' p50/p95/p99).
- אחוזי הצלחה, אחוזי שגיאה, הפצת סיבות.
- גודל יוצא (מספר לא מפורסם), חזרות/שניות
- גרפים לכל דייר.
- Correlation 'event _ id'/' aggregate _ id'/' saga _ id'; משתרע ”db-tx”, ”לפרסם”, ”retry”.
- אנוטציות: 'ניסיון', 'backoff _ ms',' diq = נכון '.
- תקליטים קצרים להצלחה; פרטים מלאים לשגיאה/דרייב.
13) בדיקה ותוהו ובוהו
מבחן אטומיטי: ”ליפול” באופן מלאכותי לאחר ביצוע עסקת שטח לפני הפרסום - האירוע חייב להשתחרר מאוחר יותר.
מבחן שכפול: אנו מפרסמים את אותו אירוע מספר פעמים - הצרכן מבצע בדיוק אפקט אחד (תיבת דואר אלקטרוני).
מבחן סדר: קבוצה של אירועים לפי סימון צבירה אחד - סימון רצף/אידמפוטנטיות.
כאוס: כשל ברוקר, עלייה במסד הנתונים Latency, מוציא לאור מפוצל-מוח, שעון רזה.
14) תבניות הגדרות (דוגמה)
yaml outbox:
poll_interval_ms: 200 batch_size: 200 order_by: occurred_at backoff:
strategy: exponential_full_jitter initial_ms: 250 max_ms: 10_000 max_attempts: 20 fairness:
per_tenant_parallelism: 4 per_key_serial: true
publisher:
rate_limit_per_sec: 500 headers:
idempotency_key: event_id schema_version: v3 dlq:
enabled: true topic: myapp. events. dlq include_metadata:
- error
- attempts
- source_table
- tenant_id
- aggregate_id
15) אינטגרציה עם סאגות ונסיגות
Extbox - ”תחבורה ביטחונית” עבור סאגה צעדים: עסקה מקומית כותבת אפקט ופקודה/אירוע; פרסום - אמין ומסומן.
מדיניות חזרה וחזרה חייבת להיות עקבית עם ”Retry-After” ושובר מעגל; הימנע ”סערת המגש מחדש”.
16) שגיאות אופייניות
הם כותבים אירוע לאחר שהמדינה מתחייבת - אובדן במהלך נפילה הוא אפשרי.
אין אינדקסים/ארכיון in 'outbox' publishing latency rough.
מו "ל בלי" סקיפ נעול "או בלי לשדרג - תחרות וחסימה.
חוסר אידאות בקרב הצרכנים - שכפולים ותופעות לוואי.
PII ערבוב ללא מיסוך ב DLQ/יומנים.
תור הוצאה לאור גלובלי אחד ללא הגינות - דייר ”רועש” מאט את כולם.
חוסר ניטור פיגור * השפלה סמויה.
17) בחירת אסטרטגיה מהירה
רמת התחלה: סקרים ממסד הנתונים, 100-500 קבוצות, גיבוי מלא-עצבני, תיבת דואר אלקטרוני לצרכנים.
עומס גבוה: CDC מתוך יומן העסקה, מחודד על ידי "terenant _ id _ aggregate _ id', WFQ על ידי דייר.
סדר קפדני על ידי צבירה: פרסום סדרתי למפתח (mutex), חלוקה של הנושא עם מפתח.
ציות/PII: הצפנת מטען, מהדורת DLQ, תיבת חוץ אזורית.
18) רשימת בדיקות לפני המכירה
[ ] Domain משתנה וכותב 'outbox' להתרחש באותה עסקה.
[ המו "ל מטפל בחבורות, משתמש ב" דלג נעול "]
[ ] הצרכנים הם אידמפוטנטים (טבלה ”תיבת דואר אלקטרוני ”/deadup log).
[ ] DLQ ושחרור מאובטח מוגדרים.
[ ] לאג/שגיאה ומדדי התראה על סף p95/p99.
[ סדר מפתח ] מובטח (חבורות/סדרות).
[ ] ארכיון/שימור תיבה "ורישומים ברורים שפורסמו.
[ ] מדיניות מח "ש וביקורת המדינה.
[ ] בדיקות טיפה בין להתחייב ולפרסם, כפילויות וסדר.
[ ] תיעוד חוזה אירוע (סכמות/גרסאות/תאימות).
סיכום
התבנית היוצאת הופכת את החבילה ה ”שברירית” של ”DB ↔ Bus” לצינור אמין: קיבעון מצב אטומי, מובטח (אם כי ”לפחות פעם אחת”) פרסום, מנויים מפוטמים ושחזור מבוקר. עם טלמטריה נכונה, גבולות, ומשמעת סכימה, זה נותן התנהגות מעשית בדיוק פעם אחת, הפחתת המורכבות של עסקאות מבוזרות והגברת עמידות המערכת להתרסקויות ועומס שיא.