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.
Det här blogginlägget dök ursprungligen upp här och återges i sin helhet här.
HBase är en distribuerad databas byggd kring kärnkoncepten för en ordnad skrivlogg och ett loggstrukturerat sammanslagningsträd. Som med vilken databas som helst är optimerad I/O en avgörande fråga för HBase. När det är möjligt är prioritet att inte utföra någon I/O alls. Detta innebär att minnesanvändning och cachningsstrukturer är av yttersta vikt. För detta ändamål upprätthåller HBase två cachestrukturer:"minnesarkivet" och "blockcachen". Minneslagring, implementerad som MemStore
, ackumulerar dataredigeringar när de tas emot och buffrar dem i minnet (1). Blockcachen, en implementering av BlockCache
gränssnitt, behåller datablock i minnet efter att de har lästs.
MemStore
är viktigt för att komma åt senaste redigeringar. Utan MemStore
, för att få åtkomst till den datan när den skrevs in i skrivloggen skulle det krävas läsning och avserialisering av poster tillbaka från den filen, åtminstone en O(n)
drift. Istället MemStore
upprätthåller en skipliststruktur som har en O(log n)
åtkomstkostnad och kräver ingen disk I/O. MemStore
innehåller dock bara en liten bit av data som lagras i HBase.
Service läser från BlockCache
är den primära mekanismen genom vilken HBase kan betjäna slumpmässiga läsningar med millisekunders latens. När ett datablock läses från HDFS cachelagras det i BlockCache
. Efterföljande läsningar av angränsande data – data från samma block – lider inte av I/O-straffet att återigen hämta dessa data från disken (2). Det är BlockCache
det kommer att vara det återstående fokus i detta inlägg.
Blockerar till cache
Innan du förstår BlockCache
, hjälper det att förstå exakt vad ett HBase "block" är. I HBase-sammanhang är ett block en enda enhet av I/O. När man skriver ut data till en HF-fil är blocket den minsta enheten av data som skrivs. På samma sätt är ett enda block den minsta mängd data som HBase kan läsa tillbaka från en HF-fil. Var noga med att inte blanda ihop ett HBase-block med ett HDFS-block eller med blocken i det underliggande filsystemet – alla dessa är olika (3).
HBase-block finns i fyra varianter: DATA
, META
, INDEX
och BLOOM
.
DATA
block lagrar användardata. När BLOCKSIZE
är specificerad för en kolumnfamilj, är det en ledtråd för denna typ av block. Kom ihåg, det är bara ett tips. Medan du spolar MemStore
, kommer HBase att göra sitt bästa för att respektera denna riktlinje. Efter varje Cell
skrivs kontrollerar skribenten om beloppet som skrivs är>=målet BLOCKSIZE
. Om så är fallet kommer det att stänga det aktuella blocket och starta nästa (4).
INDEX
och BLOOM
block tjänar samma mål; båda används för att påskynda läsvägen. INDEX
block ger ett index över Cell
s som finns i DATA
block. BLOOM
block innehåller ett blomfilter över samma data. Indexet låter läsaren snabbt veta var en Cell
bör förvaras. Filtret talar om för läsaren när en Cell
är definitivt frånvarande i uppgifterna.
Slutligen META
block lagrar information om själva HF-filen och annan diverse information – metadata, som du kan förvänta dig. En mer omfattande översikt över HFile-formaten och rollerna för olika blocktyper finns i Apache HBase I/O – HFile.
HBase BlockCache och dess implementeringar
Det finns en enda BlockCache
instans i en regionserver, vilket innebär att all data från alla regioner som serveras som värd delar samma cachepool (5). BlockCache
instansieras vid start av regionserver och behålls under hela processens livstid. Traditionellt har HBase bara tillhandahållit en enda BlockCache
implementering: LruBlockCache
. Utgåvan 0.92 introducerade det första alternativet i HBASE-4027: SlabCache
. HBase 0.96 introducerade ett annat alternativ via HBASE-7404, kallat BucketCache
.
Den viktigaste skillnaden mellan den beprövade LruBlockCache
och dessa alternativ är hur de hanterar minnet. Närmare bestämt LruBlockCache
är en datastruktur som helt och hållet finns på JVM-högen, medan de andra två kan dra nytta av minnet utanför JVM-högen. Detta är en viktig skillnad eftersom JVM-högminne hanteras av JVM Garbage Collector, medan de andra inte gör det. I fall av SlabCache
och BucketCache
, är tanken att minska GC-trycket som upplevs av regionserverprocessen genom att minska antalet objekt som finns kvar på högen.
LruBlockCache
Detta är standardimplementeringen. Datablock cachelagras i JVM-högen med denna implementering. Den är uppdelad i tre områden:enkelåtkomst, multiåtkomst och inminne. Områdenas storlek är 25 %, 50 %, 25 % av den totala BlockCache
storlek, respektive (6). Ett block som initialt lästs från HDFS är befolkat i enkelåtkomstområdet. Konsekutiva åtkomster främjar det blocket till fleråtkomstområdet. Området i minnet är reserverat för block som laddas från kolumnfamiljer flaggade som IN_MEMORY
. Oavsett område, vräkas gamla block för att ge plats åt nya block med hjälp av en Minst Nyligen använda algoritm, därav "Lru" i "LruBlockCache".
SlabCache
Den här implementeringen allokerar minnesområden utanför JVM-högen med hjälp av DirectByteBuffer
s. Dessa områden tillhandahåller texten för denna BlockCache
. Det exakta området där ett visst block kommer att placeras baseras på blockets storlek. Som standard tilldelas två områden, som förbrukar 80 % respektive 20 % av den totala konfigurerade off-heap-cachestorleken. Den förra används för att cache-blockera block som är ungefär lika stora som målblocket (7). Den senare innehåller block som är ungefär 2x målblockstorleken. Ett block placeras i det minsta området där det får plats. Om cachen stöter på ett block som är större än vad som får plats i något av områdena, kommer det blocket inte att cachelagras. Som LruBlockCache
, blockavhysning hanteras med hjälp av en LRU-algoritm.
BucketCache
Den här implementeringen kan konfigureras för att fungera i ett av tre olika lägen: heap
, offheap
och file
. Oavsett driftsläge, BucketCache
hanterar minnesområden som kallas "buckets" för att hålla cachade block. Varje hink skapas med en målblockstorlek. heap
implementering skapar dessa hinkar på JVM-högen; offheap
implementering använder DirectByteByffers
att hantera hinkar utanför JVM-högen; file
läge förväntar sig en sökväg till en fil i filsystemet där hinkarna skapas. file
läget är avsett för användning med en backing-butik med låg latens – ett filsystem i minnet, eller kanske en fil som sitter på SSD-lagring (8). Oavsett läge, BucketCache
skapar 14 hinkar i olika storlekar. Den använder frekvensen av blockerad åtkomst för att informera användningen, precis som LruBlockCache
, och har samma engångs-, multiåtkomst- och minnesuppdelning på 25 %, 50 %, 25 %. Liksom standardcachen hanteras blockeviction med hjälp av en LRU-algoritm.
Caching på flera nivåer
Både SlabCache
och BucketCache
är designade för att användas som en del av en strategi för cachelagring på flera nivåer. Alltså en del av den totala BlockCache
storleken tilldelas en LruBlockCache
exempel. Den här instansen fungerar som cache på första nivån, "L1", medan den andra cacheinstansen behandlas som cache på andra nivån, "L2". Men interaktionen mellan LruBlockCache
och SlabCache
skiljer sig från hur LruBlockCache
och BucketCache
interagera.
SlabCache
strategi, kallad DoubleBlockCache
, är att alltid cacheblock i både L1- och L2-cachen. De två cachenivåerna fungerar oberoende av varandra:båda kontrolleras när ett block hämtas och var och en avhyser block utan hänsyn till den andra. BucketCache
strategi, kallad CombinedBlockCache
, använder L1-cachen exklusivt för Bloom- och Index-block. Datablock skickas direkt till L2-cachen. I händelse av avhysning av L1-block, i stället för att kasseras helt, degraderas det blocket till L2-cachen.
Vilket ska jag välja?
Det finns två skäl att överväga att aktivera en av de alternativa BlockCache
implementeringar. Den första är helt enkelt mängden RAM-minne du kan dedikera till regionservern. Gemenskapens visdom erkänner att den övre gränsen för JVM-högen, vad gäller regionservern, är någonstans mellan 14 GB och 31 GB (9). Den exakta gränsen beror vanligtvis på en kombination av hårdvaruprofil, klusterkonfiguration, formen på datatabeller och applikationsåtkomstmönster. Du vet att du har hamnat i farozonen när GC pausar och RegionTooBusyException
s börja översvämma dina loggar.
Den andra tiden att överväga en alternativ cache är när svarslatens verkligen frågor. Genom att hålla högen nere runt 8-12 GB kan CMS-samlaren köras mycket smidigt (10), vilket har mätbar inverkan på den 99:e percentilen av svarstider. Med tanke på denna begränsning är de enda valen att utforska en alternativ sophämtare eller ta en av dessa off-heap-implementeringar för ett svep.
Det här andra alternativet är precis vad jag har gjort. I mitt nästa inlägg kommer jag att dela några ovetenskapliga men informativa experimentresultat där jag jämför svarstiderna för olika BlockCache
implementeringar.
Som alltid, håll utkik och fortsätt med HBase!
1: MemStore
ackumulerar dataredigeringar när de tas emot och buffrar dem i minnet. Detta tjänar två syften:det ökar den totala mängden data som skrivs till disken i en enda operation, och det behåller de senaste ändringarna i minnet för efterföljande åtkomst i form av läsningar med låg latens. Det förra är viktigt eftersom det håller HBase-skrivbitar ungefär synkroniserade med HDFS-blockstorlekar, vilket anpassar HBase-åtkomstmönster med underliggande HDFS-lagring. Det senare är självförklarande och underlättar läsbegäranden till nyligen skrivna data. Det är värt att påpeka att denna struktur inte är inblandad i datahållbarhet. Redigeringar skrivs också till den beställda skrivloggen, HLog
, som involverar en HDFS-tilläggsoperation med ett konfigurerbart intervall, vanligtvis omedelbart.
2:Att läsa om data från det lokala filsystemet är det bästa scenariot. HDFS är trots allt ett distribuerat filsystem, så i värsta fall måste du läsa det blocket över nätverket. HBase gör sitt bästa för att upprätthålla datalokalitet. Dessa två artiklar ger en djupgående titt på vad datalokalitet betyder för HBase och hur den hanteras.
3:Filsystem-, HDFS- och HBase-block är alla olika men relaterade. Det moderna I/O-undersystemet är många lager av abstraktion ovanpå abstraktion. Kärnan i denna abstraktion är konceptet med en enda dataenhet, kallad ett "block". Därför definierar alla dessa tre lagringslager sina egna block, vart och ett av sin egen storlek. I allmänhet innebär en större blockstorlek ökad sekventiell åtkomstgenomströmning. En mindre blockstorlek underlättar snabbare slumpmässig åtkomst.
4:Placera BLOCKSIZE
kontroll efter att data skrivits har två konsekvenser. En enda Cell
är den minsta dataenheten som skrivits till en DATA
blockera. Det betyder också en Cell
kan inte sträcka sig över flera block.
5:Detta skiljer sig från MemStore
, för vilken det finns en separat instans för varje region som är värd för regionservern.
6:Tills helt nyligen var dessa minnespartitioner statiskt definierade; det fanns inget sätt att åsidosätta 25/50/25-delningen. Ett givet segment, multiaccessområdet till exempel, skulle kunna växa sig större än det är 50 % tilldelning så länge de andra områdena var underutnyttjade. Ökat utnyttjande i de andra områdena kommer att vräka ingångar från multiaccessområdet tills balansen 25/50/25 uppnås. Operatören kunde inte ändra dessa standardstorlekar. HBASE-10263, som levereras i HBase 0.98.0, introducerar konfigurationsparametrar för dessa storlekar. Det flexibla beteendet behålls.
7:Affären "ungefärligt" är att tillåta lite rörelseutrymme i blockstorlekar. HBase-blockstorlek är ett grovt mål eller tips, inte en strikt tvingad begränsning. Den exakta storleken på ett visst datablock beror på målblockets storlek och storleken på Cell
värden som finns däri. Tipset om blockstorlek anges som standardblockstorleken på 64 kb.
8:Använda BucketCache
i file
läge med en ihållande stödbutik har en annan fördel:uthållighet. Vid start kommer den att leta efter befintliga data i cachen och verifiera dess giltighet.
9:Som jag förstår det finns det två komponenter som ger den övre gränsen för detta intervall. Först är en gräns för JVM-objekts adresserbarhet. JVM:n kan referera till ett objekt på högen med en 32-bitars relativ adress istället för den fullständiga 64-bitars ursprungliga adressen. Denna optimering är endast möjlig om den totala högstorleken är mindre än 32 GB. Se Komprimerad Hopp för mer information. Det andra är sopsamlarens förmåga att hålla jämna steg med mängden föremål som transporteras i systemet. Vad jag kan säga är de tre källorna till objektavgång MemStore
, BlockCache
och nätverksdrift. Den första mildras av MemSlab
funktion, aktiverad som standard. Den andra påverkas av storleken på din datauppsättning kontra storleken på cachen. Den tredje kan inte hjälpas så länge HBase använder en nätverksstack som förlitar sig på datakopiering.
10:Precis som med 8, förutsätter detta "modern hårdvara". Interaktionerna här är ganska komplicerade och långt utanför omfattningen av ett enda blogginlägg.