При выполнении приложения Spark кластер Spark запускает два потока JVM: Driver и Executor. Первый является основным процессом управления и отвечает за создание контекста Spark, отправку заданий Spark (задания) и преобразование заданий в вычислительные задачи (задачи). ). Каждый процесс-исполнитель координирует планирование задач. Последний отвечает за выполнение определенных вычислительных задач на рабочих узлах и возврат результатов драйверу. Он также обеспечивает функции хранения для RDD, которые необходимо сохранить. Поскольку управление памятью Драйвера относительно простое, в этой статье в основном анализируется управление памятью Исполнителя. Память Spark в контексте относится конкретно к памяти Исполнителя.
Как процесс JVM, управление памятью Executor основано на управлении памятью JVM, более детально распределяя пространство в куче JVM, чтобы полностью использовать память. В то же время Spark вводит технологию off-heap, которая позволяет напрямую освобождать пространство в системной памяти рабочего узла для дальнейшей оптимизации использования памяти.
Память внутри кучи единообразно управляется JVM, а память вне кучи напрямую запрашивается и освобождается из операционной системы.
Размер Память в куче,Параметр spark.executor.memory настраивается при запуске приложения Spark. Параллельные задачи Исполнителя Память разделяют кучу JVM,ЭтиПамять, занимаемая задачами при кэшировании данных RDD и широковещательных данных, планируется как память хранения, а память, занимаемая этими задачами при выполнении Shuffle, планируется как память выполнения (Execution).,Для остальных частей специального планирования не требуется.,Эти экземпляры объектов внутри Spark,Или экземпляр пользовательского объекта в приложении Spark.,Все занимают оставшееся место,в разных режимах управления,Эти три части занимают разное количество места.
Управление кучной памятью в Spark представляет собой управление логическим планированием, поскольку приложение и освобождение памяти, занятой экземплярами объектов, выполняются JVM, и Spark может записывать память только перед применением и освобождением.
Подать заявку на память:
Свободная память:
Объекты JVM могут храниться сериализованным образом. Процесс сериализации заключается в преобразовании объекта в двоичный поток байтов. По сути, его можно понимать как преобразование цепного хранилища прерывистого пространства в непрерывное пространство или блочное хранилище при доступе. , Требуется десериализация. Для сериализованных объектов в Spark, которые представлены в виде потоков байтов, объем занимаемой ими памяти можно рассчитать напрямую, а для несериализованных объектов объем занимаемой ими памяти приблизительно оценивается посредством периодической выборки.
Экземпляры объектов, помеченные как выпущенные Spark, скорее всего, на самом деле не будут переработаны JVM. В результате фактически доступная память меньше, чем доступная память, записанная Spark, что делает невозможным полностью избежать исключений нехватки памяти (OOM).
Чтобы дополнительно оптимизировать использование памяти и повысить эффективность сортировки во время Shuffle, Spark вводит память вне кучи, которая позволяет напрямую открывать пространство в системной памяти рабочего узла и хранить сериализованные двоичные данные.
Память вне кучи означает, что объекты памяти размещаются в памяти за пределами кучи виртуальной машины Java. Эта память напрямую управляется операционной системой (а не виртуальной машиной). Результатом этого является сохранение кучи меньшего размера, чтобы уменьшить влияние сборки мусора на приложение.
С помощью JDK Unsafe API (начиная с версии Spark 2.0 управление памятью хранения вне кучи больше не основано на Tachyon, а такое же, как и исполнительная память вне кучи, реализованная на основе JDK Unsafe API), Spark может напрямую управлять внешней кучей памяти, сокращая ее. Устраняются ненужные накладные расходы на память, а также частое сканирование и перезапуск GC, что повышает производительность обработки. Память вне кучи может быть точно применена и освобождена (причина, по которой память вне кучи может быть точно применена и освобождена, заключается в том, что применение и освобождение памяти больше не проходят через механизм JVM, а напрямую применяются к операционной системе. JVM очищает память. Невозможно точно указать момент времени, поэтому точного освобождения добиться невозможно), а пространство, занимаемое сериализованными данными, можно точно рассчитать, что снижает сложность управления и уменьшает количество ошибок по сравнению с в. -куча памяти.
Память вне кучи не включена по умолчанию. Ее можно включить, настроив параметр spark.memory.offHeap.enabled, а размер пространства вне кучи задается параметром spark.memory.offHeap.size. За исключением того, что другого пространства нет, память вне кучи и память в куче делятся одинаково, и все одновременно выполняемые задачи совместно используют память хранения и память выполнения.
В соответствии с механизмом управления статической памятью, первоначально принятым в Spark, размеры памяти хранения, памяти выполнения и других воспоминаний фиксируются во время работы эталонной программы Spark, но пользователи могут настроить их перед запуском эталонной программы.
Доступная память = systemMaxMemory * spark.storge.memoryFraction * spark.storage.safetyFraction
доступная исполнительная память = systemMaxMemory * spark.shuffle.memoryFraction * spark.shuffle.safetyFraction
Зарезервированная страховая область — это просто логический план. Spark не обрабатывает ее по-другому при конкретном использовании. Управление ею остается на усмотрение JVM, как и с другой памятью.
Память вне кучиРаспространение проще,толькохранилище Памятьи исполнение Память,Определяется параметром spark.memory.storageFraction.,Поскольку место, занимаемое Память вне кучи, можно точно вычислить,Поэтому нет необходимости устанавливать зону страхования.
Механизм унифицированного управления памятью, представленный после Spark 1.6, отличается от управления статической памятью тем, что память хранения и память выполнения используют одно и то же пространство и могут динамически занимать область пространства объекта.
Структура памяти вне кучи унифицированного управления памятью показана на рисунке ниже:
Важная оптимизация заключается в механизме динамической занятости, правила которого следующие:
Благодаря унифицированному механизму управления памятью Spark в определенной степени улучшает использование ресурсов памяти в куче и вне кучи и упрощает разработчикам обслуживание памяти Spark. Однако это не означает, что разработчики могут сидеть сложа руки и расслабляться. поэтому, если пространство для хранения слишком велико или содержит слишком много кэшированных данных, это приведет к частой полной сборке мусора и снижению производительности выполнения задач, поскольку кэшированные данные RDD обычно находятся в памяти в течение длительного времени.
Как наиболее фундаментальная абстракция данных Spark, RDD представляет собой набор записей разделов, доступных только для чтения (раздел). Его можно создать только на основе набора данных в стабильном физическом хранилище или создать новый путем выполнения операций преобразования. другие существующие RDD. Между преобразованным RDD и существующим RDD существуют отношения зависимости, образующие родословную. Благодаря lineage Spark каждый RDD может быть восстановлен.
Когда задача считывает раздел в начале запуска, она сначала определяет, сохранился ли раздел. Если нет, необходимо проверить контрольную точку или пересчитать в соответствии с происхождением. Поэтому, если вам нужно выполнить несколько действий с RDD, вы можете использовать метод persist или кэширования в первом действии, чтобы сохранить или кэшировать RDD в памяти или на диске, тем самым улучшая скорость вычислений в последующих действиях. Фактически, метод кэширования использует уровень хранения по умолчанию MEMORY_ONLY для сохранения RDD в памяти, поэтому кэш представляет собой особый вид сохранения. Конструкция памяти в куче и вне кучи позволяет унифицировать планирование и управление памятью, используемой при кэшировании RDD.
Сохранение RDD обеспечивается модулем Spark Storage, который отделяет RDD от физического хранилища. Модуль Storage отвечает за управление данными, генерируемыми Spark в процессе расчета, и инкапсулирует функции хранения данных в памяти или на диске, локально или удаленно. В конкретной реализации модули Storage на стороне Driver и Executor образуют архитектуру master-slave, то есть BlackManager на стороне Driver является Master, а BlockManager на стороне Executor является ведомым. Модуль Storage логически использует блок в качестве базовой единицы хранения. Каждый раздел RDD однозначно соответствует блоку после обработки (формат BlockId — rdd_RDD-ID_PARTITION-ID). Мастер отвечает за управление и обслуживание информации метаданных блоков для всего приложения Spark. Ведомому устройству необходимо сообщать Мастеру статус обновления блока и получать команды от Мастера, например, на добавление или удаление RDD.
При сохранении RDD Spark предоставляет 7 различных типов MEMORY_ONLY, MEMORY_AND_DISK и т. д., а уровень хранения представляет собой комбинацию следующих 5 переменных:
Семь уровней хранения в Spark следующие:
Путем анализа структуры данных видно, что уровень хранения определяет метод хранения раздела RDD (то есть блока) в трех измерениях:
Прежде чем RDD будет кэширован в памяти хранилища, доступ к данным в разделе обычно осуществляется с использованием структуры данных итератора (Iterator). Каждый сериализованный или несериализованный элемент данных (запись) в разделе может быть получен через итератор. Эти экземпляры объекта Record логически занимают пространство других частей кучи JVM. Пространства разных записей в одном разделе не являются общими. то же самое. Не непрерывно.
После того, как RDD кэшируется в памяти хранения, раздел преобразуется в блок, а запись занимает непрерывное пространство в куче или внекучной памяти. Процесс преобразования Parititon из прерывистого пространства хранения в непрерывное пространство хранения называется Unroll от Spark.
Блок имеет два формата хранения: сериализованный и несериализованный. Конкретный метод зависит от уровня хранения RDD. Модуль хранения каждого исполнителя использует связанную структуру карты (LinkedHashMap) для управления всеми экземплярами объектов Block в памяти кучи и вне кучи. Введение и удаление этой LinkedHashMap записывает приложение и освобождение памяти.
Поскольку нет никакой гарантии, что пространство хранения сможет вместить все данные в итераторе одновременно, текущая вычислительная задача должна обратиться к MemoryManager, чтобы получить достаточно места для развертывания, чтобы временно занять это пространство при развертывании. Если места недостаточно, Unroll выполнит это. потерпеть неудачу, когда будет достаточно места, он может продолжиться. Для сериализованных разделов необходимое пространство развертывания можно рассчитать напрямую и применить однократно. Несериализованные разделы должны применяться последовательно во время процесса обхода записи, то есть каждый раз, когда запись читается, необходимое для нее пространство развертывания выбирается и оценивается для применения. Когда места недостаточно, его можно прервать, чтобы. освободить занятое пространство развертывания.
Если развертывание наконец-то прошло успешно, пространство развертывания, занимаемое текущим разделом, преобразуется в пространство хранения обычного кэша RDD.
Поскольку все вычислительные задачи одного и того же Исполнителя используют ограниченное пространство памяти, когда новый блок необходимо кэшировать, но оставшееся пространство недостаточно и не может быть динамически занято, старый блок в LinkedHashMap должен быть удален (вытеснение) и удален. Если уровень хранения Блока также включает требование хранить его на диске, его необходимо сбросить на диск (DROP), в противном случае Блок будет удален напрямую.
Правила исключения памяти для хранения:
Память выполнения в основном используется для хранения памяти, занимаемой задачами при выполнении Shuffle. Shuffle — это процесс перераспределения данных RDD в соответствии с определенными правилами. Давайте посмотрим на использование исполнительной памяти на двух этапах записи и чтения Shuffle:
Shuffle Write
Shuffle Read
В ExternalSorter и Aggreator Spark будет использовать хеш-таблицу AppendOnlyMap для хранения данных в оперативной памяти кучи. Однако во время процесса перемешивания не все данные могут быть сохранены в хеш-таблице, когда эта хэш-таблица занята. Память будет периодически выбираться. Когда она достигнет определенного уровня и новая память выполнения не может быть запрошена из MemoryManager, Spark сохранит все ее содержимое в файле на диске. Этот процесс называется Spill, файлы, перегруженные на диск, окончательно удаляются. объединены (Объединить).
ссылка: