Architekt systemówArchitekt Systemów

Rozwiń architekturę platformy do przetwarzania strumieniowego o stanie w skali planetarnej, która umożliwia semantykę dokładnego razu dla agregacji okien czasowych w nieograniczonych strumieniach danych, zapewnia automatyczne ponowne balansowanie w trakcie zmian topologii i utrzymuje odzyskiwanie punktów kontrolnych poniżej sekundy, jednocześnie wspierając wiele języków przetwarzania i replikację aktywno-aktywną bez zależności od wspólnego przechowywania?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Historia pytania

Architektury przetwarzania strumieniowego ewoluowały od przetwarzania przynajmniej raz w Apache Storm do nowoczesnych gwarancji dokładnie raz wprowadzonych przez Apache Flink i Spark Structured Streaming. Gdy przedsiębiorstwa migrowały z batchowych architektur Lambda do ciągłych strumieni Kappa, złożoność przesuwała się z prostych transformacji do zarządzania rozproszonym stanem dla agregacji okien i sesji. Pojawienie się wymagań związanych z suwerennością danych i ograniczeniami opóźnień regionalnych wymusiło wdrożenia aktywno-aktywne bez polegania na wspólnym przechowywaniu NFS lub SAN, co stworzyło nowe wyzwania dotyczące spójności stanu podczas awarii geograficznych.

Problem

Przetwarzanie strumieniowe z stanem wymaga utrzymywania gigabajtów stanu operatorów (okna z kluczem, przechowalnie sesji) lokalnie na węzłach przetwarzających, przy jednoczesnym wchłanianiu milionów wydarzeń na sekundę. Semantyka dokładnie raz wymaga atomowych zatwierdzeń w trzech komponentach: śledzenie offsetów źródła, aktualizacje zaplecza stanu oraz zapisy do sinka. Replikacja aktywno-aktywna między regionami bez wspólnego przechowywania wprowadza ryzyko tzw. split-brain, gdy występują partycje sieciowe, podczas gdy autoskalowanie wymaga migracji stanu na żywo bez utraty rekordów w ruchu czy naruszania gwarancji czasu przetwarzania. Wspieranie wielu języków (Java, Python, Go) tradycyjnie wymusza narzut na serializację lub blokady związane z określonym środowiskiem wykonawczym.

Rozwiązanie

Architektura wykorzystuje decoupled design z Apache Kafka lub Apache Pulsar jako jednolitym dziennikiem, węzły przetwarzania działają na Kubernetes z bezjęzykowymi sidecarami gRPC dla wsparcia polyglot. Zarządzanie stanem korzysta z wbudowanego RocksDB z asynchronicznymi inkrementalnymi punktami kontrolnymi do zgodnego z S3 przechowywania obiektów, koordynowanymi za pomocą lekkiej usługi koordynacji rozproszonej (etcd lub ZooKeeper). Semantykę dokładnie raz osiąga się dzięki algorytmowi snapshot Chandy-Lamport dla stanu i protokołom dwuetapowego zatwierdzenia (2PC) dla transakcyjnych sinków (transakcje Kafka lub idempotentne zapisy JDBC). Replikacja międzyregionowa wykorzystuje przesyłanie stanu oparte na dzienniku poprzez Kafka MirrorMaker 2 lub Pulsar Geo-Replication, z rozwiązywaniem konfliktów przez CRDT-based komutacyjne liczniki dla agregacji i wersjonowane pierwotne własności dla stanów z kluczem.

Odpowiedź na pytanie

Platforma składa się z czterech warstw logicznych: wchłaniania, przetwarzania, zarządzania stanem i koordynacji.

Warstwa Wchłaniania

Klastery Apache Kafka działają w wielu regionach z MirrorMaker 2, umożliwiając dwukierunkową replikację tematów. Idempotencja producenta i transakcyjne identyfikatory zapewniają dokładne wchłanianie nawet podczas awarii producenta między regionami.

Warstwa Przetwarzania

Zarządzane przez Apache Flink lub podobne przetworniki strumieniowe działają jako StatefulSets w Kubernetes. Każdy TaskManager eksponuje sidecara gRPC, który akceptuje zadania serializowane za pomocą Protobuf, umożliwiając wykonanie funkcji zdefiniowanych przez użytkownika (UDF) w kontenerach gRPC, podczas gdy środowisko uruchomieniowe Java zarządza stanem i punktami kontrolnymi. JobManager dzieli topologię między TaskManagery za pomocą consistent hashing na kluczach rekordów.

Zarządzanie Stanem

Zaplecze stanu operatora korzysta z RocksDB z włączoną inkrementalną kontrolą punktową. Punkty kontrolne zapisują zmiany stanu delta do regionalnych koszy S3 asynchronicznie co 15 sekund. Dla spójności międzyregionowej, wdrożenia aktywno-aktywne wykorzystują CRDT LWW-Element-Set dla monotonicznych agregacji (liczby, sumy) oraz powiązanie klucza podstawowego dla operacji niekomutacyjnych. Podczas regionalnej awarii, zapasowe TaskManagery hodują stan z S3 przy użyciu Savepoints.

Gwarancje Dokładnie Raz

System implementuje końcówkę do końca dokładnie raz poprzez:

  • Dwuetapowe Zatwierdzenie: Sinki uczestniczą w TwoPhaseCommitSinkFunction Flink'a, wstępnie zatwierdzając do Kafka lub PostgreSQL podczas punktów kontrolnych i zatwierdzając na pozytywne powiadomienie o punkcie kontrolnym.
  • Idempotentni Producenci: Górni producenci Kafka używają idempotentnej dostawy z numerami sekwencyjnymi do eliminacji duplikatów.
  • Izolacja Transakcji: Punkty kontrolne działają jako granice transakcyjne; niezatwierdzone dane pozostają niewidoczne dla konsumentów downstream.

Sytuacja z życia

Globalna platforma ride-sharing wymagała obliczeń cen surge w czasie rzeczywistym, agregujących dostępność kierowców i popyt na przejazdy w każdym geohash w AWS us-east-1 i AWS eu-west-1. Poprzednia architektura korzystała z klastra Redis z jedną lokalizacją i opóźnieniem replikacji, co powodowało okna awarii na poziomie 2 sekund, podczas gdy obliczenia cen powodowały przestarzałe lub duplikowane mnożniki surge podczas awarii regionalnych, co skutkowało błędnymi obliczeniami taryf i skargami klientów.

Rozwiązanie 1: Aktywny-Pasywny z Wspólnym Przechowywaniem

Zespół rozważył zamontowanie EFS (wspólne NFS) w różnych regionach do przechowywania stanu. Zalety: Uproszczona awaria z pojedynczą semantyką zapisu, silna spójność. Wady: Latencja EFS przekraczała 100 ms dla dostępu międzyregionowego, naruszając 50 ms SLA przetwarzania; dodatkowo, problemy z spójnością zapisu NFS spowodowały uszkodzenie punktów kontrolnych podczas partycji sieciowych.

Rozwiązanie 2: Architektura Lambda

Wdrożenie warstwy prędkości z Kafka Streams i warstwy batch z Spark dla korekcji. Zalety: Odporność na awarie poprzez niezmienne dzienniki, prosta regeneracja. Wady: Złożoność operacyjna związana z utrzymywaniem dwóch ścieżek kodu; poprawki batchowe przychodziły zbyt późno na cenę surge, która wymagała dokładności poniżej sekundy, by zrównoważyć podaż i popyt.

Rozwiązanie 3: Aktywny-Aktywny Przetwarzanie Strumieniowe z CRDT

Wdrożenie Apache Flink w obu regionach z stanem RocksDB, inkrementalnymi punktami kontrolnymi S3 i licznikami opartymi na CRDT dla liczby przejazdów. Zalety: Lokalne opóźnienie przetwarzania poniżej 20 ms, automatyczne rozwiązanie konfliktów dla równoczesnych aktualizacji regionalnych, awaryjność bez przestojów. Wady: Wymagało przeorganizowania agregacji w celu ich komutatywności (używając G-Counters i PN-Counters), zwiększało koszty przetrzymywania danych dla punktów kontrolnych w dwóch regionach.

Zespół wybrał Rozwiązanie 3, ponieważ wymaganie biznesowe 99,99% dostępności z awarią poniżej sekundy nie mogło tolerować 2-sekundowego okna Rozwiązania 1 ani latencji wspólnego przechowywania. Wdrożyli G-Counters dla liczby kierowców i LWW-Registers dla najnowszych multiplikatorów cenowych.

Wynik

System osiągnął dokładnie raz obliczenia cen surge z latencją p99 15 ms w obu regionach. Podczas symulowanej awarii us-east-1, eu-west-1 kontynuował przetwarzanie, korzystając z lokalnie replikowanego stanu bez duplikatów obliczeń taryf. Czas odzyskiwania punktów kontrolnych wynosił średnio 800 ms, co mieściło się w wymaganiu poniżej sekundy.

Co kandydaci często pomijają

Jak dostosowanie interwałów punktów kontrolnych wpływa na mechanizmy przeciążenia w stanowych przetwornikach strumieniowych?

Wielu kandydatów optymalizuje interwały punktów kontrolnych pod kątem czasu regeneracji, nie biorąc pod uwagę propagacji przeciążenia. Gdy bariery punktów kontrolnych dostosowują się powoli z powodu przeciążenia, algorytm Chandy-Lamport zatrzymuje wykonanie potoku, potencjalnie powodując kaskadowe przekroczenia czasu. Odpowiednie podejście polega na dostosowaniu limitów czasów punktów kontrolnych do progów przeciążenia, stosując nieprzystosowane punkty kontrolne (gdzie bariery wyprzedzają bufory) podczas dużego obciążenia, oraz oddzielając synchronizowane i asynchronizowane fazy punktów kontrolnych. Inkrementalne punkty kontrolne RocksDB muszą być spowolnione przy użyciu konfiguracji RateLimiter, aby zapobiec przeciążeniu dysku I/O oraz pogorszeniu przeciążenia.

Jaka jest zasadnicza różnica między dostarczaniem przynajmniej raz w połączeniu z idempotentnymi sinkami a prawdziwą semantyką przetwarzania dokładnie raz?

Idempotentne sinki gwarantują, że duplikowane przetwarzanie produkuje ten sam stan wyjściowy (np. operacje UPSERT w PostgreSQL lub HBase), ale ujawniają stany pośrednie podczas ponownych prób. Jeśli sink zapisuje rekordy A, B, a następnie awarii i ponownie zapisuje A, B, C, obserwatorzy downstream przez chwilę widzą A, B, A, B, C przed eliminacją duplikatów. Prawdziwe dokładnie raz (efektywnie raz) wykorzystuje izolację transakcyjną, gdzie wstępnie zatwierdzone dane pozostają niewidoczne do zakończenia punktu kontrolnego. To wymaga, aby sink wspierał transakcje (np. transakcje Kafka z isolation.level=read_committed) lub protokoły dwuetapowego zatwierdzenia. Kandydaci często pomijają, że idempotencja rozwiązuje problem poprawności, ale nie problem spójności/widoczności podczas odzyskiwania.

Jak powinno się obsługiwać okna czasowe zdarzeń w przypadku późnego przybywania danych podczas scenariuszy awarii międzyregionowych?

Gdy występuje awaria z Regionu A do Regionu B, rekordy w ruchu w buforach sieciowych Regionu A mogą zostać utracone lub opóźnione poza horyzont watermark. Kandydaci często sugerują wydłużenie watermarków w nieskończoność, co narusza gwarancje pełności okien. Odpowiednia architektura wykorzystuje Side Outputs (w terminologii Flink) do uchwycenia późnych danych połączonych z wymaganiami Allowed Lateness. Podczas awarii system powinien hodować okna z zapisów S3 z znacznikami czasu, a następnie łączyć późno przybywające rekordy z martwego zbioru w regionie awarii w kolejne okna lub uruchamiać specyficzne obsługiwacze późnych danych. Dodatkowo generacja watermarków musi być idempotentna między regionami; korzystanie z czasu zegarowego do watermarków powoduje odchylenia podczas awarii, dlatego watermarki muszą wynikać z monotonicznego wydobywania czasu zdarzeń w obu aktywnych regionach.