תכנון מערכות מבוזרות בעזרת העקרונות של Reactive Systems

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

קרדיט צלם\תמונה: gilaxia, Getty Images Israel

מאת אלעד אבנרי

מדי כמה שנים קמה בעולם הארכיטקטורה והנדסת התוכנה מגמה חדשה סביב מושג מרכזי, שמולידה מספר שיטות ומערכות של עקרונות. למשל בשנות ה-90 הכל היה מונחה אובייקטים: "Object Oriented Whaterver". לאחר מכן באה שעתן של שיטות ה-"Whatever driven Whatever" (במקום whatever תכניסו domain, design, test, development, behavior וכו'). נראה שלאחרונה אותו הדבר קורא סביב "Reactive".

אז יש לנו Reactive Programming ו-Reactive Extensions מצד אחד ו-Reactive Systems מצד שני, שזה הנושא בו אעסוק כאן. המשותף לכל שיטות ה-Reactive למיניהן זה שביסוד כולן עומדת התפיסה לפיה רוב הקוד שנכתוב יהיה מנקודת מבט של תגובה למשהו (קוד ראקטיבי). התפיסה היא שכל שרשרת של אירועים מתחילה מתוך משהו שמתרחש מחוץ למערכת שאנחנו כותבים (משתמש מבקש משהו, העכבר זז, נגמר המקום בדיסק הקשיח, וכו'…) שגורם לשרשרת של תגובות בקוד שלנו. אבל כאן פחות או יותר מסתיים הדמיון בין Reactive Systems ל-Reactive Programming. ההבדל הוא שבעוד ש-Reactive Programming מתעסק במה שקורה בתוך שירות (service) או תהליך (process) בודד, הרי שב-Reactive Systems אנחנו מתעניינים במה שקורה בין שירותים שונים שמורצים בתהליכים שונים, בדר"כ על מחשבים שונים והרבה פעמים במקומות שונים בעולם (distributed services). מתוך ההבדל הזה נובע הבדל מהותי אחר שאותו אציין בהמשך.

הבחנה נוספת שחשוב להעלות היא היחס בין Reactive Systems ל-Micro Services. בסופו של דבר מדובר בתפיסות שמשלימות אחת את השניה. בעוד ש-Micro Services עוסק בהגדרת אבני הבניין של המערכת שלנו הרי ש-Reactive Systems עוסק בתווך שבין אותן אבני בניין.

אז מה זה בעצם Reactive Systems? מדובר באוסף של עקרונות לארכיטקטורה ותכנון של מערכות מבוזרות. כמו שקורה הרבה פעמים בהופעה של מגמות כאלו, העקרונות עצמם לאו דווקא מהווים חידוש אלא שהחידוש הוא בכך שהעקרונות הללו מקבלים שם ומאוגדים תחת מעטה אחד (ואין לזלזל בחשיבות של זה). במקרה שלנו אותו אוסף חובר לכדי מניפסט שנקרא (כמה מפתיע) The Reactive Manifesto.
המטרה הסופית של אותם עקרונות היא לעזור לנו ליצור מערכת שהיא "רספונסיבית". נשמע טוב, לא? אבל מהי בעצם מערכת רספונסיבית? ובכן, על פי אחת ההגדרות המקובלות:

"מערכת רספונסיבית היא מערכת שמגיבה במהירות לכל המשתמשים גם תחת תנאים קשים על מנת לייצר חווית משתמש עקבית וחיובית"

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

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

מסקנה: על מנת שמערכת תהיה רספונסיבית היא צריכה לרוץ מהר

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

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

מסקנה: על מנת שמערכת תהיה רספונסיבית היא צריכה לרוץ מהר להיות סקלבילית

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

מסקנה: על מנת שמערכת תהיה רספונסיבית היא צריכה להיות סקלבילית אלסטית

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

מסקנה: על מנת שמערכת תהיה רספונסיבית היא צריכה להיות אלסטית ובעלת עמידות

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

בשביל זה באים החברים שחיברו את ה Reactive Manifesto לעזרתנו ואומרים את הדבר הבא (בתרגום חופשי שלי):

"מערכות ראקטיביות מתבססות על העברת הודעות אסינכרונית על מנת לבסס גבולות בין רכיבים שמאפשרים "צימוד רופף" (loose coupling), בידוד ושקיפות במיקום"

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

עד כאן המשל. ומה הנמשל? אז במערכת מבוזרת שולח ההודעה הוא תהליך הלקוח (client process), מקבל ההודעה הוא תהליך השרת (server process), שליחת ההודעה במשל שקולה להכנסת ההודעה לתור וקבלת ההודעה שקולה לשליפתה מהתור. עכשיו, נשאר לנו להבין איך כל זה עוזר כדי להשיג אלסטיות ועמידות.

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

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

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

מסקנה: על מנת שמערכת תהיה רספונסיבית היא צריכה להיות אלסטית ובעלת עמידות וזאת על ידי שימוש בהודעות אסינכרוניות

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

זאת הדיאגרמה שמופיעה ב Reactive Manifesto ושום מאמר או הרצאה על הנושא לא שלם בלעדיה…
אני, עם זאת, מעדיף את הדיאגרמה הבאה שעוזרת יותר להבין את התפקידים של כל רכיב ואת יחסי הגומלין ביניהם:

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

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

הכותב הינו ארכיטקט ב-Zerto

כתב אורח

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

הגב

4 Comments on "תכנון מערכות מבוזרות בעזרת העקרונות של Reactive Systems"

avatar
Photo and Image Files
 
 
 
Audio and Video Files
 
 
 
Other File Types
 
 
 
Sort by:   newest | oldest | most voted
Moti
Guest

תודה. הבנתי הכל.

אלזם
Guest

תודה שהרמת את הכפפה לפנק בביאור איכותי לנושא המרתק.

יצחק
Guest

ב"ה

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

אני לא יודע אם זה גם נכנס לקטגוריה הזאת אבל לדעתי במידה ולא זה חייב להיות חלק ממנה

כתבה לעניין תודה

אלעד
Guest

ניתן לראות הרצאה בנושא כאן: https://youtu.be/Bz6nKQLiUFs

wpDiscuz

תגיות לכתבה: