Útmutató az Apache BookKeeperhez

1. Áttekintés

Ebben a cikkben bemutatjuk a BookKeeper szolgáltatást, amely a elosztott, hibatűrő rekordtároló rendszer.

2. Mi az Könyvelő?

A BookKeeper-t eredetileg a Yahoo fejlesztette ki ZooKeeper alprojektként, és 2015-ben érettségizett egy legfelső szintű projektgé. Alapvető fontosságú, hogy a BookKeeper megbízható és nagy teljesítményű rendszer legyen, amely tárolja a Naplóbejegyzések (más néven Rekordok) nevű adatstruktúrákban Nagykönyvek.

A főkönyvek fontos jellemzője, hogy csak függelékben vannak és megváltoztathatatlanok. Ez a BookKeeper jó jelölt bizonyos alkalmazásokhoz, például elosztott naplózási rendszerek, Pub-Sub üzenetküldő alkalmazások és valós idejű adatfeldolgozáshoz.

3. BookKeeper fogalmak

3.1. Naplóbejegyzések

A naplóbejegyzés oszthatatlan adategységet tartalmaz, amelyet egy ügyfélalkalmazás tárol vagy elolvas a BookKeeper-től. Ha egy főkönyvben tárolja, minden bejegyzés tartalmazza a megadott adatokat és néhány metaadat mezőt.

Ezek a metaadat mezők tartalmaznak egy entryId, amelynek egyedinek kell lennie egy adott főkönyvön belül. Van egy hitelesítési kód is, amelyet a BookKeeper használ arra, hogy észlelje, ha egy bejegyzés sérült vagy azt megváltoztatták.

A BookKeeper önmagában nem kínál sorosítási funkciókat, ezért az ügyfeleknek meg kell találniuk a saját módszerüket a magasabb szintű konstrukciók konvertálására byte tömbök.

3.2. Nagykönyvek

A főkönyv a BookKeeper által kezelt alapvető tárolóegység, amely a naplóbejegyzések sorrendjét tárolja. Amint azt korábban említettük, a főkönyvekben csak mellékletet tartalmazó szemantika található, ami azt jelenti, hogy a rekordok nem módosíthatók, ha hozzá vannak adva.

Továbbá, ha az ügyfél abbahagyja az írást a főkönyvbe, és bezárja azt, a BookKeeper pecsétek és már nem adhatunk hozzá adatokat, még később sem. Ez egy fontos szempont, amelyet érdemes szem előtt tartani a BookKeeper körüli alkalmazás megtervezésekor. A főkönyvek nem alkalmasak a magasabb szintű konstrukciók közvetlen megvalósítására, például egy sor. Ehelyett azt látjuk, hogy a főkönyveket gyakrabban használják olyan alapvető adatstruktúrák létrehozására, amelyek támogatják ezeket a magasabb szintű fogalmakat.

Például az Apache Distributed Log projektje főkönyveket használ naplószegmensként. Ezeket a szegmenseket összesített naplókba összesítik, de a mögöttes főkönyvek átláthatóak a rendszeres felhasználók számára.

A BookKeeper a főkönyvi rugalmasságot úgy éri el, hogy a naplóbejegyzéseket több kiszolgálópéldányon replikálja. Három paraméter szabályozza, hogy hány szervert és másolatot tárolnak:

  • Együttes mérete: a főkönyv adatainak írásához használt kiszolgálók száma
  • Kvórum méret írása: az adott naplóbejegyzés replikálásához használt kiszolgálók száma
  • A kvórum mérete: azoknak a kiszolgálóknak a száma, amelyeknek nyugtázniuk kell egy adott naplóbejegyzés írási műveletet

Ezen paraméterek módosításával hangolhatjuk az adott főkönyv teljesítményének és rugalmasságának jellemzőit. Amikor a főkönyvbe ír, a BookKeeper csak akkor fogja sikeresnek tekinteni a műveletet, ha a klasztertagok minimális kvóruma elismeri.

A belső metaadatok mellett a BookKeeper támogatja az egyedi metaadatok hozzáadását a főkönyvhez. Ezek a kulcs / érték párok térképei, amelyeket az ügyfelek létrehozáskor átadnak, és a BookKeeper a sajátjával együtt a ZooKeeperben tárolja.

3.3. Bookies

A fogadóirodák olyan szerverek, amelyek egy vagy módfőkönyvet tartalmaznak. A BookKeeper fürt egy adott környezetben futó fogadóirodákból áll, amelyek sima TCP vagy TLS kapcsolaton keresztül nyújtanak szolgáltatásokat az ügyfeleknek.

A fogadóirodák a ZooKeeper által nyújtott fürtszolgáltatások segítségével koordinálják a tevékenységeket. Ez azt jelenti, hogy ha teljesen hibatűrő rendszert akarunk elérni, akkor legalább 3-példányos ZooKeeper-re és 3-példányos BookKeeper-beállításra van szükségünk. Egy ilyen beállítás képes lenne elviselni a veszteséget, ha egyetlen példány meghibásodik, és továbbra is képes normálisan működni, legalábbis az alapértelmezett főkönyvi beállítás esetében: 3 csomópontos együttes méret, 2 csomópontos írási kvórum és 2 csomópontos aktuális kvórum.

4. Helyi beállítás

A BookKeeper helyi futtatásának alapvető követelményei meglehetősen szerények. Először is szükségünk van egy ZooKeeper példányra, amely fut, és amely biztosítja a főkönyv metaadat-tárolását a BookKeeper számára. Ezután telepítünk egy fogadóirodát, amely a tényleges szolgáltatásokat nyújtja az ügyfeleknek.

Bár minden bizonnyal lehetséges ezeket a lépéseket manuálisan elvégezni, itt a dokkoló-ír fájl, amely hivatalos Apache képeket használ a feladat egyszerűsítésére:

$ cd $ docker-compose

Ez dokkoló-ír három fogadóirodát és egy ZooKeeper példányt hoz létre. Mivel minden fogadó ugyanazon a gépen fut, csak tesztelési célokra használható. A hivatalos dokumentáció tartalmazza a teljesen hibatűrő fürt konfigurálásához szükséges lépéseket.

Végezzünk el egy alaptesztet annak ellenőrzésére, hogy a várt módon működik-e, a könyvelő shell parancsával listbookik:

$ 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 

A kimenet az elérhető listát mutatja bukmékerek, amely három fogadóirodából áll. Felhívjuk figyelmét, hogy a megjelenített IP-címek a helyi Docker telepítés sajátosságaitól függően változnak.

5. A Ledger API használata

A Ledger API a BookKeeperrel való interfész legalapvetőbb módja. Ez lehetővé teszi számunkra, hogy közvetlenül lépjünk kapcsolatba Főkönyv objektumok, de másrészről nincs közvetlen támogatása a magasabb szintű absztrakcióknak, például a patakoknak. Ezeknek a felhasználási eseteknek a BookKeeper projekt egy másik könyvtárat, a DistributedLog programot kínál, amely támogatja ezeket a szolgáltatásokat.

A Ledger API használatához hozzá kell adni a könyvelő-szerver függőség a projektünktől:

 org.apache.bookkeeper könyvelő-szerver 4.10.0 

MEGJEGYZÉS: A dokumentációban leírtak szerint ennek a függőségnek a használata a protobuf és a guava könyvtár függőségeit is magában foglalja. Ha a projektünknek is szüksége lenne ezekre a könyvtárakra, de a BookKeeper által használtaktól eltérő verzióval, akkor alternatív függőséget használhatunk, amely árnyékolja ezeket a könyvtárakat:

 org.apache.bookkeeper könyvelő-szerver árnyékolt 4.10.0 

5.1. Csatlakozás a Bookies-hez

A Könyvelő osztály a Ledger API fő belépési pontja, néhány módszert biztosít a BookKeeper szolgáltatáshoz való csatlakozáshoz. A legegyszerűbb formájában mindössze annyit kell tennünk, hogy létrehozunk egy új példányt ebből az osztályból, átadva a BookKeeper által használt egyik ZooKeeper szerver címét:

BookKeeper kliens = new BookKeeper ("zookeeper-host: 2131"); 

Itt, állattartó-házigazda a BookKeeper fürtkonfigurációval rendelkező ZooKeeper szerver IP-címére vagy gazdagépnevére kell állítani. Esetünkben általában ez a „localhost” vagy az a gazdagép, amelyre a DOCKER_HOST környezeti változó mutat.

Ha jobban ellenőriznünk kell az ügyfelünk finomhangolásához rendelkezésre álló számos paramétert, használhatjuk a ClientConfiguration példány, és felhasználja az ügyfél létrehozásához:

ClientConfiguration cfg = new ClientConfiguration (); cfg.setMetadataServiceUri ("zk + null: // zookeeper-host: 2131"); // ... egyéb tulajdonságok beállítása BookKeeper.forConfig (cfg) .build ();

5.2. Főkönyv létrehozása

Miután megvan a Könyvelő Például egy új főkönyv létrehozása egyszerű:

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

Itt a módszer legegyszerűbb változatát használtuk. Létrehoz egy új főkönyvet alapértelmezett beállításokkal, a MAC összefoglaló típust használva a bejegyzés integritásának biztosítása érdekében.

Ha egyéni metaadatokat akarunk hozzáadni a főkönyvünkhöz, akkor egy olyan változatot kell használnunk, amely az összes paramétert felveszi:

LedgerHandle lh = bk.createLedger (3, 2, 2, DigestType.MAC, "jelszó" .getBytes (), Collections.singletonMap ("név", "én-főkönyv" .getBytes ()));

Ezúttal a teljes verzióját használtuk createLedger () módszer. A három első argumentum az együttes nagysága, az írási kvórum és az ack kvórum értéke. Ezután ugyanazok az emésztési paraméterek vannak, mint korábban. Végül elhaladunk a Térkép egyedi metaadatainkkal.

Mindkét fenti esetben createLedger egy szinkron művelet. A BookKeeper emellett aszinkron főkönyvi létrehozást kínál visszahívás segítségével:

bk.asyncCreateLedger (3, 2, 2, BookKeeper.DigestType.MAC, "passwd" .getBytes (), (rc, lh, ctx) -> {// ... használja az lh-t a főkönyv műveletek eléréséhez}, null, Gyűjtemények .emptyMap ()); 

A BookKeeper újabb verziói (> = 4.6) szintén támogatják a folyékony stílusú API-t és CompletableFuture ugyanazon cél elérése érdekében:

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

Vegye figyelembe, hogy ebben az esetben a WriteHandle a helyett LedgerHandle. Mint később látni fogjuk, bármelyiket felhasználhatjuk a főkönyvünk elérésére LedgerHandle megvalósítja WriteHandle.

5.3. Adatok írása

Miután megszereztük a LedgerHandle vagy WriteHandle, adatokat írunk a társított főkönyvbe az egyik használatával mellékel() módszerváltozatok. Kezdjük a szinkron változattal:

for (int i = 0; i <MAX_MESSAGES; i ++) {byte [] adatok = új karakterlánc ("üzenet-" + i) .getBytes (); lh.append (adatok); } 

Itt egy olyan változatot használunk, amely a byte sor. Az API támogatja a Netty-eket is ByteBuf és a Java NIO-k ByteBuffer, amelyek jobb memóriakezelést tesznek lehetővé az időkritikus esetekben.

Aszinkron műveleteknél az API kissé eltér a megszerzett fogantyútípustól. WriteHandle használ CompletableFuture, mivel LedgerHandle támogatja a visszahívás-alapú módszereket is:

// Elérhető a WriteHandle és a LedgerHandle CompletableFuture f = lh.appendAsync (adatok); // Csak a LedgerHandle könyvtárban érhető el lh.asyncAddEntry (data, (rc, ledgerHandle, entryId, ctx) -> {// ... visszahívási logika elhagyva}, null);

Az, hogy melyiket választja, nagyrészt személyes választás, de általában használva CompletableFuture-alapú API-k általában könnyebben olvashatók. Ezenkívül van egy mellékhatás is, amelyet megalkothatunk a Monó közvetlenül onnan, megkönnyítve a BookKeeper integrálását a reaktív alkalmazásokba.

5.4. Adatok olvasása

Az adatok olvasása a BookKeeper főkönyvéből az íráshoz hasonlóan működik. Először is a mi Könyvelő példány létrehozásához LedgerHandle:

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

Kivéve a ledgerId paraméter, amelyről később kitérünk, ez a kód nagyjából hasonlít a createLedger () módszer, amelyet korábban láttunk. Van azonban egy fontos különbség; ez a módszer csak olvasható eredményt ad vissza LedgerHandle példa. Ha megpróbáljuk felhasználni a rendelkezésre álló bármelyiket mellékel() módszerek, csak kivételeket kapunk.

Alternatív megoldásként biztonságosabb módszer a folyékony stílusú API használata:

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

ReadHandle rendelkezik a szükséges módszerekkel a főkönyvünk adatainak kiolvasásához:

long lastId = lh.readLastConfirmed (); rh.read (0, lastId) .forEach ((entry) -> {// ... csinálj valamit});

Itt egyszerűen a szinkron segítségével kértük az összes rendelkezésre álló adatot ebben a főkönyvben olvas változat. A várakozásoknak megfelelően van egy aszinkron változat is:

rh.readAsync (0, lastId) .theAccept ((bejegyzések) -> {bejegyzések.forEach ((bejegyzés) -> {// ... folyamatbejegyzés});});

Ha a régebbi mellett döntünk openLedger () módszerrel, további módszereket találunk, amelyek támogatják az aszinkron módszerek visszahívási stílusát:

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

5.5. Nagykönyvek listázása

Korábban láttuk, hogy szükségünk van a főkönyvre id hogy kinyissa és olvassa az adatait. Szóval, hogyan szerezzünk egyet? Az egyik módszer a LedgerManager felület, amelyhez a mi Könyvelő példa. Ez a felület alapvetően a főkönyvi metaadatokkal foglalkozik, de rendelkezik a asyncProcessLedgers () módszer. Ezzel a módszerrel - és néhány segítséggel egyidejű primitívek kialakításában - felsorolhatjuk az összes rendelkezésre álló főkönyvet:

public List listAllLedgers (BookKeeper bk) {List ledgers = Collections.synchronizedList (new ArrayList ()); CountDownLatch processDone = új 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); próbáld ki a {processDone.await (1, TimeUnit.MINUTES); visszaküldött főkönyvek; } catch (InterruptedException ie) {dob új RuntimeException (ie); }} 

Emésztjük meg ezt a kódot, amely egy kissé hosszabb, mint amire egy látszólag triviális feladat miatt számítani lehetett. A asyncProcessLedgers () A módszer két visszahívást igényel.

Az első összegyűjti az összes főkönyv azonosítót egy listában. Itt szinkronizált listát használunk, mert ez a visszahívás több szálból is meghívható. A főkönyv azonosító mellett ez a visszahívás visszahívási paramétert is kap. Fel kell hívnunk processResult () módszer annak elismerésére, hogy feldolgoztuk az adatokat, és jelezzük, hogy készen állunk további adatok megszerzésére.

A második visszahívást akkor hívják meg, ha az összes főkönyvet elküldte a processzor visszahívásának, vagy ha hiba történt. Esetünkben kihagytuk a hibakezelést. Ehelyett csak csökkentjük a CountDownLatch, ami viszont befejezi a várják műveletet, és hagyja, hogy a metódus visszatérjen az összes rendelkezésre álló főkönyv listájával.

6. Következtetés

Ebben a cikkben bemutattuk az Apache BookKeeper projektet, áttekintve annak alapkoncepcióit, és alacsony szintű API-ját használva a Ledgers elérésére és az olvasási / írási műveletek végrehajtására.

Szokás szerint az összes kód elérhető a GitHubon.