Ten wpis na blogu został opublikowany na Hortonworks.com przed fuzją z Cloudera. Niektóre linki, zasoby lub odniesienia mogą nie być już dokładne.
Ten post na blogu pierwotnie pojawił się tutaj i jest tutaj w całości odtworzony.
HBase to rozproszona baza danych zbudowana wokół podstawowych koncepcji uporządkowanego dziennika zapisu i drzewa scalającego o strukturze dziennika. Podobnie jak w przypadku każdej bazy danych, zoptymalizowane operacje we/wy mają kluczowe znaczenie dla HBase. Jeśli to możliwe, priorytetem jest w ogóle nie wykonywać żadnych operacji we/wy. Oznacza to, że wykorzystanie pamięci i struktury pamięci podręcznej mają ogromne znaczenie. W tym celu HBase utrzymuje dwie struktury pamięci podręcznej:„magazyn pamięci” i „pamięć podręczna bloku”. Magazyn pamięci zaimplementowany jako MemStore
, gromadzi edycje danych w miarę ich odbierania, buforując je w pamięci (1). Pamięć podręczna bloków, implementacja BlockCache
interfejs, utrzymuje bloki danych rezydujące w pamięci po ich odczytaniu.
MemStore
jest ważne, aby uzyskać dostęp do ostatnich zmian. Bez MemStore
, dostęp do tych danych w takiej postaci, w jakiej zostały zapisane w dzienniku zapisu, wymagałby odczytania i zdeserializowania wpisów z tego pliku, co najmniej O(n)
operacja. Zamiast tego MemStore
utrzymuje strukturę listy pomijania, która korzysta z O(log n)
koszt dostępu i nie wymaga dysku I/O. MemStore
zawiera jednak tylko niewielką część danych przechowywanych w HBase.
Obsługa odczytów z BlockCache
jest podstawowym mechanizmem, dzięki któremu HBase jest w stanie obsłużyć losowe odczyty z milisekundowym opóźnieniem. Gdy blok danych jest odczytywany z HDFS, jest buforowany w BlockCache
. Kolejne odczyty sąsiednich danych – danych z tego samego bloku – nie podlegają karze we/wy w postaci ponownego pobrania tych danych z dysku (2). Jest to BlockCache
na tym skupimy się w tym poście.
Bloki do pamięci podręcznej
Zanim zrozumiesz BlockCache
, pomaga zrozumieć, czym dokładnie jest „blok” HBase. W kontekście HBase blok jest pojedynczą jednostką we/wy. Podczas zapisywania danych do HFile blok jest najmniejszą jednostką zapisanych danych. Podobnie, pojedynczy blok to najmniejsza ilość danych, jaką HBase może odczytać z powrotem z HFile. Uważaj, aby nie pomylić bloku HBase z blokiem HDFS lub blokami podstawowego systemu plików – wszystkie są różne (3).
Bloki HBase są dostępne w 4 odmianach: DATA
, META
, INDEX
i BLOOM
.
DATA
bloki przechowują dane użytkownika. Gdy BLOCKSIZE
jest określony dla rodziny kolumn, jest to wskazówka dla tego rodzaju bloku. Pamiętaj, to tylko wskazówka. Podczas opróżniania MemStore
, HBase dołoży wszelkich starań, aby przestrzegać tej wytycznej. Po każdej Cell
zostanie zapisany, autor sprawdza, czy zapisana kwota jest>=celem BLOCKSIZE
. Jeśli tak, zamknie bieżący blok i rozpocznie następny (4).
INDEX
i BLOOM
klocki służą temu samemu celowi; oba są używane do przyspieszenia ścieżki odczytu. INDEX
bloki zapewniają indeks nad Cell
s zawarte w DATA
Bloki. BLOOM
bloki zawierają filtr rozkwitu na tych samych danych. Indeks pozwala czytelnikowi szybko dowiedzieć się, gdzie znajduje się Cell
powinny być przechowywane. Filtr informuje czytelnika, kiedy Cell
jest zdecydowanie nieobecny w danych.
Na koniec META
bloki przechowują informacje o samym HFile i inne różne informacje – metadane, jak można się spodziewać. Bardziej kompleksowy przegląd formatów HFile i ról różnych typów bloków znajduje się w Apache HBase I/O – HFile.
HBase BlockCache i jego implementacje
Istnieje jeden BlockCache
wystąpienie na serwerze regionu, co oznacza, że wszystkie dane ze wszystkich regionów obsługiwanych przez ten serwer współdzielą tę samą pulę pamięci podręcznej (5). BlockCache
jest tworzony podczas uruchamiania serwera regionu i jest zachowywany przez cały okres istnienia procesu. Tradycyjnie HBase udostępniał tylko jeden BlockCache
implementacja: LruBlockCache
. W wersji 0.92 wprowadzono pierwszą alternatywę w HBASE-4027:SlabCache
. HBase 0.96 wprowadził inną opcję za pośrednictwem HBASE-7404, o nazwie BucketCache
.
Kluczowa różnica między wypróbowanym i prawdziwym LruBlockCache
a te alternatywy to sposób, w jaki zarządzają pamięcią. W szczególności LruBlockCache
jest strukturą danych, która znajduje się w całości na stercie JVM, podczas gdy pozostałe dwie są w stanie korzystać z pamięci spoza sterty JVM. Jest to ważne rozróżnienie, ponieważ pamięć sterty JVM jest zarządzana przez moduł JVM Garbage Collector, a pozostałymi nie. W przypadku SlabCache
i BucketCache
, pomysł polega na zmniejszeniu presji GC doświadczanej przez proces serwera regionu poprzez zmniejszenie liczby obiektów przechowywanych na stercie.
LruBlockCache
To jest implementacja domyślna. Bloki danych są buforowane na stercie JVM przy użyciu tej implementacji. Jest podzielony na trzy obszary:jednodostępowy, wielodostępowy i w pamięci. Obszary mają rozmiar 25%, 50%, 25% całości BlockCache
rozmiar odpowiednio (6). Blok początkowo odczytany z HDFS jest umieszczany w obszarze jednodostępowym. Kolejne dostępy promują ten blok w obszarze wielodostępu. Obszar w pamięci jest zarezerwowany dla bloków wczytanych z rodzin kolumn oznaczonych jako IN_MEMORY
. Niezależnie od obszaru, stare bloki są eksmitowane, aby zrobić miejsce na nowe bloki przy użyciu algorytmu najrzadziej używanego, stąd „Lru” w „LruBlockCache”.
Pamięć podręczna płyty
Ta implementacja przydziela obszary pamięci poza stertą JVM za pomocą DirectByteBuffer
s. Te obszary stanowią treść tego BlockCache
. Dokładny obszar, w którym zostanie umieszczony konkretny blok, zależy od rozmiaru bloku. Domyślnie przydzielane są dwa obszary, które zużywają odpowiednio 80% i 20% całkowitego skonfigurowanego rozmiaru pamięci podręcznej poza stertą. Pierwsza służy do buforowania bloków, które mają w przybliżeniu rozmiar bloku docelowego (7). Ten ostatni zawiera bloki, które są około 2 razy większe od docelowego rozmiaru bloku. Blok jest umieszczany w najmniejszym obszarze, w którym może się zmieścić. Jeśli pamięć podręczna napotka blok większy niż może zmieścić się w którymkolwiek obszarze, blok ten nie zostanie zapisany w pamięci podręcznej. Jak LruBlockCache
, eksmisja blokowa jest zarządzana za pomocą algorytmu LRU.
Pamięć podręczna
Ta implementacja może być skonfigurowana do działania w jednym z trzech różnych trybów: heap
, offheap
i file
. Niezależnie od trybu pracy, BucketCache
zarządza obszarami pamięci zwanymi „zasobnikami” do przechowywania buforowanych bloków. Każdy kubeł jest tworzony z docelowym rozmiarem bloku. heap
implementacja tworzy te wiadra na stercie JVM; offheap
implementacja korzysta z DirectByteByffers
do zarządzania zasobnikami poza stertą JVM; file
mode oczekuje ścieżki do pliku w systemie plików, w którym tworzone są zasobniki. file
tryb jest przeznaczony do użytku z magazynem kopii zapasowych o niskim opóźnieniu — systemem plików w pamięci lub być może plikiem znajdującym się na dysku SSD (8). Niezależnie od trybu, BucketCache
tworzy 14 wiader o różnych rozmiarach. Wykorzystuje częstotliwość dostępu blokowego do informowania o wykorzystaniu, podobnie jak LruBlockCache
, i ma taki sam podział jednodostępowy, wielodostępowy i w pamięci wynoszący 25%, 50%, 25%. Podobnie jak domyślna pamięć podręczna, eksmisja bloków jest zarządzana za pomocą algorytmu LRU.
Buforowanie wielopoziomowe
Zarówno SlabCache
i BucketCache
są przeznaczone do użycia w ramach wielopoziomowej strategii buforowania. W związku z tym pewna część całkowitego BlockCache
rozmiar jest przydzielony do LruBlockCache
instancja. Ta instancja działa jako pamięć podręczna pierwszego poziomu, „L1,”, podczas gdy druga instancja pamięci podręcznej jest traktowana jako pamięć podręczna drugiego poziomu, „L2”. Jednak interakcja między LruBlockCache
i SlabCache
różni się od tego, w jaki sposób LruBlockCache
i BucketCache
interakcji.
SlabCache
strategia o nazwie DoubleBlockCache
, ma zawsze buforować bloki w obu pamięciach podręcznych L1 i L2. Dwa poziomy pamięci podręcznej działają niezależnie:oba są sprawdzane podczas pobierania bloku i każdy eksmituje bloki bez względu na drugi. BucketCache
strategia o nazwie CombinedBlockCache
, używa pamięci podręcznej L1 wyłącznie dla bloków Bloom i Index. Bloki danych są wysyłane bezpośrednio do pamięci podręcznej L2. W przypadku usunięcia bloku L1, zamiast całkowitego odrzucenia, blok ten jest degradowany do pamięci podręcznej L2.
Który wybrać?
Istnieją dwa powody, dla których warto rozważyć włączenie jednej z alternatyw BlockCache
wdrożenia. Pierwsza to po prostu ilość pamięci RAM, którą możesz przeznaczyć na serwer regionu. Mądrość społeczności uznaje, że górna granica sterty JVM, jeśli chodzi o serwer regionu, wynosi od 14 GB do 31 GB (9). Dokładny limit zwykle zależy od kombinacji profilu sprzętowego, konfiguracji klastra, kształtu tabel danych i wzorców dostępu do aplikacji. Będziesz wiedział, że znalazłeś się w niebezpiecznej strefie, gdy GC zatrzyma się i RegionTooBusyException
zaczną zalewać Twoje logi.
Innym momentem, w którym należy rozważyć alternatywną pamięć podręczną, jest opóźnienie odpowiedzi naprawdę sprawy. Utrzymanie sterty w okolicach 8-12 GB pozwala kolektorowi CMS działać bardzo płynnie (10), co ma wymierny wpływ na 99. percentyl czasu odpowiedzi. Biorąc pod uwagę to ograniczenie, jedynym wyborem jest zbadanie alternatywnego odśmiecacza pamięci lub skorzystanie z jednej z tych implementacji ze sterty na przejażdżkę.
Ta druga opcja jest dokładnie tym, co zrobiłem. W następnym poście przedstawię kilka nienaukowych, ale pouczających wyników eksperymentów, w których porównuję czasy odpowiedzi dla różnych BlockCache
implementacje.
Jak zawsze, bądź na bieżąco i kontynuuj z HBase!
1: MemStore
gromadzi edycje danych w miarę ich odbierania, buforując je w pamięci. Służy to dwóm celom:zwiększa całkowitą ilość danych zapisanych na dysku podczas jednej operacji i zachowuje te ostatnie zmiany w pamięci do późniejszego dostępu w postaci odczytów o niskim opóźnieniu. To pierwsze jest ważne, ponieważ utrzymuje fragmenty zapisu HBase z grubsza zsynchronizowane z rozmiarami bloków HDFS, dopasowując wzorce dostępu HBase do podstawowej pamięci masowej HDFS. To ostatnie nie wymaga wyjaśnień, ułatwiając odczytywanie ostatnio zapisanych danych. Warto zaznaczyć, że struktura ta nie wpływa na trwałość danych. Zmiany są również zapisywane w uporządkowanym dzienniku zapisu, HLog
, który obejmuje operację dołączania HDFS w konfigurowalnych odstępach czasu, zwykle natychmiast.
2:Najlepszym scenariuszem jest ponowne wczytanie danych z lokalnego systemu plików. HDFS to w końcu rozproszony system plików, więc najgorszy przypadek wymaga odczytania tego bloku przez sieć. HBase dokłada wszelkich starań, aby zachować lokalność danych. Te dwa artykuły zapewniają dogłębne spojrzenie na to, co oznacza lokalizacja danych dla HBase i jak jest zarządzana.
3:Bloki systemu plików, HDFS i HBase są różne, ale powiązane. Nowoczesny podsystem we/wy składa się z wielu warstw abstrakcji nałożonych na abstrakcję. Rdzeniem tej abstrakcji jest koncepcja pojedynczej jednostki danych, określanej jako „blok”. W związku z tym wszystkie trzy z tych warstw pamięci definiują swój własny blok, każdy o własnym rozmiarze. Ogólnie rzecz biorąc, większy rozmiar bloku oznacza zwiększoną przepustowość dostępu sekwencyjnego. Mniejszy rozmiar bloku ułatwia szybszy dostęp losowy.
4:Umieszczenie BLOCKSIZE
sprawdzenie po zapisaniu danych ma dwie konsekwencje. Pojedyncza Cell
to najmniejsza jednostka danych zapisywana w DATA
blok. Oznacza to również Cell
nie może obejmować wielu bloków.
5:to coś innego niż MemStore
, dla którego istnieje osobna instancja dla każdego regionu hostowanego przez serwer regionu.
6:Do niedawna te partycje pamięci były zdefiniowane statycznie; nie było możliwości obejścia podziału 25/50/25. Dany segment, na przykład obszar wielodostępowy, może urosnąć do poziomu przekraczającego 50% przydziału, o ile inne obszary będą niewykorzystane. Zwiększone wykorzystanie w pozostałych obszarach spowoduje eksmisję wejść z obszaru wielodostępowego do czasu osiągnięcia salda 25/50/25. Operator nie mógł zmienić tych domyślnych rozmiarów. HBASE-10263, dostarczany w HBase 0.98.0, wprowadza parametry konfiguracyjne dla tych rozmiarów. Elastyczne zachowanie zostaje zachowane.
7:„W przybliżeniu” biznes polega na umożliwieniu trochę swobody w rozmiarach bloków. Rozmiar bloku HBase jest przybliżonym celem lub wskazówką, a nie ściśle wymuszonym ograniczeniem. Dokładny rozmiar konkretnego bloku danych będzie zależał od rozmiaru bloku docelowego i rozmiaru Cell
zawartych w nich wartości. Wskazówka dotycząca rozmiaru bloku jest określona jako domyślny rozmiar bloku 64kb.
8:Korzystanie z BucketCache
w file
tryb z trwałym magazynem zapasowym ma jeszcze jedną zaletę:trwałość. Podczas uruchamiania wyszuka istniejące dane w pamięci podręcznej i zweryfikuje ich poprawność.
9:Jak rozumiem, istnieją dwa elementy, które zalecają górną granicę tego zakresu. Pierwszym z nich jest ograniczenie adresowalności obiektów JVM. JVM może odwoływać się do obiektu na stercie za pomocą 32-bitowego adresu względnego zamiast pełnego 64-bitowego adresu natywnego. Ta optymalizacja jest możliwa tylko wtedy, gdy całkowity rozmiar sterty jest mniejszy niż 32 GB. Zobacz Skompresowane ups, aby uzyskać więcej informacji. Drugi to zdolność garbage collectora do nadążania za ilością obiektów odchodzących w systemie. Z tego, co mogę powiedzieć, trzy źródła rezygnacji obiektów to MemStore
, BlockCache
i operacje sieciowe. Pierwszy jest łagodzony przez MemSlab
funkcja, domyślnie włączona. Na drugi wpływ ma rozmiar zestawu danych w porównaniu z rozmiarem pamięci podręcznej. Trzeciego nie można pomóc, o ile HBase korzysta ze stosu sieciowego, który opiera się na kopiowaniu danych.
10:Podobnie jak w przypadku 8, zakłada się „nowoczesny sprzęt”. Interakcje tutaj są dość złożone i znacznie wykraczają poza zakres pojedynczego wpisu na blogu.