Dette blogindlæg blev offentliggjort på Hortonworks.com før fusionen med Cloudera. Nogle links, ressourcer eller referencer er muligvis ikke længere nøjagtige.
Til dette indlæg tager vi et teknisk dybt dyk ned i et af kerneområderne i HBase. Specifikt vil vi se på, hvordan Apache HBase fordeler belastning gennem regioner og styrer regionsdeling. HBase gemmer rækker af data i tabeller. Tabeller er opdelt i bidder af rækker kaldet "regioner". Disse regioner er fordelt på tværs af klyngen, hostet og gjort tilgængelige for klientprocesser af RegionServer-processen. Et område er et kontinuerligt område inden for nøglerummet, hvilket betyder, at alle rækker i tabellen, der sorterer mellem regionens startnøgle og slutnøgle, er gemt i samme område. Regioner er ikke-overlappende, dvs. en enkelt rækkenøgle hører til præcis én region på et hvilket som helst tidspunkt. En region betjenes kun af en enkelt regionsserver på et hvilket som helst tidspunkt, hvilket er hvordan HBase garanterer stærk konsistens inden for en enkelt række#. Sammen med -ROOT- og .META. regioner, danner en tabels regioner effektivt et 3-niveau B-træ med det formål at lokalisere en række i en tabel.
En region består til gengæld af mange "Butikker", som svarer til kolonnefamilier. Et lager indeholder et memstore og nul eller flere butiksfiler. Dataene for hver kolonnefamilie gemmes og tilgås separat.
En tabel består typisk af mange regioner, som igen hostes af mange regionsservere. Således er regioner den fysiske mekanisme, der bruges til at fordele skrive- og forespørgselsbelastningen på tværs af regionsservere. Når en tabel først oprettes, vil HBase som standard kun tildele én region til tabellen. Det betyder, at i første omgang vil alle anmodninger gå til en enkelt regionsserver, uanset antallet af regionsservere. Dette er den primære årsag til, at indledende faser af indlæsning af data i en tom tabel ikke kan udnytte hele klyngens kapacitet.
Forudspaltning
Grunden til, at HBase kun opretter én region for tabellen, er, at den umuligt kan vide, hvordan man opretter splitpunkterne inden for rækketastrummet. At træffe sådanne beslutninger er i høj grad baseret på fordelingen af nøglerne i dine data. I stedet for at gætte og overlade dig til at håndtere konsekvenserne, giver HBase dig værktøjer til at styre dette fra klienten. Med en proces kaldet præ-opdeling, kan du oprette en tabel med mange regioner ved at levere splitpointene på tidspunktet for oprettelse af tabellen. Da præ-opdeling vil sikre, at den indledende belastning er mere jævnt fordelt i hele klyngen, bør du altid overveje at bruge den, hvis du kender din nøglefordeling på forhånd. Forudspaltning har dog også en risiko for at skabe områder, der ikke rigtigt fordeler belastningen jævnt på grund af dataskævhed eller i nærværelse af meget varme eller store rækker. Hvis det indledende sæt af regionsplitpunkter er valgt dårligt, kan du ende med heterogen belastningsfordeling, hvilket igen vil begrænse dine klyngers ydeevne.
Der er ikke noget kort svar på det optimale antal regioner for en given belastning, men du kan starte med et lavere multiplum af antallet af regionsservere som antal opdelinger, og derefter lade automatiseret opdeling tage sig af resten.
Et problem med præ-opdeling er at beregne splitpointene for tabellen. Du kan bruge værktøjet RegionSplitter. RegionSplitter opretter splitpunkterne ved at bruge en pluggbar SplitAlgorithm. HexStringSplit og UniformSplit er to foruddefinerede algoritmer. Førstnævnte kan bruges, hvis rækketasterne har et præfiks for hexadecimale strenge (som hvis du bruger hashes som præfikser). Sidstnævnte deler nøglerummet jævnt op, forudsat at de er tilfældige byte-arrays. Du kan også implementere din tilpassede SplitAlgorithm og bruge den fra RegionSplitter-værktøjet.
$ hbase org.apache.hadoop.hbase.util.RegionSplitter test_table HexStringSplit -c 10 -f f1
hvor -c 10, angiver det ønskede antal regioner som 10, og -f angiver de kolonnefamilier, du ønsker i tabellen, adskilt af ":". Værktøjet vil oprette en tabel med navnet "test_table" med 10 regioner:
13/01/18 18:49:32 DEBUG hbase.HRegionInfo: Current INFO from scan results = {NAME => 'test_table,,1358563771069.acc1ad1b7962564fc3a43e5907e8db33.', STARTKEY => '', ENDKEY => '19999999', ENCODED => acc1ad1b7962564fc3a43e5907e8db33,} 13/01/18 18:49:32 DEBUG hbase.HRegionInfo: Current INFO from scan results = {NAME => 'test_table,19999999,1358563771096.37ec12df6bd0078f5573565af415c91b.', STARTKEY => '19999999', ENDKEY => '33333332', ENCODED => 37ec12df6bd0078f5573565af415c91b,} ...
Hvis du har splitpoints ved hånden, kan du også bruge HBase-skallen til at lave bordet med de ønskede splitpoints.
hbase(main):015:0> create 'test_table', 'f1', SPLITS=> ['a', 'b', 'c']
eller
$ echo -e "anbnc" >/tmp/splits hbase(main):015:0> create 'test_table', 'f1', SPLITSFILE=>'/tmp/splits'
For optimal belastningsfordeling bør du tænke på din datamodel og nøglefordeling for at vælge den korrekte splitalgoritme eller splitpunkter. Uanset hvilken metode du valgte til at oprette tabellen med forudbestemt antal regioner, kan du nu begynde at indlæse dataene i tabellen og se, at belastningen er fordelt over hele din klynge. Du kan lade den automatiske opdeling tage over, når dataindlæsningen starter, og løbende overvåge det samlede antal regioner for tabellen.
Automatisk opdeling
Uanset om præ-opdeling bruges eller ej, bliver den automatisk opdelt i to områder, når en region når en vis grænse. Hvis du bruger HBase 0.94 (som følger med HDP-1.2), kan du konfigurere, hvornår HBase beslutter sig for at opdele en region, og hvordan den beregner splitpointene via den pluggbare RegionSplitPolicy API. Der er et par foruddefinerede regionsdelingspolitikker:ConstantSizeRegionSplitPolicy, IncreasingToUpperBoundRegionSplitPolicy og KeyPrefixRegionSplitPolicy.
Den første er standard- og kun opdelt politik for HBase-versioner før 0.94. Det opdeler regionerne, når den samlede datastørrelse for en af butikkerne (svarende til en kolonnefamilie) i regionen bliver større end konfigureret "hbase.hregion.max.filesize", som har en standardværdi på 10 GB. Denne opdelingspolitik er ideel i tilfælde, hvor du har foretaget præ-opdeling og er interesseret i at få et lavere antal regioner pr. regionsserver.
Standardopdelingspolitikken for HBase 0.94 og trunk er IncreasingToUpperBoundRegionSplitPolicy, som udfører mere aggressiv opdeling baseret på antallet af regioner, der hostes på den samme regionsserver. Opdelingspolitikken bruger maks. lagerfilstørrelse baseret på Min (R^2 * "hbase.hregion.memstore.flush.size", "hbase.hregion.max.filesize"), hvor R er antallet af regioner af samme tabel hostet på den samme regionserver. Så for eksempel, med standard memstore flush størrelse på 128MB og standard max store størrelse på 10GB, vil den første region på regionsserveren blive opdelt lige efter den første flush på 128MB. Efterhånden som antallet af regioner hostet i regionserveren stiger, vil den bruge stigende opdelte størrelser:512MB, 1152MB, 2GB, 3,2GB, 4,6GB, 6,2GB osv. Efter at have nået 9 regioner, vil opdelingsstørrelsen gå ud over den konfigurerede "hbase" .hregion.max.filesize", hvorefter 10 GB opdelt størrelse vil blive brugt fra da af. For begge disse algoritmer, uanset hvornår opdelingen finder sted, er det brugte splitpunkt den rækketast, der svarer til midtpunktet i "blokindekset" for den største butiksfil i den største butik.
KeyPrefixRegionSplitPolicy er en nysgerrig tilføjelse til HBase-arsenalet. Du kan konfigurere længden af præfikset for dine rækkenøgler til at gruppere dem, og denne opdelingspolitik sikrer, at regionerne ikke opdeles i midten af en gruppe rækker med det samme præfiks. Hvis du har sat præfikser for dine nøgler, så kan du bruge denne opdelingspolitik til at sikre, at rækker med det samme rækketastpræfiks altid ender i samme region. Denne gruppering af poster omtales nogle gange som "Entity Groups" eller "Row Groups". Dette er en nøglefunktion, når du overvejer at bruge funktionen "lokale transaktioner" (alternativt link) i dit applikationsdesign.
Du kan konfigurere standardopdelingspolitikken til brug ved at indstille konfigurationen "hbase.regionserver.region.split.policy", eller ved at konfigurere tabelbeskrivelsen. For jer modige sjæle kan I også implementere jeres egen brugerdefinerede opdelingspolitik og tilslutte den ved oprettelse af bordet eller ved at ændre en eksisterende tabel:
HTableDescriptor tableDesc = new HTableDescriptor("example-table"); tableDesc.setValue(HTableDescriptor.SPLIT_POLICY, AwesomeSplitPolicy.class.getName()); //add columns etc admin.createTable(tableDesc);
Hvis du laver præ-opdeling og ønsker at administrere regionsopdelinger manuelt, kan du også deaktivere regionopdelinger ved at indstille "hbase.hregion.max.filesize" til et højt tal og indstille opdelingspolitikken til ConstantSizeRegionSplitPolicy. Du bør dog bruge en beskyttelsesværdi på 100 GB, så regioner ikke vokser ud over en regionsservers muligheder. Du kan overveje at deaktivere automatiseret opdeling og stole på det indledende sæt af regioner fra for-opdeling, for eksempel, hvis du bruger ensartede hashes til dine nøglepræfikser, og du kan sikre, at læse/skrive indlæses til hver region såvel som dens størrelse er ensartet på tværs af regionerne i tabellen.
Tvungen opdeling
HBase gør det også muligt for klienter at tvinge opdeling af et online bord fra klientsiden. For eksempel kan HBase-skallen bruges til at opdele alle områder i tabellen eller opdele en region, eventuelt ved at angive et splitpunkt.
hbase(main):024:0> split 'b07d0034cbe72cb040ae9cf66300a10c', 'b' 0 row(s) in 0.1620 seconds
Med omhyggelig overvågning af din HBase-belastningsfordeling, hvis du ser, at nogle regioner får ujævne belastninger, kan du overveje at opdele disse områder manuelt for at udjævne belastningen og forbedre gennemløbet. En anden grund til, at du måske ønsker at lave manuelle opdelinger, er, når du ser, at de indledende opdelinger for regionen viser sig at være suboptimale, og du har deaktiveret automatiserede opdelinger. Det kan for eksempel ske, hvis datafordelingen ændrer sig over tid.
Sådan implementeres regionsopdelinger
Da skriveanmodninger håndteres af regionsserveren, akkumuleres de i et lagersystem i hukommelsen kaldet "memstore". Når memstore er fyldt, skrives dets indhold til disken som ekstra lagerfiler. Denne begivenhed kaldes en "memstore flush". Efterhånden som butiksfiler akkumuleres, vil RegionServeren "komprimere" dem til kombinerede, større filer. Efter hver skylning eller komprimering afsluttes, sættes en regionopdelingsanmodning i kø, hvis RegionSplitPolicy beslutter, at regionen skal opdeles i to. Da alle datafiler i HBase er uforanderlige, vil de nyoprettede datterområder ikke omskrive alle data til nye filer, når der sker en opdeling. I stedet vil de oprette små sym-link-lignende filer, kaldet referencefiler, som peger på enten den øverste eller nederste del af den overordnede butiksfil i henhold til splitpunktet. Referencefilen vil blive brugt ligesom en almindelig datafil, men kun halvdelen af posterne. Regionen kan kun opdeles, hvis der ikke er flere referencer til de uforanderlige datafiler i den overordnede region. Disse referencefiler renses gradvist ved komprimering, så regionen holder op med at referere til sine forældrefiler og kan opdeles yderligere.
Selvom opsplitning af regionen er en lokal beslutning, der træffes på RegionServeren, skal selve opdelingsprocessen koordineres med mange aktører. RegionServeren underretter Master før og efter opdelingen, opdaterer .META. tabel, så klienter kan opdage de nye datterområder, og omarrangerer mappestrukturen og datafilerne i HDFS. Split er en proces med flere opgaver. For at aktivere tilbagerulning i tilfælde af en fejl, fører RegionServeren en journal i hukommelsen om udførelsestilstanden. De trin, RegionServeren tager for at udføre opdelingen, er illustreret af figur 1. Hvert trin er mærket med dets trinnummer. Handlinger fra RegionServers eller Master vises med rødt, mens handlinger fra klienterne vises med grønt.
1. RegionServer beslutter lokalt at opdele regionen og forbereder opdelingen. Som et første trin opretter det en znode i zookeeper under /hbase/region-in-transition/region-name i OPDELT tilstand.
2. Mesteren lærer om denne znode, da den har en overvåger for den overordnede region-in-transition znode.
3. RegionServer opretter en undermappe med navnet ".splits" under forælderens regionmappe i HDFS.
4. RegionServer lukker den overordnede region, tvinger en skylning af cachen og markerer regionen som offline i dens lokale datastrukturer. På dette tidspunkt vil klientanmodninger, der kommer til den overordnede region, kaste NotServingRegionException. Klienten vil prøve igen med en vis backoff.
5. RegionServer opretter regionbibliotekerne under .splits-biblioteket for datterregion A og B og opretter nødvendige datastrukturer. Derefter opdeler den butiksfilerne i den forstand, at den opretter to referencefiler pr. butiksfil i den overordnede region. Disse referencefiler vil pege på de overordnede regionsfiler.
6. RegionServer opretter selve regionbiblioteket i HDFS og flytter referencefilerne for hver datter.
7. RegionServer sender en Put-anmodning til .META. tabel, og indstiller overordnet som offline i .META. tabel og tilføjer oplysninger om datterregioner. På dette tidspunkt vil der ikke være individuelle poster i .META. for døtrene. Klienter vil se, at den overordnede region er opdelt, hvis de scanner .META., men vil ikke vide om døtrene, før de vises i .META.. Også, hvis dette Sæt til .META. lykkes, bliver forælderen reelt splittet. Hvis RegionServer fejler, før denne RPC lykkes, vil Master og den næste regionsserver, der åbner regionen, rense den beskidte tilstand om regionopdelingen. Efter .META. Opdatering vil regionsopdelingen dog blive rullet frem af Master.
8. RegionServer åbner døtre parallelt for at acceptere skrivninger.
9. RegionServer tilføjer døtrene A og B til .META. sammen med oplysninger om, at det er vært for regionerne. Efter dette tidspunkt kan klienter opdage de nye regioner og udstede anmodninger til den nye region. Klienter cachelagrer .META. indtastninger lokalt, men når de sender anmodninger til regionsserveren eller .META., vil deres caches blive ugyldige, og de vil lære om de nye regioner fra .META..
10. RegionServer opdaterer znode /hbase/region-in-transition/region-name i zookeeper til at angive SPLIT, så masteren kan lære om det. Balanceren kan frit omtildele datterregionerne til andre regionsservere, hvis den vælger det.
11. Efter opdelingen vil meta og HDFS stadig indeholde referencer til den overordnede region. Disse referencer vil blive fjernet, når komprimeringer i datterregioner omskriver datafilerne. Skraldeopsamlingsopgaver i masteren kontrollerer med jævne mellemrum, om datterregionerne stadig henviser til forældrefiler. Hvis ikke, vil den overordnede region blive fjernet.
Region flettes
I modsætning til regionopdeling giver HBase på dette tidspunkt ikke brugbare værktøjer til at flette regioner. Selvom der er HMerge- og Merge-værktøjer, er de ikke særlig velegnede til generel brug. Der er i øjeblikket ingen understøttelse af online tabeller og automatisk fletningsfunktionalitet. Men med problemer som OnlineMerge, Master-initierede automatiske regionsammenlægninger, ZK-baserede læse/skrive-låse til tabeloperationer, arbejder vi på at stabilisere regionsopdelinger og muliggøre bedre understøttelse af regionsammenlægninger. Hold dig opdateret!
Konklusion
Som du kan se, laver HBase under hætten meget husholdning for at administrere regionsopdelinger og udføre automatiseret sharding gennem regioner. HBase leverer dog også de nødvendige værktøjer omkring regionsstyring, så du kan styre opsplitningsprocessen. Du kan også kontrollere præcist, hvornår og hvordan regionsopdelinger sker via en RegionSplitPolicy.
Antallet af regioner i en tabel, og hvordan disse regioner er opdelt, er afgørende faktorer for at forstå og justere din HBase-klyngebelastning. Hvis du kan estimere din nøglefordeling, bør du oprette tabellen med forhåndsopdeling for at få den optimale indledende belastningsydelse. Du kan starte med et lavere multiplum af antallet af regionsservere som udgangspunkt for det indledende antal regioner og lade automatiseret opdeling tage over. Hvis du ikke kan estimere de indledende opdelingspunkter korrekt, er det bedre bare at oprette tabellen med én region og starte en indledende indlæsning med automatiseret opdeling og bruge IncreasingToUpperBoundRegionSplitPolicy. Men husk på, at det samlede antal regioner vil stabilisere sig over tid, og det aktuelle sæt af regionsplitpunkter vil blive bestemt ud fra de data, som tabellen har modtaget indtil videre. Du vil måske til enhver tid overvåge belastningsfordelingen på tværs af regionerne, og hvis belastningsfordelingen ændrer sig over tid, skal du bruge manuel opdeling eller indstille mere aggressive regionopdelingsstørrelser. Til sidst kan du prøve den kommende online flettefunktion og bidrage med dit brugssag.