Sprievodca po Apache BookKeeper

1. Prehľad

V tomto článku predstavíme službu BookKeeper, ktorá implementuje a distribuovaný, na chyby odolný systém na ukladanie záznamov.

2. Čo je BookKeeper?

BookKeeper bol pôvodne vyvinutý spoločnosťou Yahoo ako subprojekt ZooKeeper a jeho promóciou sa stal projekt najvyššej úrovne v roku 2015. Vo svojej podstate si BookKeeper kladie za cieľ byť spoľahlivým a vysoko výkonným systémom, ktorý ukladá sekvencie Záznamy do denníka (alias Záznamy) v dátových štruktúrach tzv Ledgers.

Dôležitou vlastnosťou účtovných kníh je skutočnosť, že sú iba pripojiteľné a nemenné. Vďaka tomu je BookKeeper dobrým kandidátom pre určité aplikácie, ako sú distribuované systémy protokolovania, aplikácie pre zasielanie správ Pub-Sub a spracovanie toku v reálnom čase.

3. Koncepty BookKeeper

3.1. Záznamy do denníka

Položka denníka obsahuje nedeliteľnú jednotku údajov, ktoré klientska aplikácia ukladá alebo číta z BookKeeper. Keď sú uložené v hlavnej knihe, každý záznam obsahuje dodané údaje a niekoľko polí metaúdajov.

Tieto polia metadát zahŕňajú entryId, ktoré musia byť v rámci danej účtovnej knihy jedinečné. K dispozícii je tiež autentifikačný kód, ktorý BookKeeper používa na zistenie, či je záznam poškodený alebo či bol neoprávnene upravený.

BookKeeper sám o sebe neponúka žiadne funkcie serializácie, takže klienti musia navrhnúť vlastnú metódu na prevod konštruktov vyššej úrovne na / z bajt polia.

3.2. Ledgers

Účtovná kniha je základná úložná jednotka spravovaná spoločnosťou BookKeeper, ktorá ukladá usporiadanú postupnosť položiek protokolu. Ako už bolo spomenuté, účtovné knihy majú sémantiku iba na pripojenie, čo znamená, že záznamy už nie je možné upravovať, keď sa k nim pridajú.

Akonáhle klient prestane písať do hlavnej knihy a zavrie ju, BookKeeper pečate a už k tomu nebudeme môcť pridávať údaje, a to ani neskôr. Toto je dôležitý bod, ktorý treba mať na pamäti pri navrhovaní aplikácie okolo BookKeeper. Účtovné knihy nie sú dobrým kandidátom na priamu implementáciu konštrukcií vyššej úrovne, napríklad poradie. Namiesto toho vidíme účtovné knihy, ktoré sa používajú častejšie na vytváranie základných dátových štruktúr, ktoré podporujú tieto koncepty na vyššej úrovni.

Napríklad projekt Distribuovaného protokolu Apache používa účtovné knihy ako segmenty protokolu. Tieto segmenty sa agregujú do distribuovaných denníkov, ale podkladové knihy sú pre bežných používateľov transparentné.

BookKeeper dosahuje odolnosť účtovných kníh replikáciou položiek protokolu na viacerých inštanciách servera. Tri parametre určujú, koľko serverov a kópií sa uchováva:

  • Veľkosť súboru: počet serverov použitých na zápis údajov hlavnej knihy
  • Veľkosť kvóra na zápis: počet serverov použitých na replikáciu danej položky protokolu
  • Ack quorum size: počet serverov, ktoré musia potvrdiť danú operáciu zápisu položky záznamu

Úpravou týchto parametrov môžeme vyladiť výkonové a pružnostné charakteristiky danej účtovnej knihy. Pri zápise do hlavnej knihy bude BookKeeper považovať operáciu za úspešnú, iba ak ju uzná minimálne kvórum členov klastra.

Okrem svojich interných metadát podporuje BookKeeper aj pridávanie vlastných metadát do hlavnej knihy. Jedná sa o mapu párov kľúč / hodnota, ktorú klienti odovzdajú v čase vytvorenia, a obchody BookKeeper v ZooKeeper spolu so svojimi vlastnými.

3.3. Bookmakeri

Bookmakeers sú servery, ktoré obsahujú jednu alebo celú knihu režimov. Klaster BookKeeper sa skladá z mnohých bookmakrov, ktorí bežia v danom prostredí a poskytujú služby klientom prostredníctvom jednoduchých pripojení TCP alebo TLS.

Bookmarti koordinujú akcie pomocou klastrových služieb poskytovaných ZooKeeper. To znamená, že ak chceme dosiahnuť systém plne odolný voči chybám, potrebujeme minimálne 3-inštančné nastavenie ZooKeeper a 3-inštančné nastavenie BookKeeper. Takéto nastavenie by bolo schopné tolerovať stratu, ak niektorá jednotlivá inštancia zlyhá, a stále by dokázalo fungovať normálne, prinajmenšom pre predvolené nastavenie účtovnej knihy: veľkosť súboru s 3 uzlami, kvórum na zapísanie do 2 uzlov a kvórum na zadanie do 2 uzlov.

4. Lokálne nastavenie

Základné požiadavky na miestne spustenie aplikácie BookKeeper sú pomerne skromné. Najskôr potrebujeme, aby bola spustená inštancia ZooKeeper, ktorá poskytuje BookKeeper úložisko metadát hlavnej knihy. Ďalej nasadíme bookmakera, ktorý poskytuje skutočné služby klientom.

Aj keď je určite možné vykonať tieto kroky ručne, tu použijeme a ukotviť-zložiť súbor, ktorý používa oficiálne obrázky Apache na zjednodušenie tejto úlohy:

$ cd $ docker - zostaviť

Toto ukotviť-zložiť vytvorí tri bookmakery a inštanciu ZooKeeper. Pretože všetky bookmakery fungujú na rovnakom stroji, je to užitočné iba na účely testovania. Oficiálna dokumentácia obsahuje kroky potrebné na konfiguráciu klastra plne odolného voči chybám.

Urobíme základný test, ktorý pomocou príkazu shellu účtovníka skontroluje, či funguje podľa očakávania zoznamy kníh:

$ docker exec -it apache-bookkeeper_bookie_1 / opt / bookkeeper / 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 

Výstup zobrazuje zoznam dostupných bookmakri, pozostávajúci z troch bookmakrov. Upozorňujeme, že zobrazené adresy IP sa budú meniť v závislosti od špecifík miestnej inštalácie Dockeru.

5. Používanie rozhrania Ledger API

Ledger API je najzákladnejším spôsobom prepojenia s BookKeeper. Umožňuje nám priamo komunikovať Hlavná kniha objekty, ale na druhej strane postráda priamu podporu pre abstrakcie vyššej úrovne, ako sú toky. Pre tieto prípady použitia ponúka projekt BookKeeper ďalšiu knižnicu DistributedLog, ktorá tieto funkcie podporuje.

Používanie rozhrania Ledger API vyžaduje pridanie súboru účtovník-server závislosť na našom projekte:

 org.apache.bookkeeper bookkeeper-server 4.10.0 

POZNÁMKA: Ako je uvedené v dokumentácii, použitie tejto závislosti bude tiež zahŕňať závislosti pre knižnice protobuf a guava. Ak by náš projekt tiež potreboval tieto knižnice, ale v inej verzii, ako používa BookKeeper, mohli by sme použiť alternatívnu závislosť, ktorá tieto knižnice zatieňuje:

 org.apache.bookkeeper bookkeeper-server-shaded 4.10.0 

5.1. Pripojenie k bookmakerom

The BookKeeper trieda je hlavným vstupným bodom rozhrania Ledger API, ktorý poskytuje niekoľko spôsobov pripojenia k našej službe BookKeeper. V najjednoduchšej podobe musíme vytvoriť novú inštanciu tejto triedy a odovzdať adresu jedného zo serverov ZooKeeper, ktorý používa BookKeeper:

Klient BookKeeper = nový BookKeeper ("zookeeper-host: 2131"); 

Tu, zookeeper-hostiteľ by mala byť nastavená na IP adresu alebo názov hostiteľa servera ZooKeeper, ktorý obsahuje konfiguráciu klastra BookKeeper. V našom prípade to je zvyčajne „localhost“ alebo hostiteľ, na ktorého ukazuje premenná prostredia DOCKER_HOST.

Ak potrebujeme väčšiu kontrolu nad niekoľkými parametrami, ktoré sú k dispozícii na doladenie nášho klienta, môžeme použiť a ClientConfiguration inštanciu a použijeme ju na vytvorenie nášho klienta:

ClientConfiguration cfg = nový ClientConfiguration (); cfg.setMetadataServiceUri ("zk + null: // zookeeper-host: 2131"); // ... nastaviť ďalšie vlastnosti BookKeeper.forConfig (cfg) .build ();

5.2. Vytvorenie hlavnej knihy

Keď už máme BookKeeper napríklad vytvorenie novej účtovnej knihy je jednoduché:

LedgerHandle lh = bk.createLedger (BookKeeper.DigestType.MAC, "heslo" .getBytes ());

Tu sme použili najjednoduchší variant tejto metódy. Vytvorí novú účtovnú knihu s predvoleným nastavením pomocou typu prehľadu MAC na zabezpečenie integrity záznamu.

Ak chceme do našej účtovnej knihy pridať vlastné metadáta, musíme použiť variant, ktorý berie všetky parametre:

LedgerHandle lh = bk.createLedger (3, 2, 2, DigestType.MAC, "heslo" .getBytes (), Collections.singletonMap ("meno", "moja-hlavna kniha" .getBytes ()));

Tentokrát sme použili úplnú verziu createLedger () metóda. Tri prvé argumenty sú veľkosť súboru, hodnoty kvóra na zapísanie a hodnoty ack kvóra. Ďalej máme rovnaké parametre trávenia ako predtým. Nakoniec minieme a Mapa s našimi vlastnými metadátami.

V obidvoch vyššie uvedených prípadoch createLedger je synchrónna operácia. BookKeeper tiež ponúka asynchrónne vytváranie účtovných kníh pomocou spätného volania:

bk.asyncCreateLedger (3, 2, 2, BookKeeper.DigestType.MAC, "passwd" .getBytes (), (rc, lh, ctx) -> {// ... použiť lh na prístup k operáciám hlavnej knihy}, null, Zbierky .emptyMap ()); 

Novšie verzie aplikácie BookKeeper (> = 4,6) tiež podporujú plynulé rozhranie API a CompletableFuture na dosiahnutie rovnakého cieľa:

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

Upozorňujeme, že v tomto prípade dostaneme a WriteHandle namiesto a LedgerHandle. Ako uvidíme neskôr, môžeme použiť ktorékoľvek z nich na prístup do našej účtovnej knihy ako LedgerHandle náradie WriteHandle.

5.3. Zápis údajov

Akonáhle sme získali LedgerHandle alebo WriteHandle, zapisujeme údaje do priradenej účtovnej knihy pomocou jednej z pridať () varianty metód. Začnime so synchrónnym variantom:

for (int i = 0; i <MAX_MESSAGES; i ++) {byte [] data = new String ("message-" + i) .getBytes (); lh.append (údaje); } 

Tu používame variant, ktorý má a bajt pole. API tiež podporuje Netty's ByteBuf a Java NIO ByteBuffer, ktoré umožňujú lepšiu správu pamäte v časovo kritických scenároch.

Pre asynchrónne operácie sa API trochu líši v závislosti od konkrétneho typu popisovača, ktorý sme získali. WriteHandle používa CompletableFuture, keďže LedgerHandle podporuje aj metódy založené na spätnom volaní:

// Dostupné v WriteHandle a LedgerHandle CompletableFuture f = lh.appendAsync (data); // Dostupné iba v LedgerHandle lh.asyncAddEntry (data, (rc, ledgerHandle, entryId, ctx) -> {// ... vynechaná logika spätného volania}, null);

Ktorý z nich si vyberiete, je zväčša osobná voľba, ale vo všeobecnosti je to použitie CompletableFutureAPI založené na API majú tendenciu byť ľahšie čitateľné. Existuje tiež vedľajšia výhoda, ktorú môžeme vytvoriť Mono priamo z nej, čo uľahčuje integráciu BookKeeper do reaktívnych aplikácií.

5.4. Čítanie údajov

Čítanie údajov z knihy BookKeeper funguje podobným spôsobom ako zápis. Najprv použijeme náš BookKeeper napríklad na vytvorenie a LedgerHandle:

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

Okrem ledgerId parametrom, ktorému sa budeme venovať neskôr, vyzerá tento kód podobne ako createLedger () metóda, ktorú sme už videli. Je tu však dôležitý rozdiel; táto metóda vráti iba na čítanie LedgerHandle inštancia. Ak sa pokúsime použiť niektorú z dostupných možností pridať () metódy, dostaneme iba výnimku.

Bezpečnejším spôsobom je tiež použitie rozhrania API pre plynulý štýl:

ReadHandle rh = bk.newOpenLedgerOp () .withLedgerId (ledgerId) .withDigestType (DigestType.MAC) .withPassword ("heslo" .getBytes ()) .execute () .get (); 

ReadHandle má požadované metódy na čítanie údajov z našej hlavnej knihy:

long lastId = lh.readLastConfirmed (); rh.read (0, lastId) .forEach ((záznam) -> {// ... niečo urobte});

Tu sme pomocou synchrónneho kódu jednoducho vyžiadali všetky dostupné údaje v tejto knihe čítať varianta. Podľa očakávania existuje aj asynchronný variant:

rh.readAsync (0, lastId) .thenAccept ((položky) -> {entries.forEach ((položka) -> {// ... položka procesu});});

Ak sa rozhodneme použiť staršie openLedger () nájdeme ďalšie metódy, ktoré podporujú štýl spätného volania pre asynchronné metódy:

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

5.5. Zoznamy účtov

Už sme videli, že potrebujeme účtovnú knihu id otvoriť a prečítať jeho údaje. Ako na to? Jedným zo spôsobov je použitie LedgerManager rozhranie, ku ktorému máme prístup z nášho BookKeeper inštancia. Toto rozhranie sa v zásade zaoberá metadátami hlavnej knihy, ale má aj asyncProcessLedgers () metóda. Pomocou tejto metódy - a nejakej pomoci pri vytváraní súbežných primitívov - môžeme vymenovať všetky dostupné knihy:

public List listAllLedgers (BookKeeper bk) {List ledgers = Collections.synchronizedList (new ArrayList ()); CountDownLatch processDone = nový 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); try {processDone.await (1, TimeUnit.MINUTES); spiatočné knihy; } catch (InterruptedException tj.) {hod novy RuntimeException (tj); }} 

Poďme stráviť tento kód, ktorý je o niečo dlhší, ako sa očakávalo pre zdanlivo triviálnu úlohu. The asyncProcessLedgers () metóda vyžaduje dve spätné volania.

Prvý zhromažďuje všetky identifikátory účtovných kníh v zozname. Používame tu synchronizovaný zoznam, pretože toto spätné volanie je možné volať z viacerých vlákien. Okrem identifikácie hlavnej knihy toto spätné volanie prijíma aj parameter spätného volania. Musíme to nazvať processResult () metóda na potvrdenie, že sme údaje spracovali, a na signalizáciu, že sme pripravení získať viac údajov.

Druhé spätné volanie sa zavolá, keď sa všetky účtovné knihy odošlú na spätné volanie procesora alebo keď dôjde k poruche. V našom prípade sme vynechali spracovanie chýb. Namiesto toho iba znižujeme a CountDownLatch, ktorá následne dokončí čakať operáciu a umožniť návrat metódy so zoznamom všetkých dostupných účtovných kníh.

6. Záver

V tomto článku sme sa venovali projektu Apache BookKeeper, pričom sme sa pozreli na jeho základné koncepty a pomocou jeho nízkoúrovňového API na prístup k Ledgerom a vykonávanie operácií čítania a zápisu.

Ako obvykle je všetok kód k dispozícii na GitHub.


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