paint-brush
העברת הודעות אמינה במערכות מבוזרותעל ידי@fairday
37,190 קריאות
37,190 קריאות

העברת הודעות אמינה במערכות מבוזרות

על ידי Aleksei8m2024/03/18
Read on Terminal Reader
Read this story w/o Javascript

יותר מדי זמן; לקרוא

בניית מערכת מבוזרת אמינה, זמינה מאוד וניתנת להרחבה דורשת הקפדה על טכניקות, עקרונות ודפוסים ספציפיים.
featured image - העברת הודעות אמינה במערכות מבוזרות
Aleksei HackerNoon profile picture

בעיית כתיבה כפולה

בניית מערכת מבוזרת אמינה, זמינה מאוד וניתנת להרחבה דורשת הקפדה על טכניקות, עקרונות ודפוסים ספציפיים. התכנון של מערכות כאלה כרוך בהתמודדות עם מספר עצום של אתגרים. בין הנושאים הנפוצים והבסיסיים ביותר היא בעיית הכתיבה הכפולה .


"בעיית הכתיבה הכפולה" היא אתגר שמתעורר במערכות מבוזרות, בעיקר כאשר מתמודדים עם מספר מקורות נתונים או מסדי נתונים שצריכים להיות מסונכרנים. זה מתייחס לקושי להבטיח ששינויי נתונים נכתבים באופן עקבי למאגרי נתונים שונים, כגון מסדי נתונים או מטמונים, מבלי להציג בעיות כמו חוסר עקביות בנתונים, התנגשויות או צווארי בקבוק בביצועים.


ארכיטקטורת השירותים ומסד הנתונים הדפוסים של המיקרו-שירותים לכל שירות מביאים לך יתרונות רבים, כגון פריסה ושינוי קנה מידה עצמאיים, כשלים מבודדים והגדלה פוטנציאלית של מהירות הפיתוח. עם זאת, פעולות דורשות שינויים בין מספר שירותי מיקרו, מה שמאלץ אותך לחשוב על פתרון אמין להתמודדות עם בעיה זו.

כמעט דוגמה אמיתית

בואו נשקול תרחיש שבו התחום שלנו כולל קבלת בקשות להלוואה, הערכתן ולאחר מכן שליחת התראות ללקוחות.


ברוח עיקרון האחריות הבודדת, חוק קונוויי וגישות העיצוב מונעות התחום, לאחר מספר מפגשי סערה באירועים, כל התחום פוצל לשלושה תת-תחומים עם הקשרים מוגבלים מוגדרים בעלי גבולות ברורים, מודלים של תחום ושפה בכל מקום.


על הראשון מוטלת המשימה להטמיע והרכבת בקשות הלוואה חדשות. המערכת השנייה מעריכה את היישומים הללו ומקבלת החלטות על סמך הנתונים המסופקים. תהליך הערכה זה, לרבות בדיקות KYC/KYB, אנטי הונאה ובדיקות סיכוני אשראי, עשוי להיות גוזל זמן, ומחייב את היכולת לטפל באלפי בקשות בו-זמנית. כתוצאה מכך, פונקציונליות זו הועברה לשירות מיקרו ייעודי עם מסד נתונים משלו, המאפשר שינוי קנה מידה עצמאי.

יתר על כן, תת-מערכות אלו מנוהלות על ידי שני צוותים שונים, כל אחד עם מחזורי שחרור משלו, הסכמי רמת שירות (SLA) ודרישות מדרגיות.


לבסוף , שירות הודעות מיוחד קיים כדי לשלוח התראות ללקוחות.



להלן תיאור מעודן של מקרה השימוש העיקרי של המערכת:

  1. לקוח מגיש בקשה להלוואה.
  2. שירות הבקשה להלוואה רושם את הבקשה החדשה בסטטוס "בהמתנה" ומתחיל את תהליך ההערכה באמצעות העברת הבקשה לשירות השומה.
  3. שירות השומה מעריך את בקשת ההלוואה הנכנסת ומודיע לאחר מכן לשירות הבקשה להלוואה על ההחלטה.
  4. עם קבלת ההחלטה, שירות בקשת ההלוואה מעדכן את סטטוס הבקשה להלוואה בהתאם ומפעיל את שירות ההתראות ליידע את הלקוח על התוצאה.
  5. שירות ההתראות מעבד בקשה זו ושולח הודעות ללקוח באמצעות דואר אלקטרוני, SMS או שיטות תקשורת מועדפות אחרות, בהתאם להגדרות הלקוח.


זוהי מערכת די פשוטה ופרימיטיבית במבט ראשון, אבל בואו נצלול לאופן שבו שירות בקשת ההלוואה מעבד את פקודת הגשת הבקשה להלוואה.


אנו יכולים לשקול שתי גישות לאינטראקציות שירות:

  1. First-Local-Commit-Thn-Publish: בגישה זו, השירות מעדכן את מסד הנתונים המקומי שלו (commits) ולאחר מכן מפרסם אירוע או הודעה לשירותים אחרים.

  2. First-Publish-Then-Local-Commit: לעומת זאת, שיטה זו כוללת פרסום אירוע או הודעה לפני ביצוע השינויים במסד הנתונים המקומי.


לשתי השיטות יש חסרונות והן בטוחות רק בחלקן לתקשורת במערכות מבוזרות.


זהו דיאגרמת רצף של יישום הגישה הראשונה.


תחילה-מקומי-תחייב-אחר כך-פרסם


בתרחיש זה, שירות בקשת ההלוואה משתמש בגישת First-Local-Commit-Then-Public , שם הוא מבצע תחילה עסקה ולאחר מכן מנסה לשלוח הודעה למערכת אחרת. עם זאת, תהליך זה חשוף לכישלון אם, למשל, יש בעיות ברשת, שירות ההערכה אינו זמין, או שירות יישום ההלוואה נתקל בשגיאת זיכרון חסר (OOM) וקריסה. במקרים כאלה, ההודעה תאבד, ותותיר את השומה ללא הודעה על בקשת ההלוואה החדשה, אלא אם יושמו צעדים נוספים.


והשני.

תחילה-פרסם-ואז-מקומי-תחייב
בתרחיש First-Publis-אז-Local-Commit , שירות בקשת ההלוואה עומד בפני סיכונים משמעותיים יותר. הוא עשוי ליידע את שירות ההערכה על יישום חדש אך לא יצליח לשמור עדכון זה באופן מקומי עקב בעיות כמו בעיות במסד הנתונים, שגיאות זיכרון או באגים בקוד. גישה זו עלולה להוביל לחוסר עקביות משמעותי בנתונים, שעלולים לגרום לבעיות חמורות, בהתאם לאופן שבו שירות סקירת הלוואות מטפל בבקשות נכנסות.


לכן, עלינו לזהות פתרון המציע מנגנון חזק לפרסום אירועים לצרכנים חיצוניים. אבל, לפני שנעמיק בפתרונות פוטנציאליים, עלינו להבהיר תחילה את סוגי הערבויות להעברת הודעות שניתן להשיג במערכות מבוזרות.

אחריות על משלוח הודעות

ישנם ארבעה סוגים של ערבויות שנוכל להשיג.

  1. אין ערבויות
    אין ערובה שההודעה תועבר ליעד. הגישה First-Local-Commit-When-Publisher היא בדיוק על זה. צרכנים עשויים לקבל הודעות פעם אחת, מספר פעמים, או לעולם לא.

  2. לכל היותר פעם אחת משלוח
    מסירה חד פעמית לכל היותר פירושה שההודעה תימסר ליעד לכל היותר פעם אחת. ניתן ליישם את הגישה First-Local-Commit-Then-Publisher גם בדרך זו עם מדיניות הניסיון החוזר של ניסיונות עם ערך אחד.

  3. לפחות פעם אחת מסירה\צרכנים יקבלו ויעבדו כל הודעה אך עשויים לקבל את אותה הודעה יותר מפעם אחת.

  4. משלוח פעם אחת בדיוק \ משלוח פעם אחת בדיוק אומר שהצרכן יקבל את ההודעה ביעילות פעם אחת.
    מבחינה טכנית, אפשר להשיג עם קפקא עסקאות ויישום אימפוטנטי ספציפי של יצרן וצרכן.


ברוב המקרים, ערבויות למשלוח 'לפחות פעם אחת' מטפלות בבעיות רבות על ידי הבטחת מסירת הודעות לפחות פעם אחת, אך הצרכנים חייבים להיות חסרי אונים. עם זאת, בהתחשב בתקלות הרשת הבלתי נמנעות, כל ההיגיון של הצרכן חייב להיות אימפוטנטי כדי להימנע מעיבוד הודעות כפולות, ללא קשר לערבות של היצרן. לכן, דרישה זו אינה כל כך חיסרון אלא שהיא משקפת את המציאות.

פתרונות

יש המון פתרונות לבעיה הזו, שיש להם יתרונות וחסרונות.

התחייבות דו-שלבית

לפי ויקיפדיה, Two-Phase Commit (2PC) הוא פרוטוקול עסקאות מבוזר המשמש במדעי המחשב ומערכות ניהול מסדי נתונים כדי להבטיח את העקביות והאמינות של עסקאות מבוזרות. זה מיועד למצבים שבהם משאבים מרובים (למשל, מסדי נתונים) צריכים להשתתף בעסקה אחת, והוא מבטיח שכולם יבצעו את העסקה או שכולם יבטלו אותה, ובכך שומר על עקביות הנתונים. זה נשמע בדיוק מה שאנחנו צריכים, אבל ל-Dou-Phase Commit יש כמה חסרונות:

  • אם משאב אחד משתתף אינו מגיב או חווה כשל, ניתן לחסום את התהליך כולו עד לפתרון הבעיה. זה יכול להוביל לבעיות ביצועים וזמינות אפשריות.
  • Two-Phase Commit אינו מספק מנגנוני סובלנות תקלות מובנים. הוא מסתמך על מנגנונים חיצוניים או התערבות ידנית לטיפול בכשלים.
  • לא כל מסדי הנתונים המודרניים תומכים ב-Dou-Phase Commit.

מסד נתונים משותף

הפתרון הברור ביותר לארכיטקטורת שירותי מיקרו הוא להחיל דפוס (או אפילו לפעמים אנטי-דפוס) - מסד נתונים משותף. גישה זו היא אינטואיטיבית מאוד אם אתה זקוק לעקביות עסקאות על פני טבלאות מרובות במסדי נתונים שונים, פשוט השתמש במסד נתונים משותף אחד עבור המיקרו-שירותים הללו.


החסרונות של גישה זו כוללים הצגת נקודת כשל בודדת, מניעת קנה מידה עצמאי של מסד נתונים, והגבלת היכולת להשתמש בפתרונות מסד נתונים שונים המתאימים ביותר לדרישות ולמקרי שימוש ספציפיים. בנוסף, יהיה צורך בשינויים בבסיסי הקוד של שירותי המיקרו כדי לתמוך בצורה כזו של עסקה מבוזרת.

תיבת דואר עסקית

' תיבת הטרנזקציות ' היא דפוס עיצובי המשמש במערכות מבוזרות כדי להבטיח הפצת הודעות אמינה, אפילו מול מערכות הודעות לא אמינות. זה כרוך באחסון אירועים בטבלה ייעודית 'OutboxEvents' בתוך אותה עסקה כמו הפעולה עצמה. גישה זו מתיישרת היטב עם מאפייני ACID של מסדי נתונים יחסיים. לעומת זאת, מסדי נתונים רבים מסוג No-SQL אינם תומכים באופן מלא במאפייני ACID, ובמקום זאת בוחרים בעקרונות של משפט CAP ופילוסופיית BASE, שמתעדפים זמינות ועקביות בסופו של דבר על פני עקביות קפדנית.


תיבת דואר יוצא של עסקאות מספקת לפחות פעם אחת אחריות וניתן ליישם אותה במספר גישות:

  1. זנב יומן עסקאות

  2. מפרסם סקרים


גישת זנב יומן עסקאות מרמזת על שימוש בפתרונות ספציפיים לבסיס נתונים כמו CDC (Change Data Capture). החסרונות העיקריים של גישה זו הם:

  • פתרונות ספציפיים למסד נתונים

  • זמן השהייה מוגבר עקב פרטים ספציפיים של יישומי CDC


שיטה נוספת היא Polling Publisher , המאפשרת הורדת תיבת דואר יוצא על ידי סקר טבלת הדואר יוצא. החיסרון העיקרי של גישה זו הוא הפוטנציאל לעומס מוגבר של מסד הנתונים, שיכול להוביל לעלויות גבוהות יותר. יתר על כן, לא כל מסדי הנתונים ללא SQL תומכים בשאילתה יעילה עבור מקטעי מסמכים ספציפיים. חילוץ מסמכים שלמים עלול, אם כן, לגרום לירידה בביצועים.


הנה תרשים רצף קטן המסביר איך זה עובד.


תקשיבי לעצמך

האתגר העיקרי עם דפוס תיבת הדואר המסחרית טמון בתלות שלו במאפייני ACID של מסד הנתונים. זה עשוי להיות פשוט במסדי נתונים טיפוסיים של OLTP אבל מציב אתגרים בתחום ה-NoSQL. כדי להתמודד עם זה, פתרון פוטנציאלי הוא למנף את יומן ההוספה (לדוגמה, קפקא) כבר מהתחלת עיבוד הבקשות.


במקום לעבד ישירות את פקודת 'הגשת בקשה להלוואה', אנו שולחים אותה מיד לנושא פנימי של קפקא ולאחר מכן מחזירים תוצאה 'מקובלת' ללקוח. עם זאת, מכיוון שסביר מאוד שהפקודה עדיין צריכה לעבור עיבוד, איננו יכולים ליידע מיד את הלקוח על התוצאה. כדי לנהל את העקביות הזו, אנו יכולים להשתמש בטכניקות כגון סקר ארוך, סקר ביוזמת הלקוח, עדכוני ממשק משתמש אופטימיים, או שימוש ב-WebSockets או באירועים שנשלחו על ידי שרת להתראות. עם זאת, זהו נושא מובהק לחלוטין, אז בואו נחזור לנושא הראשוני שלנו.


שלחנו את ההודעה בנושא פנימי קפקאי. לאחר מכן, שירות בקשת ההלוואה צורך הודעה זו - אותה פקודה שקיבל מהלקוח - ומתחיל בעיבוד. ראשית, הוא מבצע היגיון עסקי כלשהו; רק לאחר שההיגיון הזה מבוצע בהצלחה והתוצאות נמשכות, הוא מפרסם הודעות חדשות בנושא קפקא ציבורי.


הבה נסתכל על מעט פסאודו-קוד.


 public async Task HandleAsync(SubmitLoanApplicationCommand command, ...) { //First, process business logic var loanApplication = await _loanApplicationService.HandleCommandAsync(command, ...); //Then, send new events to public Kafka topic producer.Send(new LoanApplicationSubmittedEvent(loanApplication.Id)); //Then, commit offset consumer.Commit(); }


מה אם עיבוד ההיגיון העסקי נכשל? אל דאגה, מכיוון שהקיזוז טרם בוצע, ההודעה תבוצע שוב.


מה אם שליחת אירועים חדשים לקפקא נכשלת? אל דאגה, מכיוון שההיגיון העסקי הוא אימפוטנטי, הוא לא ייצור בקשת הלוואה כפולה. במקום זאת, הוא ינסה לשלוח שוב הודעות לנושא קפקא הציבורי.


מה אם הודעות נשלחות לקפקא, אך ביצוע הקיזוז נכשל? אין מה לדאוג, מכיוון שההיגיון העסקי הוא אימפוטנטי, הוא לא ייצור בקשת הלוואה כפולה. במקום זאת, הוא ישלח שוב הודעות לנושא קפקא הציבורי ותקווה שההתחייבות לקיזוז תצליח הפעם.


החסרונות העיקריים של גישה זו כוללים את המורכבות הנוספת הקשורה לסגנון תכנות חדש, עקביות בסופו של דבר (שכן הלקוח לא יידע מיד את התוצאה), והדרישה שכל ההיגיון העסקי יהיה אדיש.

מיקור לאירועים

מהו מיקור לאירועים וכיצד ניתן ליישם אותו כאן? מיקור אירועים הוא דפוס ארכיטקטוני של תוכנה המשמש למודל של מצב מערכת על ידי לכידת כל השינויים בנתונים שלה כסדרה של אירועים בלתי ניתנים לשינוי. אירועים אלו מייצגים עובדות או מעברי מדינה ומשמשים כמקור אמת יחיד למצבה הנוכחי של המערכת. אז מבחינה טכנית, על ידי הטמעת מערכת מיקור אירועים, כבר יש לנו את כל האירועים ב-EventStore, וה-EventStore הזה יכול לשמש את הצרכנים כמקור יחיד של אמת לגבי מה שקרה. אין צורך בפתרון מסד נתונים ספציפי למעקב אחר כל השינויים או החששות בנוגע להזמנה, הבעיה היחידה היא בצד הקריאה שכן כדי להיות מסוגל לקבל את המצב האמיתי של הישות נדרש להפעיל מחדש את כל האירועים.

מַסְקָנָה

במאמר זה, סקרנו מספר גישות לבניית מסרים אמינים במערכות מבוזרות. ישנן מספר המלצות שאנו עשויים לשקול בעת בניית מערכות עם מאפיינים אלה

  1. פתח תמיד צרכנים אדישים מכיוון שכשל רשת הוא בלתי נמנע.
  2. השתמש בזהירות ב-First-Local-Commit-Then-Published עם הבנה ברורה של דרישות הערבות.
  3. לעולם אל תשתמש בגישת First-Publish-Then-Local-Commit מכיוון שהיא עלולה להוביל לחוסר עקביות חמורה של הנתונים במערכת שלך.
  4. אם החלטת הבחירה הקיימת של מסד הנתונים עשויה להשתנות או שהאסטרטגיה הטכנית מרמזת על בחירת פתרון האחסון הטוב ביותר לבעיה - אל תבנה ספריות משותפות על ידי התקשרות לפתרונות מסד נתונים כמו CDC .
  5. השתמש בגישת Transactional Outbox כפתרון סטנדרטי להשגת ערבויות לפחות פעם אחת.
  6. שקול להשתמש בגישת הקשב לעצמך כאשר מסדי נתונים ללא SQL ממונפים.


בפעם הבאה, נבחן דוגמה מעשית יותר של יישום תיבת דואר עסקית. לִרְאוֹת

אַתָה!