Architekt systemówArchitektura Systemu

Jak zaprojektować globalnie spójną, poziomo skalowalną usługę ograniczania natężenia ruchu, która egzekwuje polityki ograniczania dla użytkowników i najemców w rozproszonych węzłach brzegowych, jednocześnie zapobiegając problemom związanym z tzw. thundering herd podczas ataków na pamięć podręczną oraz zapewniając sprawiedliwość podczas szczytów ruchu?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź na pytanie

Architektura rozproszonego ograniczania natężenia ruchu wymaga zbalansowania silnej spójności z niską latencją w rozproszonych geograficznie węzłach. Rozwiązanie wykorzystuje algorytm hierarchicznego zbiornika tokenów z następującymi składnikami:

Egzekwowanie lokalne na brzegach przy użyciu klastrów Redis z skryptami Lua do atomowego zużycia tokenów • Synchronizacja międzyregionowa za pomocą tematów Apache Kafka do globalnej rekonsyliacji kwot • Spójne haszowanie dla przywiązania użytkowników w celu zminimalizowania narzutów koordynacyjnych

Ta architektura wdraża semantykę logu okna ślizgającego w Redis przy użyciu posortowanych zbiorów (ZADD/ZREMRANGEBYSCORE) do precyzyjnego śledzenia żądań. Protokół Gossip rozprzestrzenia zmiany w konfiguracji limitów we wśród węzłów, eliminując pojedyncze punkty awarii w dystrybucji polityki.

Sytuacja z życia wzięta

Globalna platforma fintechowa przetwarzająca 500K żądań na sekundę doświadczyła katastrofalnych awarii podczas szczytów ruchu w Black Friday. Ich istniejący scentralizowany limiter na Redis wprowadzał opóźnienie przekraczające 150 ms i stał się pojedynczym punktem awarii, powodując kaskadowe czasowe przekroczenia w usługach płatności.

Pierwszym rozważanym rozwiązaniem było czysto lokalne ograniczanie natężenia ruchu w każdym węźle brzegowym NGINX. To podejście oferowało latencję sub-milisekundową i eliminowało zależności sieciowe. Jednak pozwalało to użytkownikom na przekroczenie kwot o czynnik równy liczbie lokalizacji brzegowych, co naruszało wymagania dotyczące zgodności biznesowej i umożliwiało potencjalne nadużycia w rozproszonej infrastrukturze.

Drugie podejście oceniło scentralizowany cluster Redis z Redlock do rozproszonego blokowania. Chociaż zapewniło to idealną spójność, wprowadziło niedopuszczalne opóźnienia dla użytkowników międzynarodowych i wprowadziło krytyczną podatność na podział sieciowy. Podczas problemów z łącznością międzyregionową system doświadczył całkowitego pogorszenia zamiast eleganckiego osłabienia.

Trzecie rozwiązanie wdrożyło hybrydowy licznik okna ślizgającego z ewentualną spójnością z wykorzystaniem CRDTs (Conflict-free Replicated Data Types). Zapewniło to matematyczne gwarancje zbieżności limitu ruchu bez koordynacji. Jednak pozwoliło to na tymczasowe naruszenia kwot podczas wydarzeń podziału, co było niedopuszczalne w przypadku zgodności finansowej wymagającej ścisłej kontroli wydatków.

Wybrana architektura wykorzystała dwuetapowe ograniczanie natężenia ruchu: ścisłe egzekwowanie lokalne na węzłach brzegowych przy użyciu Redis z pojemnikami opartymi na TTL, w połączeniu z asynchroniczną rekonsyliacją za pośrednictwem strumieni Kafka dla globalnej egzekucji kwot. Spójne haszowanie kierowało użytkowników do konkretnego regionalnego klastra, zapewniając, że 95% żądań nie wymagało koordynacji międzyregionowej. To zrównoważyło potrzebę natychmiastowego lokalnego egzekwowania z ewentualną globalną spójnością.

Wdrożenie zmniejszyło latencję P99 z 150 ms do 8 ms i obsłużyło szczyty ruchu 10x bez degradacji. System elegancko się degradował podczas podziałów sieciowych, pozwalając lokalnemu egzekwowaniu na kontynuowanie z nieco złagodzonymi globalnymi restrykcjami, co pozwalało na utrzymanie dostępności usługi podczas regionalnych awarii.

Co często umykają kandydatom

Jak radzić sobie z przesunięciem zegara między rozproszonymi limiterami natężenia ruchu, gdy używasz algorytmów zbiornika tokenów bez scentralizowanej koordynacji?

Synchronizacja zegara jest cichym zabójcą rozproszonych systemów ograniczania natężenia ruchu. Gdy węzły brzegowe doświadczają dryfu NTP, obliczenia zbiornika tokenów stają się niedokładne, co może prowadzić do nadmiernych wniosków lub sztucznie ograniczać ruch. Rozwiązanie wymaga implementacji logicznych zegarów za pomocą znaczników czasowych Lamporta lub Hybrydowych Logicznych Zegarów w połączeniu z buforami tolerancji dryfu. Każda operacja napełniania tokenów powinna obejmować weryfikację monotonicznego znacznika czasu, odrzucając żądania napełnienia, w których różnica znacznika czasu przekracza skonfigurowane progi (zazwyczaj 100-500 ms). To zapobiega możliwości wykorzystania, jednocześnie utrzymując dostępność podczas drobnych zdarzeń przesunięcia zegara.

Jakie strategie zapobiegają scenariuszom thundering herd, gdy licznik limitu ruchu wygasa w środowisku o wysokiej współbieżności?

Thundering herd występuje, gdy tysiące żądań jednocześnie odkrywają wygasły klucz limitu natężenia ruchu i próbują równocześnie napełnić tokeny, przytłaczając zaplecze Redis. Standardowe skrypty Lua do atomowych inkrementów rozwiązują podstawowy problem wyścigu, ale nie zapobiegają nagłym atakom podczas wygasania klucza. Implementuj prawdopodobne wczesne wygasanie (znane również jako Jitter), gdzie każde żądanie ma małą prawdopodobieństwo (zazwyczaj 1%) odnowienia zbiornika tokenów nieco przed faktycznym wygaśnięciem. Alternatywnie, użyj modułu Redis Cell lub strumieni Redis z operacjami XADD, które traktują limity jako dane czasowe, a nie proste liczniki. To przekształca thundering herd w płynny, rozłożony wzór regeneracji bez złożoności kodu w warstwie aplikacji.

Jak egzekwować sprawiedliwość między najemcami, gdy jeden najemca dominuje w wolumenie żądań, potencjalnie głodząc innych w współdzielonej infrastrukturze limitów?

Sprawiedliwość wymaga wdrożenia algorytmów Ważącej Sprawiedliwej Kolejki (WFQ) lub Hierarchicznego Zbiornika Tokenów (HTB) zamiast prostych stałych limitów na najemcę. W wielonajemczym środowisku Kubernetes rozważ użycie Proxy Envoy z filtrami lokalnego ograniczania natężenia ruchu połączonymi z adaptacyjną kontrolą współbieżności. Kluczowe zrozumienie polega na oddzieleniu punktu egzekwowania od punktu decyzyjnego: użyj wzoru sidecar, w którym Envoy obsługuje natychmiastowe odrzucenie na podstawie buforowanych wag, podczas gdy centralna płaska kontrolna działająca w etcd okresowo przelicza wagi na podstawie historycznych wzorców konsumpcji. To zapobiega problemom z głośnymi sąsiadami, jednocześnie zapewniając, że nieregularne, ale uzasadnione najemcy mogą nadal uzyskiwać dostęp do zasobów podczas okresów poza szczytem.