Тази публикация за гости от архитекта за производителност на Intel Java Ерик Качмарек (първоначално публикуван тук) изследва как да настроите събирането на отпадъци на Java (GC) за Apache HBase, като се фокусира върху 100% YCSB четения.
Apache HBase е проект с отворен код на Apache, предлагащ съхранение на данни NoSQL. Често използван заедно с HDFS, HBase се използва широко в целия свят. Добре познатите потребители включват Facebook, Twitter, Yahoo и др. От гледна точка на разработчика, HBase е „разпределена, версия, нерелационна база данни, моделирана след Bigtable на Google, разпределена система за съхранение на структурирани данни“. HBase може лесно да се справи с много висока пропускателна способност чрез увеличаване на мащаба (т.е. внедряване на по-голям сървър) или намаляване на мащаба (т.е. разполагане на повече сървъри).
От гледна точка на потребителя, латентността за всяка отделна заявка е много важна. Докато работим с потребители, за да тестваме, настройваме и оптимизираме работните натоварвания на HBase, сега се сблъскваме със значителен брой, които наистина искат 99-ти процентилни латентности на операцията. Това означава двупосочно пътуване, от заявка на клиента до отговора обратно до клиента, всичко това в рамките на 100 милисекунди.
Няколко фактора допринасят за вариациите в латентността. Един от най-опустошителните и непредсказуеми нарушители на латентност са паузите на виртуалната машина на Java (JVM) за „спиране на света“ за събиране на боклука (почистване на паметта).
За да се справим с това, опитахме някои експерименти, използвайки колектора на Oracle jdk7u21 и jdk7u60 G1 (Garbage 1st). Сървърната система, която използвахме, беше базирана на процесори Intel Xeon Ivy-bridge EP с Hyper-threading (40 логически процесора). Имаше 256GB DDR3-1600 RAM и три 400GB SSD диска като локално съхранение. Тази малка настройка съдържаше един главен и един подчинен, конфигурирани на един възел с подходящо мащабирано натоварване. Използвахме HBase версия 0.98.1 и локална файлова система за съхранение на HFile. Тестовата таблица на HBase беше конфигурирана като 400 милиона реда и беше с размер 580 GB. Използвахме стратегията по подразбиране HBase heap:40% за blockcache, 40% за memstore. YCSB беше използван за задвижване на 600 работни нишки, изпращащи заявки към HBase сървъра.
Следните диаграми показват, че jdk7u21 работи със 100% четене за един час с помощта на -XX:+UseG1GC -Xms100g -Xmx100g -XX:MaxGCPauseMillis=100
. Посочихме събирателя за боклук, който да се използва, размера на купчината и желаното време за пауза за „спиране на света“ за събиране на боклука (GC).
Фигура 1:Диви колебания във времето за пауза на GC
В този случай получихме диво люлеещи се паузи на GC. GC паузата имаше диапазон от 7 милисекунди до 5 пълни секунди след първоначален скок, който достигна до 17,5 секунди.
Следната диаграма показва повече подробности по време на стационарно състояние:
Фигура 2:Подробности за GC пауза, по време на стационарно състояние
Фигура 2 ни казва, че GC паузите всъщност се предлагат в три различни групи:(1) между 1 и 1,5 секунди; (2) между 0,007 секунди до 0,5 секунди; (3) скокове между 1,5 секунди и 5 секунди. Това беше много странно, затова тествахме последния пуснат jdk7u60, за да видим дали данните ще бъдат различни:
Проведохме едни и същи тестове за 100% четене, използвайки точно същите параметри на JVM:-XX:+UseG1GC -Xms100g -Xmx100g -XX:MaxGCPauseMillis=100
.
Фигура 3:Значително подобрена обработка на пикове във времето за пауза
Jdk7u60 значително подобри способността на G1 да се справя с пикове във времето за пауза след първоначален скок по време на стадия на успокояване. Jdk7u60 направи 1029 млади и смесени GC по време на едночасово изпълнение. GC се случваше на всеки 3,5 секунди. Jdk7u21 направи 286 GC, като всеки GC се случваше на всеки 12,6 секунди. Jdk7u60 успя да управлява времето за пауза между 0,302 до 1 секунда без големи пикове.
Фигура 4, по-долу, ни дава по-внимателен поглед върху 150 GC паузи по време на стационарно състояние:
Фигура 4:По-добре, но не достатъчно добро
По време на стационарно състояние, jdk7u60 успя да запази средното време на пауза около 369 милисекунди. Беше много по-добре от jdk7u21, но все пак не отговаряше на нашето изискване от 100 милисекунди, дадено от –Xx:MaxGCPauseMillis=100
.
За да определим какво друго бихме могли да направим, за да получим нашето време за пауза от 100 милиона секунди, трябваше да разберем повече за поведението на управлението на паметта на JVM и G1 (Garbage First) колектора за боклук. Следните фигури показват как G1 работи върху колекцията Young Gen.
Фигура 5:Слайд от презентацията на JavaOne за 2012 г. от Чарли Хънт и Моника Бекуит:„Настройка на производителността на G1 Garbage Collector“
Когато JVM стартира, въз основа на параметрите за стартиране на JVM, той иска от операционната система да разпредели голяма непрекъсната част от паметта за хостване на купчината на JVM. Тази част от паметта се разделя от JVM на региони.
Фигура 6:Слайд от презентацията на JavaOne от 2012 г. от Чарли Хънт и Моника Бекуит:„Настройка на производителността на G1 Garbage Collector“
Както показва Фигура 6, всеки обект, който Java програмата разпределя с помощта на Java API, първо идва в пространството Eden в младото поколение отляво. След известно време Eden се напълва и се задейства GC от младо поколение. Обектите, които все още са реферирани (т.е. „живи“), се копират в пространството на Survivor. Когато обектите преживеят няколко GC в младото поколение, те се повишават в пространството от старото поколение.
Когато се случи Young GC, нишките на приложението Java се спират, за да се маркират и копират безопасно живи обекти. Тези спирания са прословутите паузи на GC „стоп на света“, които правят приложенията неотговарящи, докато паузите не приключат.
Фигура 7:Слайд от презентацията на JavaOne от 2012 г. от Чарли Хънт и Моника Бекуит:„Настройка на производителността на G1 Garbage Collector“
Старото поколение също може да стане претъпкано. На определено ниво – контролирано от -XX:InitiatingHeapOccupancyPercent=?
където по подразбиране е 45% от общата купчина - задейства се смесен GC. Той събира както Young gen, така и Old gen. Смесените паузи на GC се контролират от това колко време е необходимо на Young gen за почистване, когато се случи смесен GC.
Така че можем да видим в G1, паузите на GC „спиране на света“ са доминирани от това колко бързо G1 може да маркира и копира живи обекти извън пространството на Eden. Имайки това предвид, ще анализираме как моделът за разпределяне на памет HBase ще ни помогне да настроим G1 GC, за да получим желаната пауза от 100 милисекунди.
В HBase има две структури в паметта, които консумират по-голямата част от неговата купчина:BlockCache
, кеширане на HBase файлови блокове за операции за четене, а Memstore кешира най-новите актуализации.
Фигура 8:В HBase две структури в паметта консумират по-голямата част от неговата купчина.
Изпълнението по подразбиране на BlockCache
на HBase е LruBlockCache
, който просто използва голям масив от байтове за хостване на всички HBase блокове. Когато блоковете са „изгонени“, препратката към Java обекта на този блок се премахва, което позволява на GC да премести паметта.
Нови обекти, формиращи LruBlockCache
и Memstore
отидете първо в райското пространство на младото поколение. Ако живеят достатъчно дълго (т.е. ако не са изгонени от LruBlockCache
или изтрити от Memstore), след което след няколко млади поколения GC, те си проправят път към старото поколение на купчината Java. Когато свободното пространство на старото поколение е по-малко от даден threshOld
(InitiatingHeapOccupancyPercent
за начало), смесеният GC се включва и изчиства някои мъртви обекти в старото поколение, копира живи обекти от младото поколение и преизчислява Eden на младото поколение и HeapOccupancyPercent
на старото поколение . В крайна сметка, когато HeapOccupancyPercent
достига определено ниво, FULL GC
се случва, което прави огромни паузи за „спиране на света“ GC, за да почисти всички мъртви обекти в старото поколение.
След изучаване на GC дневника, създаден от “-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintAdaptiveSizePolicy
“, забелязахме HeapOccupancyPercent
никога не е нараснал достатъчно голям, за да предизвика пълен GC по време на HBase 100% четене. Паузите на GC, които видяхме, бяха доминирани от паузите на Young Gen „стоп света“ и нарастващата обработка на референтни данни с течение на времето.
След завършване на този анализ направихме три групи промени в настройката по подразбиране G1 GC:
- Използвайте
-XX:+ParallelRefProcEnabled
Когато този флаг е включен, GC използва множество нишки за обработка на нарастващите препратки по време на Young и смесена GC. С този флаг за HBase времето за отбелязване на GC се намалява със 75%, а общото време за пауза на GC се намалява с 30%. Set -XX:-ResizePLAB and -XX:ParallelGCThreads=8+(logical processors-8)(5/8)
Буферите за локално разпределение за промоция (PLAB) се използват по време на събирането на Young. Използват се множество нишки. Всяка нишка може да се наложи да разпредели място за обекти, които се копират или в Survivor, или в старо пространство. PLAB са необходими, за да се избегне конкуренцията на нишки за споделени структури от данни, които управляват свободната памет. Всяка GC нишка има един PLAB за пространство за оцеляване и един за старо пространство. Бихме искали да спрем преоразмеряването на PLABs, за да избегнем големите разходи за комуникация между GC нишки, както и вариации по време на всеки GC. Бихме искали да фиксираме броя на GC нишките да бъде размерът, изчислен от 8+(логически процесори-8)( 5/8). Тази формула наскоро беше препоръчана от Oracle. И с двете настройки можем да видим по-плавни паузи на GC по време на изпълнение.- Променете
-XX:G1NewSizePercent
по подразбиране от 5 до 1 за 100GB heap Въз основа на изхода от-XX:+PrintGCDetails and -XX:+PrintAdaptiveSizePolicy
, забелязахме, че причината за неуспеха на G1 да изпълни желаното време за пауза 100GC е времето, необходимо за обработка на Eden. С други думи, G1 отнема средно 369 милисекунди, за да изпразни 5GB Eden по време на нашите тестове. След това променихме размера на Eden с помощта на-XX:G1NewSizePercent=
флаг от 5 надолу на 1. С тази промяна видяхме времето за пауза на GC намалено до 100 милисекунди.
От този експеримент разбрахме, че скоростта на G1 за почистване на Eden е около 1GB на 100 милисекунди или 10GB в секунда за настройката на HBase, която използвахме.
Въз основа на тази скорост можем да зададем -XX:G1NewSizePercent=
така че размерът на Eden може да се запази около 1GB. Например:
- 32GB купчина,
-XX:G1NewSizePercent=3
- 64GB купчина, –
XX:G1NewSizePercent=2
- 100 ГБ и повече купчина,
-XX:G1NewSizePercent=1
- Така че нашите последни опции на командния ред за HRegionserver са:
-XX:+UseG1GC
-Xms100g -Xmx100g
(Размер на купчина, използван в нашите тестове)-XX:MaxGCPauseMillis=100
(Желано време за пауза на GC в тестовете)- –
XX:+ParallelRefProcEnabled
-XX:-ResizePLAB
-XX:ParallelGCThreads= 8+(40-8)(5/8)=28
-XX:G1NewSizePercent=1
Ето графика с времеви паузи на GC за изпълнение на операция на 100% четене за 1 час:
Фигура 9:Най-високите първоначални пикове на утаяване бяха намалени с повече от половината.
В тази диаграма дори най-високите първоначални пикове на утаяване бяха намалени от 3,792 секунди на 1,684 секунди. Най-началните пикове бяха по-малко от 1 секунда. След уреждането GC успя да запази времето за пауза около 100 милисекунди.
Диаграмата по-долу сравнява jdk7u60 работи със и без настройка, по време на стационарно състояние:
Фигура 10:jdk7u60 работи със и без настройка, по време на стационарно състояние.
Простата настройка на GC, която описахме по-горе, дава идеални времена за пауза на GC, около 100 милисекунди, със средно 106 милисекунди и 7 милисекунди стандартно отклонение.
Резюме
HBase е приложение с критично време за реакция, което изисква времето за пауза на GC, за да бъде предвидимо и управляемо. С Oracle jdk7u60, въз основа на информацията за GC, докладвана от -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintAdaptiveSizePolicy
, можем да настроим времето за пауза на GC до желаните от нас 100 милисекунди.
Ерик Качмарек е архитект за производителност на Java в групата за софтуерни решения на Intel. Той ръководи усилията в Intel за активиране и оптимизиране на рамки за големи данни (Hadoop, HBase, Spark, Cassandra) за платформи на Intel.
Софтуерът и работните натоварвания, използвани в тестовете за производителност, може да са оптимизирани за производителност само на микропроцесори на Intel. Тестовете за производителност, като SYSmark и MobileMark, се измерват с помощта на специфични компютърни системи, компоненти, софтуер, операции и функции. Всяка промяна на някой от тези фактори може да доведе до различни резултати. Трябва да се консултирате с друга информация и тестове за ефективност, за да ви помогнем да оцените напълно планираните си покупки, включително ефективността на този продукт, когато се комбинира с други продукти.
Номерата на процесорите на Intel не са мярка за производителност. Номерата на процесорите разграничават функциите във всяко семейство процесори. Не в различни семейства процесори. Отидете на:http://www.intel.com/products/processor_number.
Copyright 2014 Intel Corp. Intel, логото на Intel и Xeon са търговски марки на Intel Corporation в САЩ и/или други страни.