מקביליות עם LMAX Disruptor - מבוא

1. סקירה כללית

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

2. מהו משבש?

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

2.1. סימפטיה מכנית

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

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

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

כמה נתונים מעידים על עלות החמצת המטמון:

חביון ממעבד ל-מחזורי מעבדזְמַן
זיכרון ראשימרובות~ 60-80 ns
מטמון L3~ 40-45 מחזורים~ 15 ns
מטמון L2~ 10 מחזורים~ 3 ns
מטמון L1~ 3-4 מחזורים~ 1 ns
להירשםמחזור אחדמאוד מאוד מהיר

2.2. למה לא תורים

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

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

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

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

2.3. איך עובד המשבש

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

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

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

3. שימוש בספריית Disruptor

3.1. תלות של Maven

נתחיל בהוספת תלות בספריית Disruptor ב- pom.xml:

 משבש com.lmax 3.3.6 

ניתן לבדוק כאן את הגרסה האחרונה של התלות.

3.2. הגדרת אירוע

בואו נגדיר את האירוע הנושא את הנתונים:

מחלקה סטטית ציבורית ValueEvent {value int private; סופי ציבורי סטטי EventFactory EVENT_FACTORY = () -> ValueEvent חדש (); // סטרים וקובעים סטנדרטיים} 

ה EventFactory מאפשר למפריע להקדים מחדש את האירועים.

3.3. צרכן

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

מחלקה ציבורית SingleEventPrintConsumer {... public EventHandler [] getEventHandler () {EventHandler eventHandler = (event, רצף, endOfBatch) -> הדפס (event.getValue (), רצף); להחזיר EventHandler חדש [] {eventHandler}; } הדפסת חלל פרטית (int id, sequences long) {logger.info ("Id הוא" + id + "מזהה הרצף ששימש הוא" + sequentid); }}

בדוגמה שלנו, הצרכן רק מדפיס ליומן.

3.4. בניית המשבש

בנה את המשבש:

ThreadFactory threadFactory = DaemonThreadFactory.INSTANCE; WaitStrategy waitStrategy = BusySpinWaitStrategy חדש (); Disruptor disruptor = Disruptor חדש (ValueEvent.EVENT_FACTORY, 16, threadFactory, ProducerType.SINGLE, waitStrategy); 

בבנאי Disruptor מוגדרים הדברים הבאים:

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

חבר את המטפל הצרכני:

disruptor.handleEventsWith (getEventHandler ()); 

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

3.5. הפעלת משבש

להפעלת משבש:

RingBuffer ringBuffer = disruptor.start ();

3.6. הפקה ופרסום אירועים

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

להשתמש ב RingBuffer מ- Disruptor לפרסום:

עבור (int eventCount = 0; eventCount <32; eventCount ++) {long sequenceId = ringBuffer.next (); ValueEvent valueEvent = ringBuffer.get (sequenceId); valueEvent.setValue (eventCount); ringBuffer.publish (sequenceId); } 

כאן המפיק מייצר ומפרסם פריטים ברצף. חשוב לציין כאן כי Disruptor עובד בדומה לפרוטוקול התחייבות דו-פאזי. זה קורא חדש רצף מזהה ומפרסם. בפעם הבאה שזה אמור להגיע רצף מזהה + 1 בתור הבא רצף מזהה.

4. מסקנה

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

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


$config[zx-auto] not found$config[zx-overlay] not found