Здравей, Хабр! Вчера на , от екипа на Rambler&Co, имаше доста въпроси от участниците, свързани с конфигуриране на този инструмент. Решихме да споделим нашия опит по този въпрос. Темата е сложна — затова предлагаме да споделяте опит и в коментарите, може би ние също нещо не разбираме или използваме неправилно.
Няколко думи за нас — как използваме Spark. Имате тримесечна програма , а през целия втори модул участниците работят с този инструмент. Следователно, нашата задача, като организатори, е да подготвим клъстер за ползване в такъв случай.
Характеристиката на нашето използване е, че броят на хората, които работят едновременно със Spark, може да е равен на цялата група. Например, по време на семинар, когато всички едновременно опитват и повтарят след нашия преподавател. А това понякога е около 40 души. Вероятно не много компании по света се сблъскват с такъв сценарий на използване.
По-нататък ще разкажа как и защо подбирахме определени параметри на конфигурацията.
Нека започнем от самото начало. Spark предлага 3 опции за работа на клъстера: standalone, с помощта на Mesos и с помощта на YARN. Ние решихме да изберем третия вариант, защото ни се стори логичен. Вече имаме Hadoop клъстер. Участниците ни вече добре познават неговата архитектура. Нека използваме YARN.
spark.master=yarnСледващото е още по-интересно. Всеки от тези 3 варианта на разгръщане има 2 варианта на внедряване: client и cluster. На базата на и различни източници в интернет, можем да заключим, че client е подходящ за интерактивна работа — например, през Jupyter Notebook, а cluster е по-подходящ за продукционни решения. В нашия случай ни интересуваше интерактивното работа, така че:
spark.deploy-mode=clientВсъщност, от този момент нататък Spark вече ще работи по някакъв начин на YARN, но за нас това не беше достатъчно. Тъй като програмата ни е за големи данни, понякога на участниците не им достигаха ресурсите, които получават в рамките на равномерно разпределяне на ресурсите. И тук открихме нещо интересно — динамично разпределение на ресурсите. Накратко, същността е следната: ако имате трудна задача и клъстерът е свободен (например, сутрин), то с помощта на тази опция Spark може да ви предостави допълнителни ресурси. Необходимостта се изчислява по хитра формула. Няма да навлизаме в подробности — тя работи добре.
spark.dynamicAllocation.enabled=trueЗададохме този параметър и при стартиране Spark се оплака и не стартира. Правилно, защото трябваше да прочетем внимателно. Там е указано, че за да е всичко наред, трябва да включим и допълнителен параметър.
spark.shuffle.service.enabled=trueЗа какво е нужен? Когато нашата задача вече не изисква толкова много ресурси, Spark трябва да ги върне в общия пул. Най-трудоемкият етап почти във всяка задача MapReduce е етапът Shuffle. Този параметър позволява запазването на данните, които се генерират в този етап и съответно освобождаване на executors. А executor — това е процесът, който изчислява всичко на работника. Той разполага с определено количество процесорни ядра и определено количество памет.
Добавихме този параметър. Всичко изглежда заработи. Забеляза се, че участниците наистина получават повече ресурси, когато им е необходимо. Но възникна друг проблем — в даден момент други участници се събудиха и също искаха да използват Spark, а там всичко беше заето и те бяха недоволни. Могат да се разберат. Започнахме да гледаме в документацията. Оказа се, че има още няколко параметъра, с които може да се влияе на процеса. Например, ако executor е в режим на изчакване — след колко време можем да отнемем ресурсите му?
spark.dynamicAllocation.executorIdleTimeout=120sВ нашия случай — ако вашите executors не правят нищо в продължение на две минути, моля, върнете ги в общия пул. Но и този параметър не винаги беше достатъчен. Беше ясно, че човекът отдавна не е активен, а ресурсите не се освобождават. Оказа се, че има и специален параметър — след колко време да се отнемат executors с кеширани данни. По подразбиране този параметър беше зададен на — безкрайност! Ние го коригирахме.
spark.dynamicAllocation.cachedExecutorIdleTimeout=600sТоест, ако в продължение на 5 минути вашите executors не извършват никаква дейност, върнете ги в общия пул. При такъв режим скоростта на освобождаване и разпределение на ресурсите за голям брой потребители стана приемлива. Неприязънта намаля. Но решихме да отидем още по-далеч и да ограничим максималния брой executors на едно приложение — по същество на един участник в програмата.
spark.dynamicAllocation.maxExecutors=19Сега, разбира се, се появиха недоволни от друга страна — “кластера стои без работа, а аз имам само 19 executors”, но какво да се прави — нужен е определен баланс. Невъзможно е да направим всички щастливи.
И още една малка история, свързана със спецификата на нашия случай. Един път на практическо занимание закъсняха няколко човека и при тях Spark по някаква причина не стартира. Погледнахме на количеството свободни ресурси — изглежда, че има. Spark трябваше да стартира. За щастие, в този момент документацията вече беше запомнена и си спомнихме, че при стартиране Spark търси порт, на който да започне. Ако първият порт от диапазона е зает, той преминава на следващия по ред. Ако е свободен, той го заема. Има и параметър, който указва максималния брой опити за това. По подразбиране — 16. Това е по-малко от хората в нашата група на занятието. Следователно, след 16 опити Spark се отказваше и казваше, че не може да стартира. Ние коригирахме този параметър.
spark.port.maxRetries=50Сега ще разкажа за някои настройки, вече не толкова свързани със спецификата на нашия случай.
За по-бърз старт на Spark, се препоръчва папката jars, която се намира в домашната директория на SPARK_HOME, да бъде архивирана и поставена на HDFS. Тогава той няма да губи време за зареждане на тези .jar файлове през работниците.
spark.yarn.archive=hdfs:///tmp/spark-archive.zipСъщо така, за по-бърза работа, се препоръчва да се използва kryo като сериализатор. Той е по-оптимизиран от този по подразбиране.
spark.serializer=org.apache.spark.serializer.KryoSerializerИма и стара проблема със Spark, че той често се срива заради паметта. Често това се случва в момента, когато работниците са довършили изчисленията и изпращат резултата на драйвера. Ние увеличихме този параметър. По подразбиране е 1Гб, ние го направихме 3.
spark.driver.maxResultSize=3072И накрая, за десерт. Как да актуализираме Spark до версия 2.1 на дистрибуцията на HortonWorks — HDP 2.5.3.0. Тази версия на HDP съдържа предварително инсталирана версия 2.0, но ние решихме, че Spark се развива активно и всяка нова версия поправя някои бъгове и предоставя допълнителни възможности, включително и за python API, затова решихме, че е необходимо да направим актуализация.
Свалихме версия от официалния сайт за Hadoop 2.7. Разархивирахме, прехвърлихме в папката с HDP. Сложихме симлинкове както трябва. Започваме — не стартира. Показва много неразбираема грешка.
java.lang.NoClassDefFoundError: com/sun/jersey/api/client/config/ClientConfigСлед като потърсихме в интернет, установихме, че Spark е решил да не чака Hadoop и е започнал да използва нова версия на jersey. Те сами спорят по този въпрос в JIRA. Решението беше — да свалим . Да го сложим в папката jars в SPARK_HOME, отново да направим zip и да качим на HDFS.
Тази грешка обиколихме, но се появи нова, доста неясна.
org.apache.spark.SparkException: Yarn приложението вече е приключило! То може да е било убито или не е успяло да стартира master на приложениетоВ същото време опитваме да стартираме версия 2.0 — всичко е наред. Опитай да се досетиш каква е причината. Проверихме логовете на това приложение и видяхме нещо такова:
/usr/hdp/${hdp.version}/hadoop/lib/hadoop-lzo-0.6.0.${hdp.version}.jarВ крайна сметка, по някакви причини hdp.version не беше разрешена. След като потърсихме решение, установихме, че трябва в Ambari да отидем в настройките на YARN и да добавим параметър в custom yarn-site:
hdp.version=2.5.3.0-37Тази магия помогна и Spark заработи. Изпробвахме няколко от нашите jupyter-ноутбуци. Всичко работи. Готови сме за първото занятие по Spark в събота (вече утре)!
UPD. По време на занятието се появи още една проблема. В някакъв момент YARN спря да предоставя контейнери за Spark. Нужно беше да се поправи параметърът в YARN, който по подразбиране беше 0.2:
yarn.scheduler.capacity.maximum-am-resource-percent=0.8 Тоест само 20% от ресурсите участваха в разпределението на ресурсите. Променяйки параметрите, презаредихме YARN. Проблемът беше решен и другите участници също успяха да стартират Spark контекста.
Източник: habr.com
