До сих пор мы рассматривали микросервисы, которые либо полагались на базовые клиенты брокера событий (шаблон BPC), либо использовали «легковесные» библиотеки, встроенные прямо в приложение. Но что делать, если бизнес-логика требует обработки терабайтов данных в секунду, сложнейших оконных агрегаций и глобальных JOIN-ов между десятками потоков?
На сцену выходят «тяжеловесные» фреймворки потоковой обработки — Apache Spark, Apache Flink, Apache Storm и Apache Beam. Они не просто обрабатывают события; они управляют целыми распределёнными кластерами, обеспечивая беспрецедентную масштабируемость и отказоустойчивость. Но за эту мощь приходится платить — архитектурной сложностью.
Это одиннадцатая глава цикла. Она опирается на главу V (watermarks, windowing), главу VI (event time), главу VII (state stores, RocksDB, checkpoint), главу IX (FaaS как противоположный полюс) и главу X (BPC как минимализм). Здесь мы замыкаем спектр: от простейшего poll-process-produce до распределённых титанов Big Data.
Чтобы понять философию «тяжеловесных» фреймворков, нужно взглянуть на их историю. Они родились из эпохи пакетной обработки (Apache Hadoop и MapReduce), когда объёмы данных выросли настолько, что их невозможно было обработать на одном сервере. Инженерам понадобились инструменты для массового параллелизма, распределённого хранения и автоматического восстановления после сбоев тысяч узлов.
Со временем запросы сместились от ночных пакетных заданий к аналитике в режиме, близком к реальному времени. Так появились Spark Streaming, Flink и Storm. Они унаследовали кластерную архитектуру предков, но адаптировали её для непрерывного потребления событий из брокеров.
Kafka Streams, BPC и FaaS живут внутри вашего приложения или в managed runtime. Flink и Spark — это отдельный кластер, который запускает ваше задание (job) как первый среди равных. Это архитектурное решение, а не деталь реализации.
Главная отличительная черта «тяжеловесного» фреймворка — наличие независимого выделенного кластера. Архитектура разделена на две основные роли:
Мозг кластера. Принимает ваше приложение (job), разбивает его на задачи (tasks), распределяет их по рабочим узлам и следит за выполнением. Если рабочий узел падает, Master перезапускает задачу на другом.
Рабочие лошадки. Потребляют вычислительные мощности, память и диски для обработки событий, выполнения агрегаций и shuffle данных между задачами.
Для координации и обеспечения высокой доступности (HA) исторически использовался Apache Zookeeper. Он решает, какой узел является главным, если текущий Master выходит из строя. Современные фреймворки учатся обходиться без него: Flink использует Kubernetes HA, Spark — Zookeeper или встроенный recovery mode.
Многие ставят Spark и Flink в один ряд как «движки потоковой обработки». Но у них принципиально разные модели, и это критично для выбора.
| Критерий | Flink | Spark Structured Streaming |
|---|---|---|
| Модель обработки | True streaming — событие за событием | Micro-batch — пакетами по N мс |
| Latency | Миллисекунды | 100 мс – секунды |
| Event time | Полная поддержка с watermarks | Через micro-batch watermarks |
| State backend | RocksDB (embedded) или Flink 2.0 ForSt (S3) | In-memory + checkpoint |
| Exactly-once | Checkpoint + two-phase commit | Micro-batch transactions |
| SQL | Flink SQL (декларативный streaming) | Spark SQL (micro-batch) |
| Лучше для | Real-time, low-latency, event-time | Analytics, ML, batch + streaming unified |
Если latency критична (финансовые транзакции, fraud detection) — Flink. Если нужна unified batch + streaming аналитика и ML — Spark. Если нужен максимум гибкости для разных runners (Flink, Spark, Dataflow, Samza) — Apache Beam.
То, как вы развёртываете «тяжеловесный» фреймворк, определяет, насколько хорошо он впишется в вашу микросервисную архитектуру. Существует три основных пути:
AWS EMR, Google Dataflow, Databricks, Confluent Cloud. Вы платите за compute, провайдер управляет кластером. Минус — vendor lock-in и ограниченная гибкость настройки.
Классический Hadoop-подход. Вы сами настраиваете VM, Zookeeper и Master. Подходит для гигантских нагрузок, но ужасен для гибкости микросервисов.
Современный золотой стандарт. Flink или Spark использует Kubernetes как планировщик ресурсов. Каждое задание запускается в изолированном session cluster. Развёртывание — как обычного контейнера, через CI/CD.
Flink поддерживает два режима: session mode — один кластер для многих jobs (быстрее старт, но изоляция слабее), application mode — кластер под каждый job (полная изоляция, но медленнее старт).
Flink и Spark на Kubernetes перестают быть «чужеродным телом» в микросервисной архитектуре. Они становятся managed workloads с CI/CD, RBAC, autoscaling — как любой другой сервис в кластере.
В распределённой системе сбои — норма. «Тяжеловесные» фреймворки решают это через механизм контрольных точек (checkpoints): моментальные снимки состояния приложения, периодически сохраняемые в надёжное хранилище (HDFS, S3).
При сбое узла Master восстанавливает задачу на новом рабочем узле, загружая последнюю контрольную точку. Фреймворк синхронизирует два типа состояния:
Хранит смещения (offsets) брокера событий. Гарантирует, что при восстановлении микросервис продолжит чтение ровно с того места, где остановился.
Хранит бизнес-логику (агрегаты, окна, JOIN-буферы). Гарантирует, что вычисления не будут дублированы или потеряны.
Если operator state и keyed state не синхронизированы в одной контрольной точке, вы получите либо потерю данных, либо их дублирование. Фреймворки берут эту рутину на себя, но разработчик должен понимать механизм.
Связь с главой VII: в Kafka Streams аналогом checkpoint является changelog topic. Во Flink — checkpoint в S3/HDFS. Это два разных подхода к одной задаче: сохранение state для восстановления после сбоя.
Flink предоставляет exactly-once через checkpointing + two-phase commit для external sinks. Это принципиально отличается от Kafka Streams exactly_once_v2:
| Аспект | Flink EOS | Kafka Streams EOS v2 |
|---|---|---|
| Механизм | Snapshot barrier + 2PC | Kafka transactions |
| Требует поддержки sink | Да (Kafka, JDBC, File с 2PC) | Только Kafka to Kafka |
| Overhead | Checkpoint frequency + 2PC на каждый sink | Transaction на batch |
| Где применяется | Flink jobs с внешними sinks | Kafka Streams applications |
Flink EOS работает с transactional sinks. Если sink не поддерживает transactions (например, HTTP API), EOS не гарантируется — нужна идемпотентность на стороне sink, как в BPC (глава X).
Watermarks — ключевое отличие тяжёлых фреймворков от BPC и FaaS. Это механизм определения «прошлое прошло»:
WatermarkStrategy.<Event>forBoundedOutOfOrderness(
Duration.ofSeconds(30))
.withTimestampAssigner((event, ts) ->
event.getOccurredAt().toEpochMilli())
.withIdleness(Duration.ofMinutes(5));
Flink предоставляет полную поддержку того, что в главе VI обсуждалось для Kafka Streams: watermarks, allowed lateness, idle sources, watermark alignment. Разница — в уровне абстракции и мощности: Flink работает с сотнями источников одновременно.
Самая большая боль при масштабировании stateful-приложений — операция shuffle. Когда вы группируете данные по ключу (groupByKey), события с одинаковыми ключами должны физически попасть на один рабочий узел.
Если вы добавляете или удаляете рабочие узлы «на лету», данные, которые уже были перемешаны старыми узлами, могут стать недоступны для новых. Исторически это решалось либо остановкой приложения, либо использованием External Shuffle Service (ESS) — выделенного хранилища, которое буферизует данные между задачами.
Apache Spark 3.0+ учится динамически масштабироваться без ESS, удерживая узлы-источники активными, пока нижестоящие задачи не заберут данные. Flink решает эту задачу иначе: state сохраняется в checkpoint, а после rescale восстанавливается на новых узлах. Это сближает тяжёлые фреймворки с парадигмой эластичных микросервисов.
Flink 2.0 (март 2025) представил disaggregated state backend (ForSt): состояние живёт в remote object storage (S3), а не на локальном диске TaskManager. State pages in/out по мере необходимости.
Что это даёт:
Trade-offs:
Связь с главой VII: Flink 2.0 ForSt — это эволюция того же подхода, что обсуждался для Kafka Streams (внутренний vs внешний state). Разница — в уровне абстракции.
Современное развитие тяжёлых фреймворков — SQL на потоках. Flink SQL и Spark SQL позволяют писать streaming jobs декларативно, без Java/Scala кода.
-- Flink SQL: session windows для рекламы
SELECT
userId,
COUNT(*) AS view_count,
SESSION_START(eventTime, INTERVAL '30' MINUTE) AS session_start,
SESSION_END(eventTime, INTERVAL '30' MINUTE) AS session_end
FROM ad_views
GROUP BY
userId,
SESSION(eventTime, INTERVAL '30' MINUTE);
Это радикально снижает порог входа: аналитики и data engineers могут писать streaming jobs без знания Java API. SQL становится языком для ETL и аналитики в реальном времени.
Как тестировать Flink job без production кластера?
Локальный Flink в unit-тестах. Запускает job в том же JVM с минимальными ресурсами. Быстрая проверка топологии.
Тестирование операторов в изоляции. Подаёт события, проверяет выход. Без полноценного кластера.
Утилиты для интеграционного тестирования Flink jobs. Проверка watermark propagation, state, sinks.
Аналог для Spark: локальный SparkSession, тестовые данные, проверка output.
| Метрика | Что показывает | Порог алерта |
|---|---|---|
checkpoint_duration |
Время на checkpoint | Растёт — диск/сеть bottleneck |
checkpoint_failures |
Провалы checkpoint | > 0 в течение 10 мин |
backpressure |
Давление на upstream | High > 5 мин |
watermark_lag |
Отставание watermark от processing time | Растёт — источник отстаёт |
task_skew |
Неравномерность нагрузки между subtasks | Ratio > 3x |
GC_time |
Время на garbage collection | > 10% от CPU time |
| Сценарий | Managed | Self-hosted |
|---|---|---|
| Старт | Быстро (минуты) | Медленно (недели) |
| Операционные расходы | Низкие | Высокие (SRE) |
| Стоимость при простое | Есть (cluster runs) | Есть (cluster runs) |
| Гибкость настройки | Ограничена | Полная |
| Vendor lock-in | Есть | Нет |
| Стоимость при scale | Растёт линейно | Растёт с дисконтом |
Managed — для команд без Big Data экспертизы и при переменной нагрузке. Self-hosted — для крупных организаций с экспертизой и предсказуемой нагрузкой. На длинном горизонте self-hosted дешевле, но требует SRE-команды.
| Критерий | Flink | Spark Structured Streaming | Beam | Storm |
|---|---|---|---|---|
| Модель | True streaming | Micro-batch | Unified (runner-dependent) | True streaming |
| Latency | ms | 100ms+ | depends | ms |
| State backend | RocksDB + ForSt (S3) | In-memory + checkpoint | depends | Custom |
| Exactly-once | Checkpoint + 2PC | Micro-batch transactions | depends | Trident |
| SQL | Flink SQL | Spark SQL | Beam SQL | Нет |
| K8s native | Да (operator) | Да (operator) | Да | Ограниченно |
| Лучше для | Real-time, event time | Analytics, ML, unified | Multi-runner | Legacy real-time |
Представьте, что нужно объединить потоки «Просмотров рекламы» и «Кликов по рекламе» для расчёта конверсии в сеансовом окне: если пользователь не кликает и не смотрит рекламу 30 минут, сеанс закрывается.
DataStream<Event> clickStream = ...;
DataStream<Event> viewStream = ...;
clickStream
.union(viewStream) // 1. Объединяем потоки
.keyBy(e -> e.getUserId()) // 2. Группируем по пользователю (shuffle)
.window(EventTimeSessionWindows
.withGap(Time.minutes(30))) // 3. Сеансовое окно
.aggregate(new SessionAggregator()) // 4. Агрегация
.addSink(producerToKafka); // 5. Результат в Kafka
Фреймворк берёт на себя управление state окон, watermarks и обработкой запоздалых событий. Разработчику остаётся только описать бизнес-логику агрегатора.
Session windows уже обсуждались в главе VI для Kafka Streams. Здесь Flink реализует тот же паттерн, но с полной поддержкой watermarks, idle sources и allowed lateness. Это тот уровень, на котором BPC уже не справляется.
| Глава | Фокус | Связь с текущей главой |
|---|---|---|
| V. Механика потоковой обработки | Windowing, state stores, rebalance | Flink и Spark реализуют то же на кластерном уровне |
| VI. Время в потоках | Event time, watermarks, late events | Flink предоставляет полную поддержку watermarks |
| VII. Память микросервисов | RocksDB, changelog, TTL | Flink state backend — RocksDB или ForSt (S3) |
| IX. Бессерверная архитектура | FaaS как противоположный полюс | Тяжёлые фреймворки не подходят для FaaS-парадигмы |
| X. Базовый шаблон Производитель-Потребитель | BPC как минимализм | Flink/Spark — верхний предел сложности EDA |
| XI. Тяжеловесные фреймворки | Flink, Spark, Beam, Storm | Замыкание спектра: от простого к сложному |
Главный тренд сегодня — интеграция тяжёлых фреймворков с Kubernetes. Когда Flink или Spark начинают воспринимать CMS как планировщик ресурсов, они перестают быть «чужеродным телом» в архитектуре и становятся полноправными, хотя и очень прожорливыми, микросервисами. Спектр EDA замкнут: от простейшего BPC до распределённых титанов Big Data — каждый инструмент занимает свою нишу.
Глава XI цикла «Событийно-управляемая архитектура». Источники: Apache Flink, Apache Spark, Apache Beam, Apache Storm, Confluent, Databricks, практические кейсы production-систем.
So far, we have looked at microservices that either relied on basic event broker clients (BPC pattern) or used "lightweight" libraries embedded directly in the application. But what if your business logic requires processing terabytes per second, complex window aggregations, and global JOINs between dozens of streams?
Enter heavyweight stream processing frameworks — Apache Spark, Apache Flink, Apache Storm, and Apache Beam. They don't just process events; they manage entire distributed clusters, providing unprecedented scalability and fault tolerance. But this power comes at a cost — architectural complexity.
This is the eleventh chapter of the series. It builds on Chapter V (watermarks, windowing), Chapter VI (event time), Chapter VII (state stores, RocksDB, checkpoint), Chapter IX (FaaS as the opposite pole), and Chapter X (BPC as minimalism). Here we close the spectrum: from the simplest poll-process-produce to the distributed titans of Big Data.
To understand the philosophy of heavyweight frameworks, we need to look at their history. They were born in the era of batch processing (Apache Hadoop and MapReduce), when data volumes grew so large they couldn't be processed on a single server. Engineers needed tools for massive parallelism, distributed storage, and automatic recovery after failures of thousands of nodes.
Over time, demands shifted from nightly batch jobs to near-real-time analytics. So Spark Streaming, Flink, and Storm appeared. They inherited the cluster architecture of their ancestors but adapted it for continuous event consumption from brokers.
Kafka Streams, BPC, and FaaS live inside your application or managed runtime. Flink and Spark are a separate cluster that runs your job as a first-class citizen. This is an architectural decision, not an implementation detail.
The main distinguishing feature of heavyweight frameworks is the presence of an independent dedicated cluster. Architecture is divided into two main roles:
The brain of the cluster. Accepts your application (job), splits it into tasks, distributes them across worker nodes, and monitors execution. If a worker node fails, the Master restarts the task on another.
The workhorses. They consume compute, memory, and disk to process events, perform aggregations, and shuffle data between tasks.
For coordination and high availability (HA), Apache Zookeeper was historically used. It decided which node is the leader if the current Master failed. Modern frameworks are learning to live without it: Flink uses Kubernetes HA, Spark uses Zookeeper or built-in recovery mode.
Many put Spark and Flink in the same row as "stream processing engines." But they have fundamentally different models, and this is critical for choosing.
| Criterion | Flink | Spark Structured Streaming |
|---|---|---|
| Processing model | True streaming — event by event | Micro-batch — batches of N ms |
| Latency | Milliseconds | 100 ms – seconds |
| Event time | Full support with watermarks | Via micro-batch watermarks |
| State backend | RocksDB (embedded) or Flink 2.0 ForSt (S3) | In-memory + checkpoint |
| Exactly-once | Checkpoint + two-phase commit | Micro-batch transactions |
| SQL | Flink SQL (declarative streaming) | Spark SQL (micro-batch) |
| Best for | Real-time, low-latency, event-time | Analytics, ML, batch + streaming unified |
If latency is critical (financial transactions, fraud detection) — Flink. If you need unified batch + streaming analytics and ML — Spark. If you need maximum flexibility across runners (Flink, Spark, Dataflow, Samza) — Apache Beam.
AWS EMR, Google Dataflow, Databricks, Confluent Cloud. You pay for compute, provider manages the cluster. Downside — vendor lock-in and limited tuning flexibility.
Classic Hadoop approach. You configure VM, Zookeeper, and Master yourself. Suitable for giant workloads, but terrible for microservice flexibility.
Modern gold standard. Flink or Spark uses Kubernetes as a resource scheduler. Each job runs in an isolated session cluster. Deployment is like any container via CI/CD.
Flink supports two modes: session mode — one cluster for many jobs (faster start, weaker isolation), application mode — cluster per job (full isolation, slower start).
Flink and Spark on Kubernetes stop being a "foreign body" in microservice architecture. They become managed workloads with CI/CD, RBAC, autoscaling — like any other service in the cluster.
In a distributed system, failures are normal. Heavyweight frameworks solve this via checkpoints: snapshots of application state periodically saved to reliable storage (HDFS, S3).
On node failure, the Master restores the task on a new worker node, loading the last checkpoint. The framework synchronizes two types of state:
Stores broker offsets. Guarantees that on recovery, the microservice resumes reading exactly where it stopped.
Stores business logic (aggregates, windows, JOIN buffers). Guarantees that computations are neither duplicated nor lost.
If operator state and keyed state are not synchronized in the same checkpoint, you get either data loss or duplication. Frameworks handle this routine, but the developer must understand the mechanism.
Link to Chapter VII: in Kafka Streams, the analog of checkpoint is the changelog topic. In Flink — checkpoint in S3/HDFS. These are two different approaches to the same task.
| Aspect | Flink EOS | Kafka Streams EOS v2 |
|---|---|---|
| Mechanism | Snapshot barrier + 2PC | Kafka transactions |
| Requires sink support | Yes (Kafka, JDBC, File with 2PC) | Only Kafka to Kafka |
| Overhead | Checkpoint frequency + 2PC per sink | Transaction per batch |
| Where applied | Flink jobs with external sinks | Kafka Streams applications |
Flink EOS works with transactional sinks. If a sink doesn't support transactions (e.g., HTTP API), EOS is not guaranteed — you need idempotency on the sink side, as in BPC (Chapter X).
WatermarkStrategy.<Event>forBoundedOutOfOrderness(
Duration.ofSeconds(30))
.withTimestampAssigner((event, ts) ->
event.getOccurredAt().toEpochMilli())
.withIdleness(Duration.ofMinutes(5));
Flink provides full support for what Chapter VI discussed for Kafka Streams: watermarks, allowed lateness, idle sources, watermark alignment. The difference is in abstraction level and power: Flink works with hundreds of sources simultaneously.
The biggest pain of scaling stateful applications is the shuffle operation. When you group data by key (groupByKey), events with the same key must physically land on the same worker node.
If you add or remove worker nodes "on the fly," data already shuffled by old nodes may become unavailable to new ones. Historically this was solved either by stopping the application or by using an External Shuffle Service (ESS) — a dedicated storage that buffers data between tasks.
Apache Spark 3.0+ is learning to dynamically scale without ESS, keeping source nodes active until downstream tasks fetch the data. Flink solves this differently: state is saved to checkpoint and restored on new nodes after rescale. This brings heavyweight frameworks closer to elastic microservices.
Flink 2.0 (March 2025) introduced the disaggregated state backend (ForSt): state lives in remote object storage (S3) rather than on the local disk of TaskManager. State pages in/out on demand.
Benefits: decouples state size from disk, speeds up rescaling, simplifies operations.
Trade-offs: higher latency (ms instead of μs), S3 costs, not for latency-sensitive workloads.
Link to Chapter VII: Flink 2.0 ForSt is the evolution of the same approach discussed for Kafka Streams (internal vs external state).
-- Flink SQL: session windows for ads
SELECT
userId,
COUNT(*) AS view_count,
SESSION_START(eventTime, INTERVAL '30' MINUTE) AS session_start,
SESSION_END(eventTime, INTERVAL '30' MINUTE) AS session_end
FROM ad_views
GROUP BY
userId,
SESSION(eventTime, INTERVAL '30' MINUTE);
This radically lowers the barrier: analysts and data engineers can write streaming jobs without Java API knowledge. SQL becomes the language for ETL and real-time analytics.
Local Flink in unit tests. Runs job in the same JVM with minimal resources. Fast topology verification.
Testing operators in isolation. Feeds events, checks output. No full cluster needed.
Utilities for integration testing Flink jobs. Watermark propagation, state, sinks verification.
Analog for Spark: local SparkSession, test data, output verification.
| Metric | What It Shows | Alert Threshold |
|---|---|---|
checkpoint_duration |
Time to checkpoint | Growing — disk/network bottleneck |
checkpoint_failures |
Checkpoint failures | > 0 within 10 min |
backpressure |
Pressure on upstream | High > 5 min |
watermark_lag |
Watermark lag behind processing time | Growing — source falling behind |
task_skew |
Load imbalance between subtasks | Ratio > 3x |
GC_time |
Garbage collection time | > 10% of CPU time |
| Scenario | Managed | Self-Hosted |
|---|---|---|
| Start | Fast (minutes) | Slow (weeks) |
| Operational cost | Low | High (SRE) |
| Cost at idle | Exists (cluster runs) | Exists (cluster runs) |
| Tuning flexibility | Limited | Full |
| Vendor lock-in | Yes | No |
| Cost at scale | Grows linearly | Grows with discount |
| Criterion | Flink | Spark SS | Beam | Storm |
|---|---|---|---|---|
| Model | True streaming | Micro-batch | Unified (runner-dependent) | True streaming |
| Latency | ms | 100ms+ | depends | ms |
| State backend | RocksDB + ForSt (S3) | In-memory + checkpoint | depends | Custom |
| Exactly-once | Checkpoint + 2PC | Micro-batch transactions | depends | Trident |
| SQL | Flink SQL | Spark SQL | Beam SQL | None |
| K8s native | Yes (operator) | Yes (operator) | Yes | Limited |
DataStream<Event> clickStream = ...;
DataStream<Event> viewStream = ...;
clickStream
.union(viewStream)
.keyBy(e -> e.getUserId())
.window(EventTimeSessionWindows
.withGap(Time.minutes(30)))
.aggregate(new SessionAggregator())
.addSink(producerToKafka);
The framework handles state management of windows, watermarks, and late event processing. The developer only describes the aggregator's business logic.
| Chapter | Focus | Link to This Chapter |
|---|---|---|
| V. The Mechanics of Stream Processing | Windowing, state stores, rebalance | Flink and Spark implement the same at cluster level |
| VI. Time in Streams | Event time, watermarks, late events | Flink provides full watermark support |
| VII. The Memory of Microservices | RocksDB, changelog, TTL | Flink state backend — RocksDB or ForSt (S3) |
| IX. Serverless Architecture | FaaS as opposite pole | Heavyweight frameworks don't fit FaaS paradigm |
| X. Basic Producer/Consumer Pattern | BPC as minimalism | Flink/Spark — upper limit of EDA complexity |
| XI. Heavyweight Frameworks | Flink, Spark, Beam, Storm | Closing the spectrum: from simple to complex |
The main trend today is integrating heavyweight frameworks with Kubernetes. When Flink or Spark begin to perceive CMS as their resource scheduler, they stop being a "foreign body" in the architecture and become full-fledged, albeit very voracious, microservices. The EDA spectrum is closed: from the simplest BPC to the distributed titans of Big Data — each tool occupies its niche.
Chapter XI of the "Event-Driven Architecture" series. Sources: Apache Flink, Apache Spark, Apache Beam, Apache Storm, Confluent, Databricks, production system case studies.