Guide til Apache BookKeeper

1. Oversigt

I denne artikel præsenterer vi BookKeeper, en service, der implementerer en distribueret, fejltolerant arkivsystem.

2. Hvad er? Bogholder?

BookKeeper blev oprindeligt udviklet af Yahoo som et ZooKeeper-underprojekt og blev gradueret til at blive et topniveau-projekt i 2015. BookKeeper sigter i sin kerne mod at være et pålideligt og højtydende system, der lagrer sekvenser af Log poster (aka Optegnelser) i datastrukturer kaldet Ledgers.

Et vigtigt træk ved hovedbøger er, at de kun er append og uforanderlige. Dette gør BookKeeper til en god kandidat til bestemte applikationer, såsom distribuerede logningssystemer, Pub-Sub-messaging-applikationer og realtids streambehandling.

3. BookKeeper-koncepter

3.1. Log poster

En logpost indeholder en udelelig enhed af data, som en klientapplikation gemmer til eller læser fra BookKeeper. Når det er gemt i en hovedbog, indeholder hver post de leverede data og et par metadatafelter.

Disse metadatafelter inkluderer en entryId, som skal være unik inden for en given hovedbog. Der er også en godkendelseskode, som BookKeeper bruger til at registrere, når en post er korrupt eller er blevet manipuleret med.

BookKeeper tilbyder ingen serialiseringsfunktioner i sig selv, så klienter skal udtænke deres egen metode til at konvertere konstruktioner på højere niveau til / fra byte arrays.

3.2. Ledgers

En hovedbog er den grundlæggende lagerenhed, der administreres af BookKeeper, der lagrer en ordnet sekvens af logposter. Som nævnt før har ledgers kun semantik, hvilket betyder, at poster ikke kan ændres, når de først er føjet til dem.

Også når en klient holder op med at skrive til en hovedbog og lukker den, BookKeeper sæler det, og vi kan ikke længere føje data til det, selv ikke på et senere tidspunkt. Dette er et vigtigt punkt at huske på, når du designer en applikation omkring BookKeeper. Ledgers er ikke en god kandidat til direkte implementering af konstruktioner på højere niveau, såsom en kø. I stedet ser vi ledgers brugt oftere til at skabe mere grundlæggende datastrukturer, der understøtter disse begreber på højere niveau.

For eksempel bruger Apache's Distribuerede log-projekt ledgers som logsegmenter. Disse segmenter samles i distribuerede logfiler, men de underliggende hovedbøger er gennemsigtige for almindelige brugere.

BookKeeper opnår resiliens modstandsdygtighed ved at replikere logposter på tværs af flere serverforekomster. Tre parametre styrer, hvor mange servere og kopier der opbevares:

  • Ensemble størrelse: antallet af servere, der bruges til at skrive storagedata
  • Skriv kvorumstørrelse: antallet af servere, der bruges til at replikere en given logindgang
  • Ack-kvorumstørrelse: antallet af servere, der skal anerkende en given logindtastning

Ved at justere disse parametre kan vi indstille ydeevnen og modstandsdygtigheden ved en given hovedbog. Når du skriver til en hovedbog, betragter BookKeeper kun operationen som vellykket, når et minimumsquorum af klyngemedlemmer anerkender det.

Ud over de interne metadata understøtter BookKeeper også tilføjelse af brugerdefinerede metadata til en hovedbog. Disse er et kort over nøgle / værdipar, som klienter passerer ved oprettelsestidspunktet, og BookKeeper gemmer i ZooKeeper sammen med sine egne.

3.3. Bookies

Bookies er servere, der indeholder en eller hovedbøger. En BookKeeper-klynge består af et antal bookmakere, der kører i et givet miljø og leverer tjenester til klienter via almindelige TCP- eller TLS-forbindelser.

Bookies koordinerer handlinger ved hjælp af klyngetjenester leveret af ZooKeeper. Dette indebærer, at hvis vi ønsker at opnå et fuldt fejltolerant system, har vi brug for mindst en 3-instans ZooKeeper og en 3-instans BookKeeper-opsætning. En sådan opsætning ville være i stand til at tolerere tab, hvis en enkelt forekomst mislykkes og stadig kan fungere normalt, i det mindste for standardboksopsætningen: 3-noders ensemblestørrelse, 2-node-skrivekvorum og 2-node ack-kvorum.

4. Lokal opsætning

De grundlæggende krav til at køre BookKeeper lokalt er ret beskedne. For det første har vi brug for en ZooKeeper-forekomst, der kan køre, som giver storagemetadatalagring til BookKeeper. Dernæst implementerer vi en bookie, der leverer de faktiske tjenester til klienter.

Selvom det helt sikkert er muligt at udføre disse trin manuelt, bruger vi her en docker-komponere fil, der bruger officielle Apache-billeder til at forenkle denne opgave:

$ cd $ docker-komponere op

Det her docker-komponere opretter tre bookmakere og en ZooKeeper-forekomst. Da alle bookmakere kører på den samme maskine, er det kun nyttigt til testformål. Den officielle dokumentation indeholder de nødvendige trin til at konfigurere en fuldt fejltolerant klynge.

Lad os lave en grundlæggende test for at kontrollere, at den fungerer som forventet ved hjælp af bogholderens shell-kommando listebøger:

$ docker execitit 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 

Outputtet viser listen over tilgængelige bookmakere, bestående af tre bookmakere. Bemærk, at de viste IP-adresser vil ændre sig afhængigt af specifikationen for den lokale Docker-installation.

5. Brug af Ledger API

Ledger API er den mest basale måde at grænseflade til BookKeeper på. Det giver os mulighed for at interagere direkte med Ledger objekter, men på den anden side mangler direkte støtte til abstraktioner på højere niveau, såsom strømme. For disse brugssager tilbyder BookKeeper-projektet et andet bibliotek, DistributedLog, som understøtter disse funktioner.

Brug af Ledger API kræver tilføjelse af bogholder-server afhængighed af vores projekt:

 org.apache.bookkeeper bogholder-server 4.10.0 

BEMÆRK: Som nævnt i dokumentationen inkluderer brug af denne afhængighed også afhængigheder for protobuf- og guava-bibliotekerne. Skulle vores projekt også have brug for disse biblioteker, men i en anden version end dem, der bruges af BookKeeper, kan vi bruge en alternativ afhængighed, der skygger disse biblioteker:

 org.apache.bookkeeper bogholder-server-skygget 4.10.0 

5.1. Opretter forbindelse til bookmakere

Det Bogholder klasse er hovedindgangsstedet for Ledger API, der giver et par metoder til at oprette forbindelse til vores BookKeeper-tjeneste. I sin enkleste form er alt, hvad vi skal gøre, at oprette en ny forekomst af denne klasse, der videregiver adressen til en af ​​de ZooKeeper-servere, der bruges af BookKeeper:

BookKeeper-klient = ny BookKeeper ("zookeeper-host: 2131"); 

Her, zookeeper-vært skal indstilles til IP-adressen eller værtsnavnet på ZooKeeper-serveren, der indeholder BookKeepers klyngekonfiguration. I vores tilfælde er det normalt “localhost” eller den vært, som DOCKER_HOST-miljøvariablen peger på.

Hvis vi har brug for mere kontrol over de forskellige parametre, der er tilgængelige for at finjustere vores klient, kan vi bruge en ClientConfiguration eksempel og brug den til at oprette vores klient:

ClientConfiguration cfg = ny ClientConfiguration (); cfg.setMetadataServiceUri ("zk + null: // zookeeper-host: 2131"); // ... sæt andre egenskaber BookKeeper.forConfig (cfg) .build ();

5.2. Oprettelse af en hovedbog

Når vi har en Bogholder eksempel, at oprette en ny hovedbog er ligetil:

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

Her har vi brugt den enkleste variant af denne metode. Det opretter en ny hovedbog med standardindstillinger ved hjælp af MAC-fordøjelsestypen for at sikre indgangsintegritet.

Hvis vi vil tilføje tilpassede metadata til vores hovedbog, skal vi bruge en variant, der tager alle parametre:

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

Denne gang har vi brugt den fulde version af createLedger () metode. De tre første argumenter er henholdsvis ensemblestørrelsen, skrive-kvorum og ack-kvorumværdier. Dernæst har vi de samme fordøjelsesparametre som før. Endelig passerer vi en Kort med vores brugerdefinerede metadata.

I begge tilfælde ovenfor createLedger er en synkron handling. BookKeeper tilbyder også oprettelse af asynkron hovedbog ved hjælp af tilbagekald:

bk.asyncCreateLedger (3, 2, 2, BookKeeper.DigestType.MAC, "passwd" .getBytes (), (rc, lh, ctx) -> {// ... brug lh til at få adgang til hovedoperations}, null, Collections .emptyMap ()); 

Nyere versioner af BookKeeper (> = 4.6) understøtter også et flydende API og Fuldført for at nå det samme mål:

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

Bemærk, at vi i dette tilfælde får en Skriv håndtag i stedet for en LedgerHandle. Som vi vil se senere, kan vi bruge enhver af dem til at få adgang til vores hovedbog som LedgerHandle redskaber Skriv håndtag.

5.3. Skrivning af data

Når vi først har erhvervet en LedgerHandle eller Skriv håndtag, vi skriver data til den tilknyttede hovedbog ved hjælp af en af Tilføj() metode varianter. Lad os starte med den synkrone variant:

for (int i = 0; i <MAX_MESSAGES; i ++) {byte [] data = ny streng ("besked-" + i) .getBytes (); lh.append (data); } 

Her bruger vi en variant, der tager en byte array. API'en understøtter også Netty's ByteBuf og Java NIO'er ByteBuffer, som giver bedre hukommelsesstyring i tidskritiske scenarier.

For asynkrone operationer adskiller API'en sig lidt afhængigt af den specifikke håndtagstype, vi har erhvervet. Skriv håndtag anvendelser Fuldførende fremtid der henviser til LedgerHandle understøtter også tilbagekaldsbaserede metoder:

// Tilgængelig i WriteHandle og LedgerHandle CompletableFuture f = lh.appendAsync (data); // Kun tilgængelig i LedgerHandle lh.asyncAddEntry (data, (rc, ledgerHandle, entryId, ctx) -> {// ... tilbagekaldslogik udeladt}, null);

Hvilken man skal vælge er stort set et personligt valg, men generelt ved hjælp af Fuldført-baserede API'er har tendens til at være lettere at læse. Der er også den sidefordel, som vi kan konstruere en Mono direkte fra det, hvilket gør det lettere at integrere BookKeeper i reaktive applikationer.

5.4. Læsning af data

Læsning af data fra en BookKeeper-hovedbog fungerer på samme måde som at skrive. For det første bruger vi vores Bogholder eksempel for at oprette en LedgerHandle:

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

Bortset fra ledgerId parameter, som vi vil dække senere, ligner denne kode meget createLedger () metode, vi har set før. Der er dog en vigtig forskel; denne metode returnerer en skrivebeskyttet LedgerHandle eksempel. Hvis vi prøver at bruge noget af det tilgængelige Tilføj() metoder, alt hvad vi får er en undtagelse.

Alternativt er en sikrere måde at bruge API'en med flydende stil:

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

ReadHandle har de nødvendige metoder til at læse data fra vores hovedbog:

long lastId = lh.readLastConfirmed (); rh.read (0, lastId) .forEach ((post) -> {// ... gør noget});

Her har vi simpelthen anmodet om alle tilgængelige data i denne hovedbog ved hjælp af synkron Læs variant. Som forventet er der også en asynkroniseringsvariant:

rh.readAsync (0, lastId) .thenAccept ((poster) -> {poster.forEach ((post) -> {// ... procesindgang});});

Hvis vi vælger at bruge den ældre openLedger () metode, finder vi yderligere metoder, der understøtter tilbagekaldstil for asynkroniseringsmetoder:

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

5.5. Notering Ledgers

Vi har tidligere set, at vi har brug for hovedbogen id for at åbne og læse dets data. Så hvordan får vi en? En måde er at bruge LedgerManager interface, som vi kan få adgang til fra vores Bogholder eksempel. Denne grænseflade beskæftiger sig stort set med metadata fra ledger, men har også asyncProcessLedgers () metode. Ved hjælp af denne metode - og noget hjælp til at danne samtidige primitiver - kan vi tælle alle tilgængelige hovedbøger:

public List listAllLedgers (BookKeeper bk) {List ledgers = Collections.synchronizedList (new ArrayList ()); CountDownLatch processDone = ny 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øv {processDone.await (1, TimeUnit.MINUTES); returbøger; } fange (InterruptedException ie) {throw new RuntimeException (ie); }} 

Lad os fordøje denne kode, som er lidt længere end forventet for en tilsyneladende triviel opgave. Det asyncProcessLedgers () Metoden kræver to tilbagekald.

Den første samler alle ledgers id'er på en liste. Vi bruger en synkroniseret liste her, fordi denne tilbagekald kan kaldes fra flere tråde. Udover hovedbog-id modtager denne tilbagekaldelse også en tilbagekaldsparameter. Vi må kalde det processResult () metode til at anerkende, at vi har behandlet dataene og signalere, at vi er klar til at få flere data.

Den anden tilbagekaldelse kaldes, når alle hovedbøger er sendt til processorens tilbagekald, eller når der er en fejl. I vores tilfælde har vi udeladt fejlhåndteringen. I stedet for reducerer vi bare en CountDownLatch, som igen vil afslutte vente betjening og lad metoden vende tilbage med en liste over alle ledige hovedbøger.

6. Konklusion

I denne artikel har vi dækket Apache BookKeeper-projektet, kigget på dets kernekoncepter og brugt dets lave niveau API til at få adgang til Ledgers og udføre læse / skrive-operationer.

Som sædvanlig er al kode tilgængelig på GitHub.


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