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

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

אוקיי, הבעיות ברורות. ניתן לבטל אותם על ידי יצירת מערכת הודעות מרכזית בין שירותים. כעת כל אחד מהשירותים צריך לדעת רק על מערכת ההודעות הזו. בנוסף, על המערכת עצמה להיות סובלנית לתקלות וניתנת להרחבה אופקית, וגם, במקרה של תאונות, לצבור מאגר גישה לעיבוד בהמשך.
כעת נבחר את הטכנולוגיה שבה תיושם העברת הודעות. כדי לעשות זאת, בואו נבין תחילה למה אנחנו מצפים ממנו:
- אין לאבד הודעות בין שירותים;
- הודעות עשויות להיות משוכפלות;
- ניתן לאחסן ולקרוא הודעות לעומק של מספר ימים (מאגר מתמשך);
- שירותים יכולים להירשם לנתונים שהם מעוניינים בהם;
- שירותים מרובים יכולים לקרוא את אותם נתונים;
- הודעות יכולות להכיל מטען מפורט ונפחי (העברת מצב נישאת באירועים);
- לפעמים צריך להבטיח את סדר ההודעות.
היה לנו גם חשוב מאוד לבחור את המערכת הניתנת להרחבה והאמינה ביותר עם תפוקה גבוהה (לפחות 100 הודעות של כמה קילובייט בשנייה).
בשלב זה נפרדנו מ-RabbitMQ (קשה לשמור על יציבות בסל"ד גבוה), PGQ מ-SkyTools (לא מספיק מהיר ולא מתקדם היטב) ו-NSQ (לא מתמשך). אנו משתמשים בכל הטכנולוגיות הללו בחברה שלנו, אך הן לא התאימו לבעיה שנפתרה.
לאחר מכן, התחלנו להסתכל על טכנולוגיות שהיו חדשות לנו - Apache Kafka, Apache Pulsar ו-NATS Streaming.
פולסר היה הראשון שהושלך. החלטנו שקפקא ופולסר הם פתרונות דומים למדי. ולמרות העובדה ש-Pulsar נבדקה על ידי חברות גדולות, היא חדשה יותר ומציעה חביון נמוך יותר (בתיאוריה), החלטנו להשאיר את קפקא מבין שני אלה כסטנדרט דה פקטו למשימות כאלה. כנראה שנחזור ל- Apache Pulsar בעתיד.
ועכשיו נותרו שני מועמדים: NATS Streaming ואפאצ'י קפקא. למדנו את שני הפתרונות בפירוט, ושניהם התאימו למשימה. אבל בסופו של דבר פחדנו מהנוער היחסי של NATS Streaming (והעובדה שאחד המפתחים הראשיים, טיילר טריט, החליט לעזוב את הפרויקט ולהתחיל משלו - Liftbridge). יחד עם זאת, מצב Clustering של NATS Streaming לא סיפק אפשרות של קנה מידה אופקי חזק (כנראה שזו כבר לא בעיה לאחר הוספת מצב החלוקה ב-2017).
עם זאת, NATS Streaming היא טכנולוגיה מגניבה שנכתבה ב-Go ונתמכת על ידי קרן Cloud Native Computing. שלא כמו אפאצ'י קפקא, זה לא צריך Zookeeper כדי לעבוד (אולי ), מכיוון שהוא מיישם את RAFT באופן פנימי. יחד עם זאת, NATS Streaming קל יותר לניהול. אנחנו לא פוסלים שנחזור לטכנולוגיה הזו בעתיד.
ועדיין, היום הזוכה שלנו הוא אפאצ'י קפקא. בבדיקות שלנו, הוא הוכיח את עצמו כדי מהיר (יותר ממיליון הודעות בשנייה לקריאה וכתיבה בנפח הודעות של 1 קילובייט), אמין למדי, ניתן להרחבה ומוכח מניסיון בייצור של חברות גדולות. בנוסף, קפקא תומך לפחות בכמה חברות מסחריות גדולות (אנחנו, למשל, משתמשים בגרסת ה-Confluent), וגם לקפקא יש אקוסיסטם מפותח.
סקירה כללית של קפקא
לפני שנתחיל, אני רוצה להמליץ מיד על ספר מצוין - "קפקא: המדריך הסופי" (יש גם תרגום לרוסית, אבל המונחים קצת מטריפים). הוא מכיל את המידע שאתה צריך כדי לקבל הבנה בסיסית של קפקא ואפילו קצת יותר. גם התיעוד של Apache והבלוג של Confluent כתובים היטב וקלים לקריאה.
אז בואו נסתכל ממעוף הציפור על איך קפקא עובד. הטופולוגיה הבסיסית של קפקא מורכבת מיצרן, צרכן, מתווך ושומר גן חיות.
ברוקר

המתווך אחראי על שמירת הנתונים שלך. כל הנתונים מאוחסנים בצורה בינארית, והברוקר יודע מעט על מה הם ומה המבנה שלהם.
כל סוג אירוע לוגי ממוקם בדרך כלל בנושא נפרד משלו. לדוגמה, אירוע של יצירת מודעה עשוי להיכנס לנושא item.created, ואירוע השינוי שלה עשוי להיכנס ל-item.changed. ניתן להתייחס לנושאים כמסווגים של אירועים. ברמת הנושא, אתה יכול להגדיר פרמטרים של תצורה כגון:
- כמות הנתונים המאוחסנים ו/או גילם (retention.bytes, retention.ms);
- גורם יתירות נתונים (גורם שכפול);
- גודל מקסימלי של הודעה אחת (max.message.bytes);
- המספר המינימלי של העתקים עקביים שבהם ניתן לכתוב נתונים לנושא (min.insync.replicas);
- היכולת לבצע כשל על העתק לא-סינכרוני בפיגור עם אובדן נתונים פוטנציאלי (unclean.leader.election.enable);
- ועוד רבים ().
בתורו, כל נושא מחולק למחיצה אחת או יותר. זה במפלגות שהאירועים נופלים בסופו של דבר. אם יש יותר ממתווך אחד באשכול, אז המחיצות יחולקו באופן שווה על פני כל המתווכים (עד כמה שניתן), מה שיאפשר להגדיל את העומס בכתיבה וקריאה לנושא אחד על פני מספר מתווכים בו זמנית.
בדיסק, הנתונים עבור כל מחיצה מאוחסנים בצורה של קבצי מקטעים, כברירת מחדל שווה לגיגה-בייט אחד (נשלט באמצעות log.segment.bytes). תכונה חשובה היא שהנתונים נמחקים ממחיצות (כאשר השמירה מופעלת) בקטעים (אי אפשר למחוק אירוע אחד ממחיצה, אפשר למחוק רק מקטע שלם ורק את הלא פעיל).
שומר
Zookeeper משמש כמחסן ורכז מטא נתונים. הוא זה שמסוגל לדעת אם מתווכים חיים (אתה יכול להסתכל על זה דרך עיניו של שומר גן החיות באמצעות פגז גן החיות עם הפקודה ls /brokers/ids), איזה מתווך הוא הבקר (get /controller), האם המחיצות מסונכרנות עם ההעתקים שלהן (get /brokers/topics/topic_name/partitions/partition_number/state). כמו כן, לשומר גן החיות היצרן והצרכן יפנו קודם כל לברר באיזה מתווך אילו נושאים ומחיצות מאוחסנים. במקרים בהם צוין גורם שכפול גדול מ-1 לנושא, שומר גן החיות יציין אילו מחיצות הן המובילות (הן ייכתבו ויקראו מהן). במקרה של כשל מתווך, מידע על מחיצות מנהיג חדשות ייקלט ב-zookeeper (מגרסה 1.1.0 באופן אסינכרוני, ).
בגרסאות ישנות יותר של קפקא, שומר גן החיות היה אחראי גם לאחסון קיזוזים, אך כעת הם מאוחסנים בנושא מיוחד __consumer_offsets על המתווך (למרות שאתה עדיין יכול להשתמש ב-zookeeper למטרות אלו).
הדרך הקלה ביותר להפוך את הנתונים שלך לדלעת היא לאבד מידע משומר גן החיות. בתרחיש כזה, יהיה קשה מאוד להבין מה לקרוא ומאיפה.
מפיק
מפיק הוא לרוב שירות שכותב נתונים ישירות לאפצ'י קפקא. המפיק בוחר נושא שבו יאחסן את הודעות הנושא שלו ומתחיל לכתוב לו מידע. לדוגמה, המפיק יכול להיות שירות מודעות. במקרה זה, הוא ישלח אירועים כגון "מודעה נוצרה", "מודעה עודכנה", "מודעה נמחקה" וכו' לנושאים נושאיים. כל אירוע הוא צמד מפתח-ערך.
כברירת מחדל, כל האירועים מופצים בין מחיצות נושא באמצעות round-robin אם המפתח לא צוין (מאבד סדר), ודרך MurmurHash (מפתח) אם המפתח קיים (הזמנה בתוך מחיצה אחת).
ראוי לציין מיד שקפקא מבטיח את סדר האירועים רק בתוך מנה אחת. אבל במציאות זו לרוב לא בעיה. לדוגמה, אתה יכול להיות בטוח להוסיף את כל השינויים לאותה הצהרה למחיצה אחת (ובכך לשמור על סדר השינויים הללו בתוך ההצהרה). ניתן גם לשלוח מספר רצף באחד משדות האירוע.
צרכנות

הצרכן אחראי להביא נתונים מאפצ'י קפקא. אם נחזור לדוגמא שלמעלה, הצרכן יכול להיות שירות המתנה. שירות זה יהיה מנוי לנושא שירות המודעות, וכאשר תופיע מודעה חדשה, היא תקבל אותה ותנתח אותה לעמידה במדיניות מסויימת.
אפאצ'י קפקא זוכר אילו אירועים אחרונים קיבל הצרכן (נושא שירות משמש לשם כך __consumer__offsets), ובכך להבטיח שאם הקריאה תצליח, הצרכן לא יקבל את אותה הודעה פעמיים. עם זאת, אם אתה משתמש באפשרות enable.auto.commit = true ומאציל לחלוטין את עבודת המעקב אחר מיקומו של הצרכן בנושא לקפקא, אתה יכול . בקוד ייצור, לרוב המיקום של הצרכן נשלט באופן ידני (המפתח שולט ברגע שבו ביצוע אירוע הקריאה חייב להתרחש).
במקרים בהם צרכן אחד אינו מספיק (למשל, זרם האירועים החדשים גדול מאוד), ניתן להוסיף עוד מספר צרכנים על ידי קישור ביניהם בקבוצת צרכנים. מבחינה לוגית קבוצת צרכנים זהה לחלוטין לצרכן, אך עם נתונים המופצים בין חברי הקבוצה. זה מאפשר לכל משתתף לקחת את חלקו בהודעות, ובכך להגדיל את מהירות הקריאה.
תוצאות הבדיקה

אני לא אכתוב כאן הרבה טקסט הסבר, אני רק אחלוק את התוצאות שהתקבלו. הבדיקה בוצעה על 3 מכונות פיזיות (12 CPU, 384GB RAM, 15k SAS DISK, 10GBit/s Net), מתווכים ו-zookeeper נפרסו ב-lxc.
בדיקת ביצועים
במהלך הבדיקה התקבלו התוצאות הבאות.
- מהירות הקלטת הודעות של 1KB בו-זמנית על ידי 9 מפיקים היא 1300000 אירועים בשנייה.
- מהירות קריאת הודעות של 1KB בו-זמנית על ידי 9 צרכנים היא 1500000 אירועים בשנייה.
בדיקת סובלנות תקלות
במהלך הבדיקה התקבלו התוצאות הבאות (3 מתווכים, 3 שומרי גן חיות).
- סיום חריג של אחד המתווכים אינו גורם לעצירת האשכול או להיות בלתי זמין. העבודה נמשכת כרגיל, אך לתווכים הנותרים יש עומס עבודה כבד.
- סיום חריג של שני מתווכים במקרה של אשכול של שלושה מתווכים ו-min.isr = 2 מוביל לכך שהאשכול אינו זמין לכתיבה, אך נגיש לקריאה. אם min.isr = 1, האשכול ממשיך להיות זמין גם לקריאה וגם לכתיבה. עם זאת, מצב זה סותר את הדרישה לאבטחת נתונים גבוהה.
- כיבוי חריג של אחד משרתי Zookeeper לא גורם לאשכול להפסיק או להיות בלתי זמין. העבודה נמשכת כרגיל.
- כיבוי חריג של שני שרתי Zookeeper מביא לכך שהאשכול אינו זמין עד לשחזור של לפחות אחד משרתי Zookeeper. הצהרה זו נכונה עבור אשכול Zookeeper של 3 שרתים. כתוצאה מכך, לאחר מחקר, הוחלט להגדיל את אשכול Zookeeper ל-5 שרתים כדי להגביר את סובלנות התקלות.
קפקא כשירות

אנו משוכנעים שקפקא היא טכנולוגיה מצוינת המאפשרת לנו לפתור את המשימה שהוטלה עלינו (יישום מתווך הודעות). עם זאת, החלטנו לאסור על שירותים לגשת ישירות לקפקא וסגרנו אותו על גבי שירות אוטובוס נתונים. למה עשינו את זה? למעשה, יש לא מעט סיבות.
Data-bus השתלט על כל המשימות הקשורות באינטגרציה עם קפקא (יישום ותצורה של צרכנים ויצרנים, ניטור, התראה, רישום, קנה מידה וכו'). לפיכך, האינטגרציה עם מתווך ההודעות היא פשוטה ככל האפשר.
Data-bus איפשר לנו להתרחק משפה או ספרייה ספציפית לעבודה עם קפקא.
Data-bus אפשר לשירותים אחרים להרחיק את שכבת האחסון. אולי בשלב מסוים נשנה את קפקא לפולסר, ואף אחד לא ישים לב לכלום (כל השירותים יודעים רק על ה-API של אוטובוס הנתונים).
Data-bus השתלט על האימות של סכימות אירועים.
האימות מיושם באמצעות Data-bus.
תחת הכיסוי של Data-bus, אנחנו יכולים לעדכן בשקט גרסאות קפקא ללא השבתה, לנהל באופן מרכזי תצורות של יצרנים, צרכנים, ברוקרים וכו'.
Data-bus אפשרה לנו להוסיף את הפיצ'רים הדרושים לנו שאינם בקפקא (כגון ביקורת נושאים, ניטור חריגות באשכול, יצירת DLQ וכו').
Data-bus מאפשר לך להטמיע מעבר לכשל באופן מרכזי עבור כל השירותים.
כרגע, כדי להתחיל לשלוח אירועים למתווך ההודעות, אתה רק צריך לחבר ספרייה קטנה לקוד השירות שלך. זה הכל. יש לך את היכולת לכתוב, לקרוא ולהתאים בשורת קוד אחת. היישום כולו מוסתר ממך, ורק כמה ידיות בגודל אצווה בולטות החוצה. מתחת למכסה המנוע, שירות אוטובוס הנתונים מעלה את המספר הנדרש של מופעי יצרנים וצרכנים ב-Kubernetes ומספק להם את התצורה הדרושה, אבל כל זה שקוף לשירות שלכם.
כמובן, אין כדור כסף, ולגישה הזו יש מגבלות.
- יש לתמוך ב-Data-bus בתוך הבית, בניגוד לספריות של צד שלישי.
- Data-bus מגדיל את מספר האינטראקציות בין השירותים לבין מתווך ההודעות, מה שמביא לביצועים נמוכים יותר בהשוואה לקפקא החשוף.
- לא הכל יכול להיות מוסתר משירותים כל כך בקלות; אנחנו לא רוצים לשכפל את הפונקציונליות של KSQL או Kafka Streams ב-data-bus, אז לפעמים אנחנו צריכים לאפשר לשירותים לעבור ישירות.
במקרה שלנו, היתרונות גברו על החסרונות, וההחלטה לכסות את מתווך ההודעות בשירות נפרד הייתה מוצדקת. במהלך שנת הפעילות לא היו לנו תאונות או בעיות חמורות.
נ.ב תודה לחברה שלי, יקטרינה אובליאייבה, על התמונות המגניבות לכתבה זו. אם אהבתם, יש עוד איורים בהמשך.
מקור: www.habr.com
