Архитектуры потоковой обработки эволюционировали от обработки рекордов как минимум один раз с помощью Apache Storm к современным гарантиям точно-один, которые были введены в Apache Flink и Spark Structured Streaming. Поскольку предприятия переходили от пакетных Lambda-архитектур к непрерывным Kappa потокам, сложность сместилась от простых трансформаций к управлению распределенным состоянием для оконных агрегаций и сессий. Появление требований к суверенитету данных и ограничениям по региональной задержке потребовало активного-активного развертывания без опоры на совместное NFS или SAN хранение, создавая новые задачи для согласованности состояния во время географических переключений.
Состояние потоковой обработки требует поддержания гигабайтов состояния операторов (ключевые окна, хранилища сессий) локально на узлах обработки при принятии миллионов событий в секунду. Семантика точно-один требует атомарных коммитов через три компонента: отслеживание смещения источника, обновления состояния бэкенда и записи в приемник. Кросс-региональная активная-активная репликация без общего хранилища вводит риски разделения разума при возникновении сетевых разделений, в то время как автоматическое масштабирование требует прямой миграции состояния без потерь в пакетах или нарушения временных гарантий обработки. Поддержка нескольких языков (Java, Python, Go) традиционно требует накладных расходов на сериализацию или привязку к конкретному времени выполнения.
Архитектура использует декуплированный дизайн с Apache Kafka или Apache Pulsar в качестве унифицированного лога, узлы обработки работают на Kubernetes с языко-независимыми gRPC-сайдкарами для поддержки полиглотности. Управление состоянием использует встраиваемую RocksDB с асинхронными инкрементальными контрольными точками в S3-совместимое объектное хранилище, координируемое через легковесную службу распределенной координации (etcd или ZooKeeper). Семантика точно-один достигается за счет алгоритма снимков Chandy-Lamport для состояния и протоколов двухфазного коммита (2PC) для транзакционных приемников (Kafka транзакции или идемпотентные JDBC записи). Кросс-региональная репликация использует доставку состояния на основе логов через Kafka MirrorMaker 2 или Pulsar Geo-Replication, при этом разрешение конфликтов осуществляется с помощью взаимозависимых счетчиков на основе CRDT для агрегаций и версионного первичного владения для ключевого состояния.
Платформа состоит из четырех логических слоев: инжекция, обработка, управление состоянием и координация.
Слой инжекции
Кластеры Apache Kafka работают в нескольких регионах с использованием MirrorMaker 2, что обеспечивает двунаправленную репликацию тем. Идемпотентность производителей и транзакционные ID гарантируют точно-один инжекцию даже при сбое производителей между регионами.
Слой обработки
Apache Flink или подобные процессоры потоков работают как StatefulSets в Kubernetes. Каждый TaskManager предоставляет gRPC-сайдкар, который принимает задачи в сериализованном формате Protobuf, что позволяет функциям, определяемым пользователями на Python и Go (UDF), выполняться внутри контейнеров gRPC, в то время как Java-окружение управляет состоянием и контрольными точками. JobManager разделяет топологию между TaskManagers, используя согласованное хеширование по ключам записей.
Управление состоянием
Бэкенды состояния операторов используют RocksDB с включенным инкрементальным контрольным пунктом. Контрольные точки записывают дельта изменения состояния в региональные S3 бакеты асинхронно каждые 15 секунд. Для кросс-региональной согласованности активные-активные развертывания используют LWW-Element-Set CRDT для монотонных агрегаций (счета, суммы) и привязанность первичного ключа для неконституционных операций. При сбое региона резервные TaskManagers восстанавливают состояние из S3 с использованием Savepoints.
Гарантии точно-один
Система реализует конец-в-концу точно-один через:
Глобальная платформа для совместного использования поездок требовала расчета цен на основе спроса в реальном времени, агрегируя доступность водителей и спрос на поездки по геохэшам в регионах AWS us-east-1 и AWS eu-west-1. Предыдущая архитектура использовала кластер Redis с единственным первичным узлом с задержкой репликации, что приводило к 2-секундным окнам переключения, в течение которых расчеты цен давали устаревшие или дублирующие множители, что приводило к неправильным расчетам тарифов и жалобам клиентов.
Решение 1: Активный-пассивный с общим хранилищем
Команда рассматривала возможность монтирования EFS (совместное NFS) в различных регионах для хранения состояния. Плюсы: Упрощенное переключение с семантикой единого писателя, высокая согласованность. Минусы: Задержка EFS превышала 100 мс при доступе между регионами, нарушая SLA на обработку 50 мс; кроме того, проблемы с согласованностью записи NFS вызывали повреждение контрольных точек во время сетевых разделений.
Решение 2: Архитектура Lambda
Внедрение скорости через Kafka Streams и пакетный уровень с помощью Spark для коррекций. Плюсы: Устойчивость к сбоям благодаря неизменяемым логам, простое восстановление. Минусы: Операционная сложность поддержания двух кодовых путей; пакетные коррекции приходили слишком поздно для расчета цен на основе спроса, что требовало точности до секунды для баланса спроса и предложения.
Решение 3: Активная-активная потоковая обработка с CRDT
Развертывание Apache Flink в обоих регионах с состоянием RocksDB, инкрементальными контрольными точками S3 и счетчиками на основе CRDT для подсчета поездок. Плюсы: Локальная задержка обработки менее 20 мс, автоматическое разрешение конфликтов для одновременных обновлений в регионах, переключение без простоя. Минусы: Требовалась переработка агрегаций для обеспечения коммутативности (с использованием G-Counters и PN-Counters), увеличенные затраты на хранение для двойных контрольных точек в регионах.
Команда выбрала Решение 3, поскольку бизнес-требование 99.99% доступности с переключением менее чем за секунду не могло терпеть 2-секундное окно решения 1 или задержки общего хранилища. Они реализовали G-Counters для подсчета водителей и LWW-Registers для последних множителей цен.
Результат
Система достигла точно-один расчетов цен на основе спроса с задержкой 15 мс на уровне p99 в обоих регионах. Во время симулированного сбоя us-east-1 регион eu-west-1 бесшовно продолжал обработку, используя локально реплицированное состояние без дублирования расчетов тарифов. Среднее время восстановления контрольных точек составило 800 мс, что хорошо вписывается в требования менее чем за секунду.
Как настройки интервала контрольных точек взаимодействуют с механизмами обратного давления в потоковых процессорах с состоянием?
Многие кандидаты оптимизируют интервалы контрольных точек для времени восстановления, не учитывая распространение обратного давления. Когда барьеры контрольных точек выстраиваются медленно из-за обратного давления, алгоритм Chandy-Lamport приостанавливает выполнение конвейера, что потенциально может вызывать каскады тайм-аутов. Правильный подход заключается в том, чтобы выровнять тайм-ауты контрольных точек с порогами обратного давления, используя невыравненные контрольные точки (где барьеры обходят буферы) во время высокой нагрузки, и разделить синхронные и асинхронные фазы контрольных точек. Инкрементальные контрольные точки RocksDB должны быть ограничены с использованием конфигураций RateLimiter, чтобы предотвратить перегрузку I/O диска и ухудшение обратного давления.
В чем фундаментальное различие между доставкой как минимум один раз, комбинированной с идемпотентными приемниками, и истинной семантикой точно-один?
Идемпотентные приемники гарантируют, что дублированная обработка приводит к одному и тому же выходному состоянию (например, операции UPSERT в PostgreSQL или HBase), но они подвергают промежуточные состояния во время повторных попыток. Если приемник записывает записи A, B, затем сбой и повторная попытка записи A, B, C, downstream-наблюдатели временно видят A, B, A, B, C до дедупликации. Истинно точно-один (эффективно-один) использует транзакционную изоляцию, где предкоммиты остаются невидимыми до завершения контрольной точки. Это требует от приемника поддержки транзакций (например, Kafka транзакции с isolation.level=read_committed) или протоколов двухфазного коммита. Кандидаты часто упускают тот факт, что идемпотентность решает проблему корректности, но не проблему согласованности/видимости во время восстановления.
Как должна обрабатываться оконная обработка времени события с поздно прибывающими данными во время сценариев сбоя между регионами?
Когда происходит сбой с региона A в регион B, находящиеся в пути записи в сетевых буферах региона A могут быть потеряны или задержаны за пределами горизонта watermark. Кандидаты часто предлагают неограниченное продление меток времени, что нарушает гарантии полноты окна. Правильная архитектура использует Side Outputs (в терминологии Flink) для захвата поздних данных в сочетании с назначениями Allowed Lateness. Во время сбоя система должна восстанавливать окна из S3 Savepoints с временными метками, а затем объединять поздно прибывающие записи из устаревшей очереди региона в последующие окна или вызывать конкретные обработчики поздних данных. Кроме того, генерация меток времени должна быть идемпотентной между регионами; использование времени стенда для меток времени приводит к расхождению во время сбоя, поэтому метки времени должны извлекаться из монотонной обработки времени событий по обоим активным регионам.