Det här blogginlägget publicerades på Hortonworks.com före sammanslagningen med Cloudera. Vissa länkar, resurser eller referenser kanske inte längre är korrekta.
För det här inlägget tar vi en teknisk djupdykning i ett av HBases kärnområden. Specifikt kommer vi att titta på hur Apache HBase fördelar belastning genom regioner och hanterar regionuppdelning. HBase lagrar rader med data i tabeller. Tabeller är uppdelade i delar av rader som kallas "regioner". Dessa regioner är fördelade över klustret, värd och görs tillgängliga för klientprocesser av RegionServer-processen. En region är ett kontinuerligt intervall inom nyckelutrymmet, vilket innebär att alla rader i tabellen som sorterar mellan regionens startnyckel och slutnyckel lagras i samma region. Regioner är icke-överlappande, d.v.s. en enda radnyckel tillhör exakt en region vid vilken tidpunkt som helst. En region betjänas endast av en enda regionserver vid någon tidpunkt, vilket är hur HBase garanterar stark konsistens inom en enda rad#. Tillsammans med -ROOT- och .META. regioner, bildar en tabells regioner ett B-träd på tre nivåer i syfte att lokalisera en rad i en tabell.
En region består i sin tur av många "butiker", som motsvarar kolumnfamiljer. En butik innehåller en memstore och noll eller fler butiksfiler. Data för varje kolumnfamilj lagras och nås separat.
En tabell består vanligtvis av många regioner, som i sin tur är värd för många regionservrar. Således är regioner den fysiska mekanismen som används för att fördela skriv- och frågebelastningen över regionservrar. När en tabell först skapas kommer HBase, som standard, endast att tilldela en region för tabellen. Detta innebär att till en början kommer alla förfrågningar att gå till en enda regionserver, oavsett antalet regionservrar. Detta är den främsta anledningen till att de inledande faserna av att ladda data till en tom tabell inte kan utnyttja hela klustrets kapacitet.
Föruppdelning
Anledningen till att HBase bara skapar en region för tabellen är att den omöjligt kan veta hur man skapar delade punkterna inom radnyckelutrymmet. Att fatta sådana beslut baseras i hög grad på fördelningen av nycklarna i din data. Istället för att ta en gissning och låta dig ta hand om konsekvenserna, ger HBase dig verktyg för att hantera detta från klienten. Med en process som kallas föruppdelning kan du skapa en tabell med många regioner genom att ange delningspunkterna när tabellen skapas. Eftersom fördelning säkerställer att den initiala belastningen är mer jämnt fördelad över hela klustret, bör du alltid överväga att använda den om du känner till din nyckelfördelning i förväg. Föruppdelning har dock också en risk för att skapa regioner som inte riktigt fördelar belastningen jämnt på grund av skev data eller i närvaro av mycket heta eller stora rader. Om den initiala uppsättningen av regiondelningspunkter väljs dåligt, kan du sluta med heterogen lastfördelning, vilket i sin tur kommer att begränsa dina klusterprestanda.
Det finns inget kort svar för det optimala antalet regioner för en given belastning, men du kan börja med en lägre multipel av antalet regionservrar som antal uppdelningar, och sedan låta automatisk uppdelning ta hand om resten.
Ett problem med fördelning är att beräkna splitpoängen för tabellen. Du kan använda verktyget RegionSplitter. RegionSplitter skapar delningspunkterna med hjälp av en pluggbar SplitAlgorithm. HexStringSplit och UniformSplit är två fördefinierade algoritmer. Den förra kan användas om radnycklarna har ett prefix för hexadecimala strängar (som om du använder hash som prefix). Den senare delar upp nyckelutrymmet jämnt förutsatt att de är slumpmässiga byte-arrayer. Du kan också implementera din anpassade SplitAlgorithm och använda den från RegionSplitter-verktyget.
$ hbase org.apache.hadoop.hbase.util.RegionSplitter test_table HexStringSplit -c 10 -f f1
där -c 10, anger det begärda antalet regioner som 10, och -f anger de kolumnfamiljer du vill ha i tabellen, separerade med ":". Verktyget skapar en tabell med namnet "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,} ...
Om du har delade poäng till hands kan du också använda HBase-skalet för att skapa tabellen med de önskade delade poängen.
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'
För optimal belastningsfördelning bör du tänka på din datamodell och nyckelfördelning för att välja rätt delad algoritm eller delade punkter. Oavsett vilken metod du valde för att skapa tabellen med förutbestämt antal regioner, kan du nu börja ladda in data i tabellen och se att belastningen är fördelad över ditt kluster. Du kan låta automatisk delning ta över när datainmatningen startar och kontinuerligt övervaka det totala antalet regioner för tabellen.
Automatisk delning
Oavsett om fördelning används eller inte, när en region väl når en viss gräns delas den automatiskt upp i två regioner. Om du använder HBase 0.94 (som kommer med HDP-1.2) kan du konfigurera när HBase bestämmer sig för att dela en region och hur den beräknar delningspunkterna via det pluggbara RegionSplitPolicy API. Det finns ett par fördefinierade policyer för regiondelning:ConstantSizeRegionSplitPolicy, IncreasingToUpperBoundRegionSplitPolicy och KeyPrefixRegionSplitPolicy.
Den första är standard- och endast delad policy för HBase-versioner före 0.94. Den delar upp regionerna när den totala datastorleken för en av butikerna (motsvarande en kolumnfamilj) i regionen blir större än den konfigurerade "hbase.hregion.max.filesize", som har ett standardvärde på 10 GB. Den här uppdelningspolicyn är idealisk i fall där du har gjort fördelning och är intresserad av att få ett lägre antal regioner per regionserver.
Standarduppdelningspolicyn för HBase 0.94 och trunk är IncreasingToUpperBoundRegionSplitPolicy, som gör mer aggressiv uppdelning baserat på antalet regioner som finns på samma regionserver. Den delade policyn använder den maximala filstorleken för arkivet baserat på Min (R^2 * "hbase.hregion.memstore.flush.size", "hbase.hregion.max.filesize"), där R är antalet regioner av samma tabell som finns på samma regionserver. Så till exempel, med standard-memstore-spolningsstorleken på 128 MB och standard-maxlagringsstorleken på 10 GB, kommer den första regionen på regionservern att delas strax efter den första spolningen vid 128 MB. När antalet regioner som är värd för regionservern ökar kommer den att använda ökande delade storlekar:512MB, 1152MB, 2GB, 3,2GB, 4,6GB, 6,2GB, etc. Efter att ha nått 9 regioner kommer den delade storleken att gå längre än den konfigurerade "hbase" .hregion.max.filesize”, då kommer 10 GB delad storlek att användas från och med då. För båda dessa algoritmer, oavsett när delning inträffar, är splitpunkten som används den radtangent som motsvarar mittpunkten i "blockindex" för den största arkivfilen i det största arkivet.
KeyPrefixRegionSplitPolicy är ett märkligt tillägg till HBase-arsenalen. Du kan konfigurera längden på prefixet för dina radnycklar för att gruppera dem, och denna uppdelningspolicy säkerställer att regionerna inte delas i mitten av en grupp rader med samma prefix. Om du har angett prefix för dina nycklar kan du använda denna delade policy för att säkerställa att rader med samma radnyckelprefix alltid hamnar i samma region. Denna gruppering av poster kallas ibland för "Entitetsgrupper" eller "Radgrupper". Detta är en nyckelfunktion när du överväger att använda funktionen "lokala transaktioner" (alternativ länk) i din applikationsdesign.
Du kan konfigurera standarduppdelningspolicyn som ska användas genom att ställa in konfigurationen "hbase.regionserver.region.split.policy", eller genom att konfigurera tabellbeskrivningen. För er modiga själar, kan ni också implementera er egen anpassade uppdelningspolicy och koppla in den när bordet skapas, eller genom att modifiera en befintlig tabell:
HTableDescriptor tableDesc = new HTableDescriptor("example-table"); tableDesc.setValue(HTableDescriptor.SPLIT_POLICY, AwesomeSplitPolicy.class.getName()); //add columns etc admin.createTable(tableDesc);
Om du gör föruppdelning och vill hantera regionuppdelningar manuellt, kan du också inaktivera regionuppdelning genom att ställa in "hbase.hregion.max.filesize" till ett högt antal och ställa in uppdelningspolicyn till ConstantSizeRegionSplitPolicy. Du bör dock använda ett skyddsvärde på typ 100 GB, så att regioner inte växer utöver en regionservers kapacitet. Du kan överväga att inaktivera automatisk delning och förlita dig på den initiala uppsättningen av regioner från fördelning till exempel, om du använder enhetliga hash för dina nyckelprefix, och du kan se till att läs/skriv läser in till varje region såväl som dess storlek är enhetlig över regionerna i tabellen.
Tvingad uppdelning
HBase gör det också möjligt för klienter att tvinga upp ett onlinebord från klientsidan. Till exempel kan HBase-skalet användas för att dela upp alla regioner i tabellen, eller dela upp en region, valfritt genom att ange en splitpunkt.
hbase(main):024:0> split 'b07d0034cbe72cb040ae9cf66300a10c', 'b' 0 row(s) in 0.1620 seconds
Med noggrann övervakning av din HBase-lastfördelning, om du ser att vissa regioner får ojämna belastningar, kan du överväga att manuellt dela upp dessa regioner för att jämna ut belastningen och förbättra genomströmningen. En annan anledning till att du kanske vill göra manuella uppdelningar är när du ser att de initiala uppdelningarna för regionen visar sig vara suboptimala, och du har inaktiverat automatisk uppdelning. Det kan till exempel hända om datadistributionen ändras över tiden.
Hur regionuppdelningar implementeras
När skrivförfrågningar hanteras av regionservern ackumuleras de i ett minnessystem som kallas "memstore". När memstore fylls skrivs dess innehåll till disken som ytterligare arkivfiler. Denna händelse kallas en "memstore flush". När butiksfiler ackumuleras kommer RegionServer att "komprimera" dem till kombinerade, större filer. Efter varje spolning eller komprimering avslutas, ställs en begäran om regiondelning i kö om RegionSplitPolicy beslutar att regionen ska delas i två. Eftersom alla datafiler i HBase är oföränderliga kommer inte de nyskapade dotterregionerna att skriva om all data till nya filer när en split sker. Istället kommer de att skapa små sym-länkliknande filer, kallade referensfiler, som pekar på antingen den övre eller nedre delen av den överordnade butiksfilen enligt delningspunkten. Referensfilen kommer att användas precis som en vanlig datafil, men bara hälften av posterna. Regionen kan bara delas om det inte finns fler referenser till de oföränderliga datafilerna i den överordnade regionen. Dessa referensfiler rensas gradvis genom komprimering, så att regionen slutar hänvisa till sina överordnade filer och kan delas upp ytterligare.
Även om uppdelningen av regionen är ett lokalt beslut som tas på RegionServer, måste själva uppdelningsprocessen samordnas med många aktörer. RegionServer meddelar Master före och efter splittringen, uppdaterar .META. tabell så att klienter kan upptäcka de nya dotterregionerna och arrangerar om katalogstrukturen och datafilerna i HDFS. Split är en process med flera uppgifter. För att möjliggöra återställning i händelse av ett fel, för RegionServer en journal i minnet om exekveringstillståndet. Stegen som tagits av RegionServer för att utföra uppdelningen illustreras av figur 1. Varje steg är märkt med sitt stegnummer. Åtgärder från RegionServers eller Master visas i rött, medan åtgärder från klienterna visas i grönt.
1. RegionServer beslutar lokalt att dela upp regionen och förbereder uppdelningen. Som ett första steg skapar den en znode i zookeeper under /hbase/region-in-transition/region-name i DELAT tillstånd.
2. Mästaren lär sig om denna znod, eftersom den har en övervakare för den överordnade region-in-transition-znoden.
3. RegionServer skapar en underkatalog med namnet ".splits" under förälderns regionkatalog i HDFS.
4. RegionServer stänger den överordnade regionen, tvingar fram en tömning av cachen och markerar regionen som offline i dess lokala datastrukturer. Vid denna tidpunkt kommer klientförfrågningar som kommer till den överordnade regionen att kasta NotServingRegionException. Klienten kommer att försöka igen med viss backoff.
5. RegionServer skapar regionkatalogerna under .splits-katalogen, för dotterregionerna A och B, och skapar nödvändiga datastrukturer. Sedan delar den upp butiksfilerna, i den meningen att den skapar två referensfiler per butiksfil i den överordnade regionen. Dessa referensfiler kommer att peka på överordnade regionsfiler.
6. RegionServer skapar den faktiska regionkatalogen i HDFS och flyttar referensfilerna för varje dotter.
7. RegionServer skickar en Put-förfrågan till .META. tabell och ställer in föräldern som offline i .META. tabell och lägger till information om dotterregioner. För närvarande kommer det inte att finnas enskilda poster i .META. för döttrarna. Klienter kommer att se att den överordnade regionen delas om de skannar .META., men kommer inte att veta om döttrarna förrän de visas i .META.. Även om detta sätts till .META. lyckas, kommer föräldern i praktiken att delas. Om RegionServer misslyckas innan denna RPC lyckas, kommer Master och nästa regionserver som öppnar regionen att rensa smutsigt tillstånd om regionuppdelningen. Efter .META. uppdatering, men regionuppdelningen kommer att rullas framåt av Master.
8. RegionServer öppnar döttrar parallellt för att acceptera skrivningar.
9. RegionServer lägger till döttrarna A och B till .META. tillsammans med information om att den är värd för regionerna. Efter denna tidpunkt kan klienter upptäcka de nya regionerna och skicka förfrågningar till den nya regionen. Klienter cachelagrar .META. poster lokalt, men när de gör förfrågningar till regionservern eller .META. kommer deras cacheminne att ogiltigförklaras och de kommer att lära sig om de nya regionerna från .META..
10. RegionServer uppdaterar znode /hbase/region-in-transition/region-name i zookeeper för att ange SPLIT, så att mastern kan lära sig om det. Balanseraren kan fritt tilldela dotterregionerna till andra regionservrar om den så önskar.
11. Efter uppdelningen kommer meta och HDFS fortfarande att innehålla referenser till den överordnade regionen. Dessa referenser kommer att tas bort när komprimering i dotterregioner skriver om datafilerna. Sophämtningsuppgifter i mastern kontrollerar med jämna mellanrum om dotterregionerna fortfarande hänvisar till föräldrafiler. Om inte kommer den överordnade regionen att tas bort.
Region sammanfogas
Till skillnad från regionuppdelning tillhandahåller HBase för närvarande inga användbara verktyg för att slå samman regioner. Även om det finns HMerge- och Merge-verktyg är de inte särskilt lämpade för allmän användning. Det finns för närvarande inget stöd för onlinebord och automatisk sammanslagningsfunktion. Men med problem som OnlineMerge, Master-initierade automatiska regionsammanslagningar, ZK-baserade läs/skrivlås för tabelloperationer, arbetar vi för att stabilisera regionuppdelningar och möjliggöra bättre stöd för regionsammanslagningar. Håll utkik!
Slutsats
Som du kan se gör HBase under huven mycket hushållning för att hantera regionuppdelningar och göra automatiserad skärning genom regioner. Men HBase tillhandahåller också de nödvändiga verktygen kring regionhantering, så att du kan hantera uppdelningsprocessen. Du kan också kontrollera exakt när och hur regionuppdelningar sker via en RegionSplitPolicy.
Antalet regioner i en tabell och hur dessa regioner är uppdelade är avgörande faktorer för att förstå och ställa in din HBase-klusterbelastning. Om du kan uppskatta din nyckelfördelning bör du skapa tabellen med fördelning för att få optimal initial belastningsprestanda. Du kan börja med en lägre multipel av antalet regionservrar som utgångspunkt för det initiala antalet regioner och låta automatiserad uppdelning ta över. Om du inte kan uppskatta de initiala delningspunkterna korrekt är det bättre att bara skapa tabellen med en region och starta en initial laddning med automatiserad uppdelning och använda IncreasingToUpperBoundRegionSplitPolicy. Kom dock ihåg att det totala antalet regioner kommer att stabiliseras över tiden, och den aktuella uppsättningen av regiondelningspunkter kommer att bestämmas från de data som tabellen har mottagit hittills. Du kanske vill övervaka belastningsfördelningen över regionerna hela tiden, och om belastningsfördelningen ändras över tiden, använd manuell delning eller ställ in mer aggressiva regiondelningsstorlekar. Slutligen kan du prova den kommande sammanslagningsfunktionen online och bidra med ditt användningsfall.