Теория и практика на използване на HBase

Добър ден Казвам се Данил Липовой, нашият екип в Sbertech започна да използва HBase като хранилище за оперативни данни. В хода на изучаването му се натрупа опит, който исках да систематизирам и опиша (надяваме се, че ще бъде полезно за мнозина). Всички експерименти по-долу бяха извършени с HBase версии 1.2.0-cdh5.14.2 и 2.0.0-cdh6.0.0-beta1.

  1. Обща архитектура
  2. Записване на данни в HBASE
  3. Четене на данни от HBASE
  4. Кеширане на данни
  5. Пакетна обработка на данни MultiGet/MultiPut
  6. Стратегия за разделяне на таблици в региони (разделяне)
  7. Устойчивост на грешки, компактификация и локалност на данните
  8. Настройки и производителност
  9. Стрес тестване
  10. Данни

1. Обща архитектура

Теория и практика на използване на HBase
Резервният Master слуша сърдечния ритъм на активния на възела ZooKeeper и в случай на изчезване поема функциите на Master.

2. Запишете данни в HBASE

Първо, нека разгледаме най-простия случай - писане на обект ключ-стойност в таблица с помощта на put(rowkey). Клиентът трябва първо да разбере къде се намира сървърът на главния регион (RRS), който съхранява таблицата hbase:meta. Той получава тази информация от ZooKeeper. След което осъществява достъп до RRS и чете таблицата hbase:meta, от която извлича информация за това кой RegionServer (RS) е отговорен за съхраняването на данни за даден ключ на ред в таблицата, която представлява интерес. За бъдеща употреба мета таблицата се кешира от клиента и следователно следващите извиквания отиват по-бързо, директно към RS.

След това RS, след като получи заявка, първо я записва в WriteAheadLog (WAL), което е необходимо за възстановяване в случай на срив. След това записва данните в MemStore. Това е буфер в паметта, който съдържа сортиран набор от ключове за даден регион. Една таблица може да бъде разделена на региони (дялове), всеки от които съдържа несвързан набор от ключове. Това ви позволява да поставяте региони на различни сървъри, за да постигнете по-висока производителност. Но въпреки очевидността на това твърдение, по-късно ще видим, че това не работи във всички случаи.

След поставяне на запис в MemStore, на клиента се връща отговор, че записът е записан успешно. В действителност обаче той се съхранява само в буфер и попада на диска едва след като изтече определен период от време или когато се запълни с нови данни.

Теория и практика на използване на HBase
При извършване на операцията „Изтриване“ данните не се изтриват физически. Те просто се маркират като изтрити, а самото унищожаване става в момента на извикване на основната компактна функция, която е описана по-подробно в параграф 7.

Файловете във формат HFile се натрупват в HDFS и от време на време се стартира малкият компактен процес, който просто обединява малки файлове в по-големи, без да изтрива нищо. С течение на времето това се превръща в проблем, който се появява само при четене на данни (ще се върнем към това малко по-късно).

В допълнение към процеса на зареждане, описан по-горе, има много по-ефективна процедура, която е може би най-силната страна на тази база данни - BulkLoad. Това се крие във факта, че ние независимо формираме HFiles и ги поставяме на диск, което ни позволява да мащабираме перфектно и да постигнем много прилични скорости. Всъщност ограничението тук не е HBase, а възможностите на хардуера. По-долу са резултатите от зареждане на клъстер, състоящ се от 16 RegionServers и 16 NodeManager YARN (CPU Xeon E5-2680 v4 @ 2.40 GHz * 64 нишки), HBase версия 1.2.0-cdh5.14.2.

Теория и практика на използване на HBase

Тук можете да видите, че чрез увеличаване на броя на дяловете (регионите) в таблицата, както и изпълнителите на Spark, получаваме увеличение на скоростта на изтегляне. Освен това скоростта зависи от обема на записа. Големите блокове дават увеличение в MB/sec, малките блокове в броя на въведените записи за единица време при равни други условия.

Можете също така да започнете да зареждате в две маси едновременно и да получите двойна скорост. По-долу можете да видите, че записването на блокове от 10 KB в две таблици наведнъж става със скорост от около 600 MB/сек във всяка (общо 1275 MB/сек), което съвпада със скоростта на запис в една таблица 623 MB/сек (вж. № 11 по-горе)

Теория и практика на използване на HBase
Но второто изпълнение със записи от 50 KB показва, че скоростта на изтегляне леко нараства, което показва, че се доближава до граничните стойности. В същото време трябва да имате предвид, че на практика няма натоварване, създадено върху самия HBASE, всичко, което се изисква от него, е първо да дадете данни от hbase:meta и след подреждане на HFiles да нулирате данните на BlockCache и да запазите MemStore буфер на диск, ако не е празен.

3. Четене на данни от HBASE

Ако приемем, че клиентът вече има цялата информация от hbase:meta (вижте точка 2), тогава заявката отива директно към RS, където се съхранява необходимият ключ. Първо, търсенето се извършва в MemCache. Независимо дали там има данни или не, търсенето се извършва и в буфера BlockCache и, ако е необходимо, в HFiles. Ако във файла са намерени данни, те се поставят в BlockCache и ще бъдат върнати по-бързо при следваща заявка. Търсенето в HFile е относително бързо благодарение на използването на филтъра Bloom, т.е. след като прочете малко количество данни, той незабавно определя дали този файл съдържа необходимия ключ и ако не, преминава към следващия.

Теория и практика на използване на HBase
След като получи данни от тези три източника, RS генерира отговор. По-специално, той може да прехвърли няколко намерени версии на обект наведнъж, ако клиентът е поискал версия.

4. Кеширане на данни

Буферите MemStore и BlockCache заемат до 80% от разпределената RS памет на heap (останалото е запазено за задачи за RS услуга). Ако типичният режим на използване е такъв, че процесите записват и незабавно четат едни и същи данни, тогава има смисъл да се намали BlockCache и да се увеличи MemStore, т.к. Когато данните за запис не попаднат в кеша за четене, BlockCache ще се използва по-рядко. Буферът BlockCache се състои от две части: LruBlockCache (винаги в купчина) и BucketCache (обикновено извън купчина или на SSD). BucketCache трябва да се използва, когато има много заявки за четене и те не се вписват в LruBlockCache, което води до активна работа на Garbage Collector. В същото време не трябва да очаквате радикално увеличение на производителността от използването на кеша за четене, но ще се върнем към това в параграф 8

Теория и практика на използване на HBase
Има един BlockCache за целия RS и има един MemStore за всяка таблица (по един за всяко семейство колони).

Като описано на теория при запис данните не влизат в кеша и наистина такива параметри CACHE_DATA_ON_WRITE за таблицата и “Cache DATA on Write” за RS са зададени на false. Въпреки това, на практика, ако запишем данни в MemStore, след това ги изчистим на диска (като по този начин ги изчистим), след това изтрием получения файл, след което чрез изпълнение на заявка за получаване ще получим успешно данните. Освен това, дори ако напълно деактивирате BlockCache и попълните таблицата с нови данни, след това нулирате MemStore на диск, изтриете ги и ги поискате от друга сесия, те пак ще бъдат извлечени от някъде. Така че HBase съхранява не само данни, но и мистериозни мистерии.

hbase(main):001:0> create 'ns:magic', 'cf'
Created table ns:magic
Took 1.1533 seconds
hbase(main):002:0> put 'ns:magic', 'key1', 'cf:c', 'try_to_delete_me'
Took 0.2610 seconds
hbase(main):003:0> flush 'ns:magic'
Took 0.6161 seconds
hdfs dfs -mv /data/hbase/data/ns/magic/* /tmp/trash
hbase(main):002:0> get 'ns:magic', 'key1'
 cf:c      timestamp=1534440690218, value=try_to_delete_me

Параметърът „Кеширане на данни при четене“ е зададен на false. Ако имате някакви идеи, добре дошли да ги обсъдите в коментарите.

5. Пакетна обработка на данни MultiGet/MultiPut

Обработката на единични заявки (Get/Put/Delete) е доста скъпа операция, така че ако е възможно, трябва да ги комбинирате в списък или списък, което ви позволява да получите значително увеличение на производителността. Това важи особено за операцията за запис, но при четене има следната клопка. Графиката по-долу показва времето за четене на 50 000 записа от MemStore. Четенето е извършено в една нишка и хоризонталната ос показва броя на ключовете в заявката. Тук можете да видите, че при увеличаване на хиляда ключа в една заявка времето за изпълнение пада, т.е. скоростта се увеличава. Въпреки това, когато режимът MSLAB е активиран по подразбиране, след този праг започва радикален спад в производителността и колкото по-голямо е количеството данни в записа, толкова по-дълго е времето за работа.

Теория и практика на използване на HBase

Тестовете са проведени на виртуална машина, 8 ядра, версия HBase 2.0.0-cdh6.0.0-beta1.

Режимът MSLAB е предназначен да намали фрагментацията на купчината, която възниква поради смесването на ново и старо поколение данни. Като заобиколно решение, когато MSLAB е активиран, данните се поставят в сравнително малки клетки (парчета) и се обработват на парчета. В резултат на това, когато обемът в заявения пакет данни надвиши определения размер, производителността спада рязко. От друга страна, изключването на този режим също не е препоръчително, тъй като ще доведе до спиране поради GC в моменти на интензивна обработка на данни. Добро решение е да се увеличи обемът на клетката в случай на активно писане чрез put едновременно с четенето. Струва си да се отбележи, че проблемът не възниква, ако след запис изпълните командата flush, която нулира MemStore на диск, или ако заредите с помощта на BulkLoad. Таблицата по-долу показва, че заявките от MemStore за по-големи (и същото количество) данни водят до забавяне. Но чрез увеличаване на размера на парчетата ние връщаме времето за обработка към нормалното.

Теория и практика на използване на HBase
В допълнение към увеличаването на размера на парчетата, разделянето на данните по региони помага, т.е. разделяне на маса. Това води до по-малко заявки, идващи към всеки регион и ако се поберат в клетка, отговорът остава добър.

6. Стратегия за разделяне на таблици на региони (разделяне)

Тъй като HBase е хранилище на ключ-стойност и разделянето се извършва по ключ, изключително важно е данните да се разделят равномерно във всички региони. Например, разделянето на такава таблица на три части ще доведе до разделяне на данните в три региона:

Теория и практика на използване на HBase
Случва се това да доведе до рязко забавяне, ако данните, заредени по-късно, изглеждат като например дълги стойности, повечето от които започват с една и съща цифра, например:

1000001
1000002
...
1100003

Тъй като ключовете се съхраняват като масив от байтове, всички те ще започват по един и същи начин и ще принадлежат към един и същ регион #1, съхраняващ този диапазон от ключове. Има няколко стратегии за разделяне:

HexStringSplit – Превръща ключа в шестнадесетичен кодиран низ в диапазона "00000000" => "FFFFFFFF" и допълване отляво с нули.

UniformSplit – Превръща ключа в масив от байтове с шестнадесетично кодиране в диапазона "00" => "FF" и допълване отдясно с нули.

Освен това можете да посочите произволен диапазон или набор от ключове за разделяне и да конфигурирате автоматично разделяне. Въпреки това, един от най-простите и най-ефективни подходи е UniformSplit и използването на хеш конкатенация, например най-значимата двойка байтове от изпълнението на ключа през функцията CRC32(rowkey) и самия rowkey:

хеш + ключ на ред

Тогава всички данни ще бъдат разпределени равномерно между регионите. При четене първите два байта просто се изхвърлят и оригиналният ключ остава. RS също контролира количеството данни и ключове в региона и, ако ограниченията бъдат превишени, автоматично ги разделя на части.

7. Устойчивост на грешки и локалност на данните

Тъй като само един регион е отговорен за всеки набор от ключове, решението на проблемите, свързани с RS сривове или извеждане от експлоатация, е да се съхраняват всички необходими данни в HDFS. Когато RS падне, главният открива това чрез липсата на сърдечен ритъм на възела ZooKeeper. След това присвоява обслужвания регион на друг RS и тъй като HFiles се съхраняват в разпределена файлова система, новият собственик ги чете и продължава да обслужва данните. Въпреки това, тъй като някои от данните може да са в MemStore и да не са имали време да влязат в HFiles, WAL, който също се съхранява в HDFS, се използва за възстановяване на историята на операциите. След като промените бъдат приложени, RS може да отговаря на заявки, но преместването води до факта, че част от данните и процесите, които ги обслужват, се озовават на различни възли, т.е. местността намалява.

Решението на проблема е основното уплътняване - тази процедура премества файловете в онези възли, които отговарят за тях (където се намират техните региони), в резултат на което по време на тази процедура натоварването на мрежата и дисковете рязко се увеличава. В бъдеще обаче достъпът до данни ще се ускори значително. Освен това major_compaction извършва сливане на всички HFiles в един файл в рамките на даден регион и също така изчиства данните в зависимост от настройките на таблицата. Например, можете да укажете броя на версиите на обект, които трябва да бъдат запазени, или продължителността на живота, след която обектът се изтрива физически.

Тази процедура може да има много положителен ефект върху работата на HBase. Картината по-долу показва как производителността е влошена в резултат на активен запис на данни. Тук можете да видите как 40 нишки записват в една таблица и 40 нишки едновременно четат данни. Нишките за писане генерират все повече и повече HFiles, които се четат от други нишки. В резултат на това все повече и повече данни трябва да бъдат премахнати от паметта и в крайна сметка GC започва да работи, което на практика парализира цялата работа. Стартирането на основното уплътняване доведе до разчистване на получените отломки и възстановяване на производителността.

Теория и практика на използване на HBase
Тестът беше извършен на 3 DataNodes и 4 RS (CPU Xeon E5-2680 v4 @ 2.40 GHz * 64 нишки). HBase версия 1.2.0-cdh5.14.2

Заслужава да се отбележи, че основното уплътняване беше стартирано на „жива“ таблица, в която данните бяха активно записвани и четени. Имаше изявление онлайн, че това може да доведе до неправилен отговор при четене на данни. За проверка беше стартиран процес, който генерира нови данни и ги записва в таблица. След което веднага прочетох и проверих дали получената стойност съвпада със записаното. Докато този процес течеше, основното уплътняване беше извършено около 200 пъти и не беше регистриран нито един отказ. Може би проблемът се появява рядко и само по време на високо натоварване, така че е по-безопасно да спрете процесите на запис и четене, както е планирано, и да извършите почистване, за да предотвратите такива усвоявания на GC.

Също така основното уплътняване не засяга състоянието на MemStore; за да го изчистите на диска и да го уплътните, трябва да използвате flush (connection.getAdmin().flush(TableName.valueOf(tblName)))).

8. Настройки и производителност

Както вече споменахме, HBase показва най-голям успех там, където не е необходимо да прави нищо, когато изпълнява BulkLoad. Това обаче се отнася за повечето системи и хора. Този инструмент обаче е по-подходящ за групово съхраняване на данни в големи блокове, докато ако процесът изисква множество конкуриращи се заявки за четене и запис, се използват описаните по-горе команди Get и Put. За определяне на оптималните параметри бяха извършени стартирания с различни комбинации от параметри и настройки на таблицата:

  • 10 нишки бяха стартирани едновременно 3 пъти подред (нека наречем това блок от нишки).
  • Времето на работа на всички нишки в блок беше осреднено и беше крайният резултат от работата на блока.
  • Всички нишки работеха с една и съща таблица.
  • Преди всяко стартиране на нишковия блок се извършва основно уплътняване.
  • Всеки блок извършва само една от следните операции:

-Слагам
— Вземете
— Get+Put

  • Всеки блок извърши 50 000 итерации на своята операция.
  • Размерът на блока на запис е 100 байта, 1000 байта или 10000 XNUMX байта (произволно).
  • Блоковете бяха стартирани с различен брой заявени ключове (или един ключ, или 10).
  • Блоковете бяха изпълнени при различни настройки на таблицата. Променени параметри:

— BlockCache = включен или изключен
— BlockSize = 65 KB или 16 KB
— Прегради = 1, 5 или 30
— MSLAB = активиран или деактивиран

Така блокът изглежда така:

а. Режимът MSLAB беше включен/изключен.
b. Създадена е таблица, за която са зададени следните параметри: BlockCache = true/none, BlockSize = 65/16 Kb, Partition = 1/5/30.
° С. Компресията беше настроена на GZ.
д. 10 нишки бяха стартирани едновременно, извършвайки 1/10 операции put/get/get+put в тази таблица със записи от 100/1000/10000 байта, изпълнявайки 50 000 заявки подред (произволни ключове).
д. Точка d се повтаря три пъти.
f. Времето на работа на всички нишки беше осреднено.

Тествани са всички възможни комбинации. Може да се предвиди, че скоростта ще спадне с увеличаване на размера на записа или че деактивирането на кеширането ще доведе до забавяне. Целта обаче беше да се разбере степента и значимостта на влиянието на всеки параметър, така че събраните данни бяха въведени във входа на линейна регресионна функция, което дава възможност да се оцени значимостта с помощта на t-статистика. По-долу са резултатите от блоковете, извършващи операции Put. Пълен набор от комбинации 2*2*3*2*3 = 144 опции + 72 т.к. някои бяха направени два пъти. Следователно има общо 216 писта:

Теория и практика на използване на HBase
Тестването беше извършено на мини-клъстер, състоящ се от 3 DataNodes и 4 RS (CPU Xeon E5-2680 v4 @ 2.40 GHz * 64 нишки). HBase версия 1.2.0-cdh5.14.2.

Най-високата скорост на вмъкване от 3.7 секунди беше получена при изключен режим MSLAB, на маса с един дял, с активиран BlockCache, BlockSize = 16, записи от 100 байта, 10 броя на пакет.
Най-ниската скорост на вмъкване от 82.8 секунди беше получена с активиран режим MSLAB, на таблица с един дял, с активиран BlockCache, BlockSize = 16, записи от 10000 1 байта, по XNUMX всеки.

Сега нека да разгледаме модела. Виждаме доброто качество на модела, базиран на R2, но е абсолютно ясно, че екстраполацията тук е противопоказана. Действителното поведение на системата при промяна на параметрите няма да бъде линейно; този модел е необходим не за прогнози, а за разбиране на това, което се е случило в дадените параметри. Например, тук виждаме от критерия на ученика, че параметрите BlockSize и BlockCache нямат значение за операцията Put (която обикновено е доста предвидима):

Теория и практика на използване на HBase
Но фактът, че увеличаването на броя на дяловете води до намаляване на производителността, е донякъде неочакван (вече видяхме положителното въздействие от увеличаването на броя на дяловете с BulkLoad), макар и разбираем. Първо, за обработка трябва да генерирате заявки към 30 региона вместо към един, а обемът на данните не е такъв, че това да доведе до печалба. Второ, общото работно време се определя от най-бавния RS и тъй като броят на DataNodes е по-малък от броя на RS, някои региони имат нулева локалност. Е, нека да разгледаме първите пет:

Теория и практика на използване на HBase
Сега нека оценим резултатите от изпълнението на блокове Get:

Теория и практика на използване на HBase
Броят на дяловете е загубил значение, което вероятно се обяснява с факта, че данните се кешират добре и кешът за четене е най-значимият (статистически) параметър. Естествено, увеличаването на броя на съобщенията в заявка също е много полезно за производителността. Най-добри резултати:

Теория и практика на използване на HBase
Е, накрая, нека да разгледаме модела на блока, който първо е изпълнил get и след това put:

Теория и практика на използване на HBase
Тук всички параметри са важни. И резултатите на лидерите:

Теория и практика на използване на HBase

9. Тестване на натоварване

Е, най-накрая ще пуснем повече или по-малко приличен товар, но винаги е по-интересно, когато имаш с какво да сравниш. На уебсайта на DataStax, ключовият разработчик на Cassandra, има данни NT на редица NoSQL хранилища, включително HBase версия 0.98.6-1. Зареждането се извършва от 40 нишки, размер на данните 100 байта, SSD дискове. Резултатът от тестването на операциите Read-Modify-Write показа следните резултати.

Теория и практика на използване на HBase
Доколкото разбирам, четенето е извършено в блокове от 100 записа и за 16 HBase възли тестът DataStax показа производителност от 10 хиляди операции в секунда.

Добре е, че нашият клъстер също има 16 възела, но не е голям „късмет“ всеки да има 64 ядра (нишки), докато в теста на DataStax са само 4. От друга страна, те имат SSD устройства, докато ние имаме HDD или повече, новата версия на HBase и използването на процесора по време на натоварване практически не се увеличи значително (визуално с 5-10 процента). Нека обаче се опитаме да започнем да използваме тази конфигурация. Настройки на таблицата по подразбиране, четенето се извършва в ключовия диапазон от 0 до 50 милиона произволно (т.е. по същество ново всеки път). Таблицата съдържа 50 милиона записа, разделени на 64 дяла. Ключовете се хешират с помощта на crc32. Настройките на таблицата са по подразбиране, MSLAB е активиран. Стартирайки 40 нишки, всяка нишка чете набор от 100 произволни ключа и незабавно записва генерираните 100 байта обратно в тези ключове.

Теория и практика на използване на HBase
Стойка: 16 DataNode и 16 RS (CPU Xeon E5-2680 v4 @ 2.40 GHz * 64 нишки). HBase версия 1.2.0-cdh5.14.2.

Средният резултат е по-близо до 40 хиляди операции в секунда, което е значително по-добро от това в теста DataStax. За експериментални цели обаче можете леко да промените условията. Малко вероятно е цялата работа да се извършва изключително на една маса, а също и само на уникални ключове. Да приемем, че има определен „горещ“ набор от ключове, който генерира основното натоварване. Затова нека се опитаме да създадем натоварване с по-големи записи (10 KB), също в партиди от 100, в 4 различни таблици и ограничаване на обхвата на заявените ключове до 50 хил. Графиката по-долу показва стартирането на 40 нишки, всяка нишка чете набор от 100 ключа и незабавно записва произволни 10 KB върху тези ключове обратно.

Теория и практика на използване на HBase
Стойка: 16 DataNode и 16 RS (CPU Xeon E5-2680 v4 @ 2.40 GHz * 64 нишки). HBase версия 1.2.0-cdh5.14.2.

По време на натоварването основното уплътняване беше стартирано няколко пъти, както е показано по-горе, без тази процедура производителността постепенно ще се влоши, но по време на изпълнението също възниква допълнително натоварване. Тегленията са причинени от различни причини. Понякога нишките приключиха работа и имаше пауза, докато бяха рестартирани, понякога приложения на трети страни създаваха натоварване на клъстера.

Четенето и незабавното писане е един от най-трудните работни сценарии за HBase. Ако правите само малки заявки за поставяне, например 100 байта, комбинирайки ги в пакети от 10-50 хиляди парчета, можете да получите стотици хиляди операции в секунда и ситуацията е подобна със заявките само за четене. Струва си да се отбележи, че резултатите са радикално по-добри от тези, получени от DataStax, най-вече поради заявки в блокове от 50 хиляди.

Теория и практика на използване на HBase
Стойка: 16 DataNode и 16 RS (CPU Xeon E5-2680 v4 @ 2.40 GHz * 64 нишки). HBase версия 1.2.0-cdh5.14.2.

10. Заключения

Тази система е доста гъвкаво конфигурирана, но влиянието на голям брой параметри все още остава неизвестно. Някои от тях бяха тествани, но не бяха включени в резултатния набор от тестове. Например, предварителните експерименти показаха незначително значение на такъв параметър като DATA_BLOCK_ENCODING, който кодира информация, използвайки стойности от съседни клетки, което е разбираемо за произволно генерирани данни. Ако използвате голям брой дублиращи се обекти, печалбата може да бъде значителна. Като цяло можем да кажем, че HBase създава впечатлението за доста сериозна и добре обмислена база данни, която може да бъде доста продуктивна при извършване на операции с големи блокове от данни. Особено ако е възможно да се разделят процесите на четене и писане във времето.

Ако според вас има нещо, което не е достатъчно разкрито, готов съм да ви разкажа по-подробно. Каним ви да споделите своя опит или да обсъдите, ако не сте съгласни с нещо.

Източник: www.habr.com

Добавяне на нов коментар