מדריך ל- Apache BookKeeper

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

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

2. מה זה מְנַהֵל חֶשׁבּוֹנוֹת?

BookKeeper פותח במקור על ידי יאהו כפרויקט משנה של ZooKeeper וסיים להיות פרויקט ברמה העליונה בשנת 2015. בבסיסו, BookKeeper שואף להיות מערכת אמינה וביצועית גבוהה המאחסנת רצפים של רישום רשומות (aka רשומות) במבני נתונים הנקראים ספרים.

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

3. מושגי BookKeeper

3.1. רישום רשומות

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

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

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

3.2. ספרים

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

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

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

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

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

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

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

3.3. בוקיסטים

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

סוכנויות הימורים מתאמות פעולות באמצעות שירותי אשכול שמספקים ZooKeeper. זה מרמז שאם אנו רוצים להשיג מערכת סובלנית לחלוטין, אנו זקוקים לפחות ל- ZooKeeper בן 3 מופעים ולהגדרת BookKeeper בת 3-מופע. התקנה כזו תוכל לסבול אובדן אם מופע יחיד ייכשל ועדיין יוכל לפעול כרגיל, לפחות עבור הגדרת ברירת המחדל של ספר החשבונות: גודל אנסמבל עם 3 צומת, מניין כתיבה של 2 צומת ומניין ack עם 2 צומת.

4. הגדרה מקומית

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

אמנם בהחלט ניתן לבצע את הצעדים הללו באופן ידני, אך כאן נשתמש ב- docker-compose קובץ המשתמש בתמונות רשמיות של אפאצ'י כדי לפשט משימה זו:

$ cd $ docker-compose up

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

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

$ docker exec -it apache-bookkeeper_bookie_1 / opt / bookholder / bin / bookkeeper \ shell listbookies -readwrite ReadWrite Bookies: 192.168.99.101 (192.168.99.101): 4181 192.168.99.101 (192.168.99.101): 4182 192.168.99.101 (192.168. 99.101): 3181 

הפלט מציג את רשימת הזמינים bookies, המורכב משלוש סוכנויות הימורים. שים לב שכתובות ה- IP המוצגות ישתנו בהתאם למפרט ההתקנה של Docker המקומי.

5. שימוש ב- API של Ledger

ממשק ה- API של Ledger הוא הדרך הבסיסית ביותר להתממשק עם BookKeeper. זה מאפשר לנו לתקשר ישירות עם פִּנקָס אובייקטים אך מצד שני חסר תמיכה ישירה להפשטות ברמה גבוהה יותר כמו זרמים. עבור מקרי שימוש אלה, פרויקט BookKeeper מציע ספרייה נוספת, DistributedLog, התומכת בתכונות אלה.

השימוש ב- API של Ledger מחייב הוספה של ה- מנהל חשבונות-שרת תלות בפרויקט שלנו:

 org.apache.bookkeeper-book-server 4.10.0 

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

 org.apache.bookkeeper מנהל חשבונות-שרת מוצל 4.10.0 

5.1. מתחבר לבוקיסטים

ה מְנַהֵל חֶשׁבּוֹנוֹת class הוא נקודת הכניסה העיקרית של ה- Ledger API, המספק כמה שיטות להתחברות לשירות BookKeeper שלנו. בצורתו הפשוטה ביותר, כל שעלינו לעשות הוא ליצור מופע חדש של מחלקה זו, שיעביר את הכתובת של אחד משרתי ZooKeeper המשמש את BookKeeper:

לקוח BookKeeper = BookKeeper חדש ("zookeeper-host: 2131"); 

פה, שרת גן-חיות צריך להיות מוגדר לכתובת ה- IP או שם המארח של שרת ZooKeeper המחזיק בתצורת האשכול של BookKeeper. במקרה שלנו, זה בדרך כלל "localhost" או המארח אליו משתנה הסביבה DOCKER_HOST מצביע.

אם אנו זקוקים לשליטה רבה יותר בכמה הפרמטרים הזמינים כדי לכוונן את לקוחנו, אנו יכולים להשתמש ב- תצורת לקוח למשל ולהשתמש בו ליצירת הלקוח שלנו:

ClientConfiguration cfg = ClientConfiguration חדש (); cfg.setMetadataServiceUri ("zk + null: // zookeeper-host: 2131"); // ... הגדר מאפיינים אחרים BookKeeper.forConfig (cfg) .build ();

5.2. יצירת ספר חשבונות

ברגע שיש לנו מְנַהֵל חֶשׁבּוֹנוֹת למשל, יצירת ספר חשבונות חדש היא פשוטה:

LedgerHandle lh = bk.createLedger (BookKeeper.DigestType.MAC, "סיסמה" .getBytes ());

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

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

LedgerHandle lh = bk.createLedger (3, 2, 2, DigestType.MAC, "password" .getBytes (), Collections.singletonMap ("name", "my-ledger" .getBytes ()));

הפעם, השתמשנו בגרסה המלאה של ה- createLedger () שיטה. שלושת הטיעונים הראשונים הם ערכי גודל האנסמבל, כתיבת המניין וה- ack, בהתאמה. לאחר מכן, יש לנו את אותם פרמטרים לעיכול כמו בעבר. לבסוף, אנו מעבירים א מַפָּה עם המטא נתונים המותאמים אישית שלנו.

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

bk.asyncCreateLedger (3, 2, 2, BookKeeper.DigestType.MAC, "passwd" .getBytes (), (rc, lh, ctx) -> {// ... השתמש ב- lh כדי לגשת לפעולות ספר החשבונות}, null, אוספים .emptyMap ()); 

גרסאות חדשות יותר של BookKeeper (> = 4.6) תומכות גם ב- API בסגנון שוטף העתיד כדי להשיג את אותה מטרה:

CompletableFuture cf = bk.newCreateLedgerOp () .withDigestType (org.apache.bookkeeper.client.api.DigestType.MAC) .withPassword ("סיסמה" .getBytes ()) .execute (); 

שים לב שבמקרה זה, אנו מקבלים א WriteHandle במקום LedgerHandle. כפי שנראה בהמשך, אנו יכולים להשתמש בכל אחד מהם כדי לגשת לחשבוננו כ- LedgerHandle מכשירים WriteHandle.

5.3. כתיבת נתונים

ברגע שרכשנו א LedgerHandle אוֹ WriteHandle, אנו כותבים נתונים לספר החשבונות המשויך באמצעות אחד מה- לְצַרֵף() גרסאות שיטה. נתחיל עם הגרסה הסינכרונית:

עבור (int i = 0; i <MAX_MESSAGES; i ++) {byte [] data = new String ("message-" + i) .getBytes (); lh.append (נתונים); } 

כאן אנו משתמשים בגרסה שלוקחת a בתים מַעֲרָך. ה- API תומך גם ב- Netty ByteBuf ו- Java NIO ByteBuffer, המאפשרים ניהול זיכרון טוב יותר בתרחישים קריטיים לזמן.

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

// זמין ב- WriteHandle ו- LedgerHandle CompletableFuture f = lh.appendAsync (נתונים); // זמין רק ב- LedgerHandle lh.asyncAddEntry (נתונים, (rc, ledgerHandle, entryId, ctx) -> {// ... לוגיקה להתקשרות חוזרת הושמעה}, null);

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

5.4. קריאת נתונים

קריאת נתונים מתוך ספר ספר BookKeeper עובדת באופן דומה לכתיבה. ראשית, אנו משתמשים שלנו מְנַהֵל חֶשׁבּוֹנוֹת מופע כדי ליצור LedgerHandle:

LedgerHandle lh = bk.openLedger (ledgerId, BookKeeper.DigestType.MAC, ledgerPassword); 

חוץ מ ה ספר חשבונות פרמטר, אותו נעסוק אחר כך, קוד זה נראה כמו ה- createLedger () שיטה שראינו בעבר. אולם יש הבדל חשוב; שיטה זו מחזירה קריאה בלבד LedgerHandle למשל. אם ננסה להשתמש בכל אחד מהזמינים לְצַרֵף() שיטות, כל מה שנקבל הוא חריג.

לחלופין, דרך בטוחה יותר היא להשתמש ב- API בסגנון שוטף:

ReadHandle rh = bk.newOpenLedgerOp () .withLedgerId (ledgerId) .withDigestType (DigestType.MAC) .withPassword ("סיסמה" .getBytes ()) .execute () .get (); 

ReadHandle יש את השיטות הנדרשות לקריאת נתונים מתוך ספר החשבונות שלנו:

long lastId = lh.readLastConfirmed (); rh.read (0, lastId) .forEach ((ערך) -> {// ... תעשו משהו});

כאן פשוט ביקשנו את כל הנתונים הזמינים בפנקס זה באמצעות הסינכרוני לקרוא גִרְסָה אַחֶרֶת. כצפוי, יש גם גרסה אסינקרית:

rh.readAsync (0, lastId) .thenAccept ((ערכים) -> {entries.forEach ((entry) -> {// ... entry process});});

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

lh.asyncReadEntries (0, lastId, (rc, lh, entries, ctx) -> {while (entries.hasMoreElements ()) {LedgerEntry e = ee.nextElement ();}}, null);

5.5. רישום ספרים

ראינו בעבר שאנחנו צריכים את ספר החשבונות תְעוּדַת זֶהוּת כדי לפתוח ולקרוא את נתוניו. אז איך נקבל אחד כזה? דרך אחת היא להשתמש ב- LedgerManager ממשק, אליו אנו יכולים לגשת מה- מְנַהֵל חֶשׁבּוֹנוֹת למשל. ממשק זה בעצם עוסק במטא נתונים של ספר החשבונות, אך יש לו גם את asyncProcessLedgers () שיטה. באמצעות שיטה זו - וחלק מהעזרים ליצירת פרימיטיבים מקבילים - אנו יכולים למנות את כל החשבונות הזמינים:

list ListlistAllLedgers (BookKeeper bk) {List ledgers = Collections.synchronizedList (ArrayList new ()); CountDownLatch processDone = CountDownLatch חדש (1); bk.getLedgerManager () .asyncProcessLedgers ((ledgerId, cb) -> {ledgers.add (ledgerId); cb.processResult (BKException.Code.OK, null, null);}, (rc, s, obj) -> { processDone.countDown ();}, null, BKException.Code.OK, BKException.Code.ReadException); נסה את {processDone.await (1, TimeUnit.MINUTES); פנקסי חזרה; } לתפוס (InterruptedException כלומר) {לזרוק RuntimeException חדש (כלומר); }} 

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

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

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

6. מסקנה

במאמר זה סקרנו את פרויקט Apache BookKeeper, התבוננו במושגי הליבה שלו והשתמשנו בממשק ה- API ברמה הנמוכה שלו כדי לגשת לספרי הספרים ולבצע פעולות קריאה / כתיבה.

כרגיל, כל הקוד זמין ב- GitHub.


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