ארכיטקטורה של מערכת גמישה, עמידה וסקלבילית – חלק 2 (צד שרת – back-end)

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

מדריך זה הוא המשך ישיר לחלק הקודם המדבר על צד לקוח (front-end), מומלץ לקרוא את חלק 1 לפני שממשיכים בחלק זה.

לפני שנוכל לדבר על תכנון צד השרת נצטרך להגדיר מספר הגדרות.

התרחבות והתכווצות (scaling – scale up/down, scale out/in)

נניח והמערכת שלנו משתמשת בשרת אחד עם 8 מעבדים. המערכת יכולה לתמוך ולשרת, לצורך העניין, 1,000 משתמשים בו זמנית. אבל כעת יש דרישה להגדיל את התמיכה ל-10,000 משתמשים בו זמנית. יש 2 גישות אפשריות כדי להגדיל את התמיכה.

Scale up/down

ניתן להגדיל ולחזק את השרת – להעביר את המערכת לשרת יותר חזק, נניח 16 או 32 מעבדים. הגדלה זו תגדיל את יכולת השרת לשרת יותר משתמשים בו זמנית. הגדלה זו – הגדלה של כח (או משאב) – נקראת התרחבות למעלה, Scale up – כיוון שאנחנו עולים בכח העיבוד שלנו. מנגד, במידה ורוצים להקטין את הכח ולרדת בכמות המעבדים, זה ייקרא Scale down. שימו לב שההגדרה של ההגדלה היא כללית ולאו דווקא מדברת על המעבדים, ייתכן ואנחנו צריכים להגדיל את הזכרון ולא את המעבד, המעבד הוא רק דוגמה.

Scale out/in

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

אבל מה שכן נוכל לעשות זה להתרחב הצידה, Scale out – כלומר להוסיף עוד שרתים שייתנו מענה למשתמשים השונים. כלומר במקום שרת אחד שהיה לנו, יכולים להיות לנו 5 או 10 שרתים, כולם עם 8 מעבדים – ובכך לתת את השירות לכל המשתמשים שנדרשים בשעת עומס. המונח הנגדי, להצטמצם, ייקרא Scale in.

ברור שיותר קל לעשות Scale up/down, כיוון שכל שנדרש הוא לקחת את המערכת, ללא התאמות, ולהעביר לשרת חזק/חלש יותר. אבל כדי לתמוך ב-Scale out/in יש צורך בתכנון מוקדם כדי שהשירות שאתם מספקים יידע לקבל מענה ממספר שרתים משתנה, ובדיוק בשביל זה אתם פה.

היום הגישה הרווחת היא לתמוך מראש ב-Scale out/in כיוון שמבינים שככה האפשרויות לא מוגבלות – במידת הצורך אפשר להוסיף עוד שרתים, ואם ממש צריך – גם את השרתים שהוספנו אפשר לחזק (Scale up/down) – כך שמשלבים פה את שתי הגישות. סיבה נוספת היא הענן – גישה עננית לשרתים היא לאפשר להם לגדול ולקטון ללא מאמץ ובאופן אוטומטי – כאלה שיגיבו לכמות המשתמשים ובכך יחסכו כסף ומאמץ מצידכם.

ניהול סטייט (State)

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

Stateful

אז המידע הזה שהשרתים שומרים נקרא State (ולפעמים Session state, מדובר לרוב על מושגים מקבילים). במצב הבסיסי – המידע הזה נשמר על שרת אחד ויחיד בשיטות שונות (יכול להיות בתוך התהליך או מחוץ לתהליך, זה לא רלוונטי לנו כרגע), אבל מה קורה כאשר השרת נסגר? או נופל ועולה מחדש? אז המידע נאבד. ברוב המקרים. ואז כאשר השרת עולה וכל ה-State מתאפס, אז המשתמשים צריכים להזדהות מחדש. כמובן שאם המשתמש הגיע לשרת אחר אז בכלל אין לשרת החדש מידע עליו (יש לזה פתרונות, אבל לא כולן טובים לטווח הארוך). מצב כזה של שרת נקרא Stateful – כיוון שכל שרת שומר את המידע הרלוונטי לו וכל הפעלה מחדש דורשת את בנייה של כל המידע בתוך השרת, תהליך מאוד לא נוח לרוב, וגם לא מרכזי – נניח שיש שינוי במידע כלשהו – איך שרתים אחרים יקבלו את השינוי הזה?

Stateless

כשמגיעים לשלב הייצור של המערכת, נרצה לייצא את ה-State לניהול חיצוני, כזה שנשמר בשרת אחר, אולי אפילו מערכת אחרת. ככה גם אם יש שינוי במידע כלשהו – הוא מיד זמין לכל שאר השרתים כיוון שהם משתמשים באותה מערכת ה-State ושולפים מאותו המקום. ישנן טכנולוגיות רבות שמאפשרות ניהול של State, אבל אני ממליץ להשתמש במוכרות ביותר – Redis או Memcached. לשתיהן יתרונות וחסרונות שלהן ואני ממליץ לקרוא עליהן לעומק, אבל אני אישית משתמש ב-Redis. לגבי ניהול שרת ה-Redis נדבר בחלק הבא שמדבר על נתונים (data).

במצב כזה שבו השרת לא מחזיק את המידע בעצמו, זה נקרא Stateless – כי השרת לא מחזיק את ה-State, אפשר להוריד אותו ולהעלות אותו או להעלות שרתים חדשים וזה לא משפיע על ה-State. וזה המצב האידיאלי שלנו – כי ככה נוכל לבצע Scale out בצורה שקופה למשתמשים, שלא משפיעה עליהם כלל.

מאזן עומסים (Load Balancer)

אחרי שהבנו את המושגים השונים – נדבר על הרכיב שיעשה לנו סדר בהכל – מאזן העומסים, ה-Load balancer. תפקידו של מאזן העומסים הוא לקבל בקשות שונות ולנתב אותן לשרתים השונים שמקושרים אליו, ככה כל שרת מקבל חלק מכל הבקשות והעומס מתחלק באופן כזה או אחר בין כל השרתים. ככל שהשרתים בנויים בצורת Stateless עם אפשרות גמישה ל-Scale out/in, ניתן להוסיף ולהוריד שרתים ממאזן העומסים ובכך לתמוך בכמות המשתמשים המשתנה. אפשר שיהיה מעט שרתים בשעות רגועות, ואפשר שיהיה הרבה שרתים בשעות העומס. כמו כן יכול להיות ששרת מסוים יפסיק להיות זמין, או שפתאום ייתקע – ומאזן העומסים יידע (בתוך זמן סביר) לא להעביר אליו פניות. כל זה יחסוך משמעותית כסף כאשר בונים ומתכננים את זה נכון.

כמובן שיש הרבה סוגים של מאזני עומסים ולא נדבר עליהם כאן, אבל זה לכל הפחות הבסיס של מערכת גמישה וסקלבילית. אגב – זה נכון לא רק לצד השרת שמחזיר תשובות בסיסיות, אלא ייתכן ומדובר על שרת שנותן תשובות צד-לקוח. ייתכן ומדובר על שרת בסיסי שמחזיר את הדף שמוצג למשתמש, ייתכן גם שמדובר על דפים דינאמיים (ASP.NET Razor, MVC, PHP וכד'). הבסיס של Stateless, התרחבות ומאזן עומסים תופסים גם כאן.

הקמה של שרת חדש

והנושא שמסיים את כל החלק הזה הוא ההקמה של השרת בפועל. תזכרו את המוטו של הענן – "מה שלא כתוב ב-script – כאילו לא קיים" (If it's not scripted – it doesn't exist). כלומר גם אם יש לכם נוהל מאוד יפה להקמת שרת – זה יהיה לא ריאלי בשבילכם לעבוד כל פעם כדי להקים את השרת. הדרך הנכונה יותר היא לכתוב סקריפט, תוכנית הרצה, שמתקינה, מגדירה ומורידה את כל מה שצריך בתוך השרת כדי לתת את השירות. השרת שלכם יכול להיות פשוט וכל מה שצריך זה להוריד קבצים ממקום מרכזי ולהפעיל שירותי אינטרנט, ויכול להיות משהו יותר מורכב שדורש מספר רב של פעולות – בכל מקרה, סקריפט נוח יפתור לנו הרבה עבודה, וככל שהוא יהיה יותר נכון – נוכל לתת לענן לעבוד לבד, להוסיף שרתים, להריץ את הסקריפט, ושיכניס לבד כבר למאזן העומסים.

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

סיכום

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

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

כתיבת תגובה

האימייל לא יוצג באתר. שדות החובה מסומנים *