1. Архитектура Spark: управление памятью, жизненный цикл приложения и модель исполнения
Архитектура Spark: управление памятью, жизненный цикл приложения и модель исполнения
Добро пожаловать на курс «Apache Spark: углубленная подготовка к собеседованию для Data Engineer». Мы начинаем с фундамента. На собеседованиях уровня Middle и Senior редко просят просто написать код трансформации. Интервьюеров интересует, понимаете ли вы, что происходит «под капотом», когда вы запускаете этот код. Почему приложение падает с OutOfMemoryError? Почему задача висит на 99%? Ответы на эти вопросы кроются в архитектуре.
В этой статье мы разберем анатомию Spark-приложения, иерархию исполнения и то, как Spark управляет памятью.
Компоненты кластера Spark
Spark использует архитектуру Master-Slave. Это означает, что есть один координатор и множество исполнителей. Давайте разберем ключевые роли.
1. Driver (Драйвер)
Это «мозг» вашего приложения. Драйвер — это процесс, в котором выполняется функцияmain() вашей программы. Он создает объект SparkContext (или SparkSession), который координирует работу всего кластера.Обязанности Драйвера:
* Преобразование пользовательского кода в граф задач (DAG).
* Планирование задач (Scheduling) и отправка их на исполнители.
* Сбор метаданных и результатов (если используется collect()).
2. Executor (Исполнитель)
Это «мускулы» кластера. Исполнители — это процессы, работающие на рабочих узлах (Worker Nodes). Они отвечают за непосредственное выполнение вычислений и хранение данных.Обязанности Экзекьютора: * Выполнение кода задач (Tasks), полученных от Драйвера. * Хранение данных в памяти или на диске (Caching/Persisting). * Возврат статуса выполнения Драйверу.
3. Cluster Manager
Это внешний сервис, который выделяет ресурсы (CPU и RAM) для Драйвера и Экзекьюторов. Spark поддерживает несколько менеджеров: Standalone, YARN, Kubernetes, Mesos.!Архитектура взаимодействия Driver, Cluster Manager и Executors
Модель исполнения: Job, Stage, Task
Одна из самых частых тем на собеседованиях — иерархия выполнения. Когда вы пишете код на PySpark или Scala, Spark не выполняет его построчно. Он ленив (lazy). Вычисления начинаются только тогда, когда вы вызываете Action (действие).
Иерархия выглядит так:
SparkContext).count(), saveAsTextFile(), collect()). Одно приложение может содержать множество Jobs.Wide vs Narrow Dependencies
Понимание того, как Spark делит Job на Stages, критически важно для оптимизации.
* Narrow Dependency (Узкая зависимость): Данные из одной партиции родительского RDD нужны только одной партиции дочернего RDD. Примеры: map, filter, union. Здесь перемещение данных между узлами не требуется. Это происходит в рамках одной стадии (Pipelining).
* Wide Dependency (Широкая зависимость): Данные из одной партиции родительского RDD могут потребоваться множеству партиций дочернего RDD. Примеры: groupByKey, reduceByKey, join. Это вызывает Shuffle — физическое перемещение данных по сети между экзекьюторами. Shuffle всегда начинает новую стадию.
!Различие между узкими и широкими зависимостями и формирование границ стадий
Управление памятью (Memory Management)
С версии Spark 1.6 используется Unified Memory Manager. Это гибкая модель, где память делится на несколько областей. Понимание этих областей поможет вам отвечать на вопросы о тюнинге и ошибках OOM.
Память экзекьютора (JVM Heap) делится следующим образом:
1. Reserved Memory (Зарезервированная память)
Системная память, зарезервированная движком Spark. Обычно это фиксированные 300 МБ. Она не доступна для хранения данных пользователя.2. User Memory (Пользовательская память)
Память для ваших собственных структур данных, созданных внутри UDF, метаданных Spark и прочего, что не управляется напрямую Spark Memory Manager. Рассчитывается как остаток после выделения Spark Memory.3. Spark Memory (Память Spark)
Самая важная область. Она делится на две части: * Execution Memory: Используется для вычислений (Shuffles, Joins, Sorts, Aggregations). Эта память краткосрочная. * Storage Memory: Используется для кэширования данных (cache(), persist()) и Broadcast-переменных.Формула расчета доступной памяти Spark:
Где:
* — итоговый объем памяти, доступный для Spark (Execution + Storage).
* — общий размер кучи (Heap) экзекьютора (параметр spark.executor.memory).
* — зарезервированная память (обычно 300 МБ).
* — доля памяти, отдаваемая под Spark (параметр spark.memory.fraction, по умолчанию 0.6 или 60%).
Динамическое распределение (Dynamic Occupancy)
Ключевая особенность Unified Memory Manager — границы между Execution и Storage не жесткие.
!Структура памяти Unified Memory Manager
Жизненный цикл приложения
Давайте проследим путь приложения от команды в консоли до получения результата.
spark-submit.SparkSession.SparkContext ресурсы освобождаются.Заключение
Понимание архитектуры Spark отличает инженера, который просто «пишет запросы», от инженера, который строит надежные пайплайны. Запомните: * Драйвер управляет, Экзекьюторы работают. * Job состоит из Stages, Stages состоят из Tasks. * Shuffle — это дорого и это граница стадий. * Execution Memory имеет приоритет над Storage Memory.
В следующей статье мы углубимся в RDD, DataFrame и Dataset, чтобы понять, как Spark хранит и обрабатывает данные на уровне API.