Notatka
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Projektuj odbiorców komunikatów tak, aby przetwarzanie tego samego komunikatu więcej niż raz było takie samo jak przetwarzanie go raz. Systemy obsługi komunikatów, które gwarantują co najmniej jednokrotne dostarczanie, mogą wielokrotnie dostarczać ten sam komunikat. Odporność na duplikaty gwarantuje, że ponowne przetwarzanie komunikatu nie tworzy zduplikowanych rekordów, dwukrotnie obciąża klienta lub ma inne niepożądane skutki.
Kontekst i problem
Aplikacje rozproszone często przekazują zadania za pośrednictwem brokera komunikatów zamiast korzystać z bezpośrednich wywołań synchronicznych. Większość brokerów, w tym Azure Service Bus, Azure Event Hubs, Apache Kafka i RabbitMQ, zapewnia co najmniej jednokrotne dostarczanie. Gwarantuje to, że komunikat dociera do konsumenta nawet wtedy, gdy wystąpią błędy, ale także oznacza, że broker może dostarczyć ten sam komunikat więcej niż raz.
Zagwarantowanie dostarczenia dokładnie raz w systemie rozproszonym jest niepraktyczne. Nawet brokerzy, którzy stosują semantykę exactly-once, mogą zagwarantować jedynie operacje, które sami bezpośrednio kontrolują, takie jak dostarczanie komunikatów konsumentom lub zapisywanie danych z powrotem do brokera. Nie mogą kontrolować skutków ubocznych, które konsumenci implementują w systemach zewnętrznych. Trwałym rozwiązaniem nie jest eliminowanie podwójnego dostarczenia, lecz zapewnienie, by konsument prawidłowo je przetwarzał. Po połączeniu co najmniej jednokrotnego dostarczania z konsumentem, który ignoruje duplikaty, uzyskujesz efektywne przetwarzanie jednokrotne .
Duplikaty mogą pochodzić z kilku źródeł:
Producent ponawia próbę. Producent wysyła wiadomość, nie otrzymuje potwierdzenia z powodu przejściowego błędu sieci lub przekroczenia limitu czasu i ponownie wysyła wiadomość. Broker przechowuje teraz dwie kopie, mimo że wysłanie powiodło się po raz pierwszy.
Ponowne dostarczenie po braku potwierdzenia. Użytkownik odbiera i przetwarza komunikat, ale nie może go potwierdzić, ponieważ użytkownik ulega awarii, wygaśnie blokada lub potwierdzenie zostanie utracone. Broker zakłada, że komunikat nie został przetworzony i dostarczy go ponownie.
Błędy konsumentów podczas przetwarzania. Użytkownik ukończy zapis bazy danych, ale ulega awarii przed potwierdzeniem komunikatu. Inna instancja pobiera komunikat i ponawia zapis.
Rozwiązanie
Utwórz konsumenta idempotentnego, każąc mu przechowywać rejestr komunikatów, które pomyślnie przetworzył, i pomijać wszystkie komunikaty już przetworzone na podstawie stabilnego identyfikatora, który pozostaje niezmienny przy ponownym dostarczeniu. Konsument sprawdza trwały magazyn identyfikatorów, aby ustalić, czy ten identyfikator został już przetworzony, a następnie albo przetwarza komunikat, albo odrzuca go jako duplikat.
Przepływ podstawowy składa się z następujących kroków:
- Przeczytaj komunikat i wyodrębnij jego klucz deduplikacji.
- Sprawdź magazyn deduplikatów pod kątem tego klucza.
- Jeśli klucz już istnieje w magazynie, traktuj komunikat jako duplikat. Potwierdź komunikat i zatrzymaj jego przetwarzanie, opcjonalnie zwracając wcześniej zarejestrowany wynik.
- Jeśli klucz jeszcze nie istnieje w repozytorium, przetwórz komunikat i zapisz klucz w ramach jednej operacji atomowej, a następnie potwierdź odbiór komunikatu.
Poniższe sekcje zawierają wskazówki dotyczące zapewnienia idempotentności konsumentów:
Wybieranie stabilnego klucza deduplikacji
Klucz deduplikacji musi jednoznacznie i spójnie identyfikować wiadomość logiczną przy każdym ponownym dostarczeniu. Użyj identyfikatora komunikatu nadanego przez producenta albo klucza idempotencji na poziomie biznesowym, identyfikujących konkretną operację logiczną, zamiast wspólnego kontekstu korelacji, który może być przenoszony przez kilka komunikatów.
Na przykład ustaw właściwość Service Bus MessageId na wartość, która jednoznacznie identyfikuje komunikat logiczny. Nie używaj CorrelationId jako klucza, ponieważ odnosi się on do grup powiązanych wiadomości, takich jak żądanie i jego odpowiedzi. W przypadku zdarzeń zgodnych ze specyfikacją CloudEvents kombinacja atrybutów source i id jednoznacznie identyfikuje zdarzenie i pozostaje niezmienna przy ponownych dostarczeniach.
Nie opieraj się na identyfikatorach warstwy transportowej, które broker generuje ponownie przy ponownym dostarczeniu, ani na wartościach wynikających z prób dostarczenia. Te wartości zmieniają się pomiędzy dostawami i uniemożliwiają wykrywanie duplikatów. Należy również unikać wyprowadzania klucza na podstawie nietrwałych pól, takich jak znaczniki czasu odbioru.
Gdy więcej niż jeden niezależny odbiorca przetwarza ten sam kanał, na przykład wielu subskrybentów w architekturze publikowania-subskrybowania, każdy odbiorca otrzymuje własną kopię wiadomości i musi samodzielnie śledzić zakończenie przetwarzania. Jeśli konsumenci współdzielą magazyn deduplikacji, należy przypisać rekordom klucz złożony z tożsamości konsumenta i identyfikatora komunikatu. Magazyn indeksowany wyłącznie tożsamością komunikatu pozwoliłby pierwszemu konsumentowi zablokować przetwarzanie przez wszystkich pozostałych konsumentów.
Wybieranie miejsca przechowywania przetworzonych kluczy
Następujące opcje magazynu są typowe dla przetworzonych kluczy:
Dedykowana tabela deduplikacji. Odbiorca przechowuje oddzielną tabelę, czasami nazywaną skrzynką odbiorczą, która zawiera jeden wiersz na przetworzony klucz. Takie podejście utrzymuje obawy dotyczące deduplikacji niezależnie od danych biznesowych i działa dobrze, jeśli wiele typów komunikatów współużytkuje ten sam mechanizm.
Sama jednostka biznesowa. Konsument przechowuje klucz w rekordzie, który jest tworzony lub aktualizowany przez wiadomość. Takie podejście pozwala uniknąć oddzielnej tabeli, ale łączy deduplikację z typem danych biznesowych.
Zatwierdź przetworzony klucz i skutki uboczne atomowo
Schemat „najpierw sprawdź, potem przetwórz” ma okno awarii. Jeśli konsument przetwarza komunikat, a następnie zapisuje klucz w osobnym kroku, awaria między tymi dwiema operacjami powoduje, że skutki uboczne zostają już zastosowane, ale klucz nie zostaje zapisany, więc konsument ponownie przetwarza komunikat przy następnym ponownym dostarczeniu.
Unikaj tego okna niepowodzenia, zapisując znacznik deduplikacji i skutki uboczne biznesowe w tej samej transakcji. Jeśli obie operacje są zatwierdzane razem albo nie są zatwierdzane wcale, to przy ponownym dostarczeniu konsument albo znajduje znacznik i pomija komunikat, albo nie znajduje znacznika, ponieważ transakcja nie została zakończona, i może bezpiecznie ponownie przetworzyć komunikat. Ten wariant transakcyjny to wzorzec Inbox i stanowi odpowiednik po stronie konsumenta dla wzorca Transactional Outbox po stronie producenta.
Ochrona przed współbieżnymi duplikatami
Przy dostarczaniu co najmniej raz i współbieżnych rywalizujących konsumentach dwie instancje mogą jednocześnie otrzymać kopie tego samego komunikatu. Oba wystąpienia mogą przejść sprawdzenie istnienia, zanim którekolwiek z nich zatwierdzi transakcję, więc samo sprawdzenie nie zapobiega podwójnemu przetwarzaniu.
Wymuś poprawność w magazynie danych zamiast w logice aplikacji, wykonując następujące czynności:
Użyj ograniczenia unikatowości w kluczu deduplikacji, tak aby dwie transakcje mogły próbować wstawić klucz, ale tylko jeden może zakończyć się powodzeniem. Druga transakcja nie spełnia ograniczenia i traktuje wiadomość jako duplikat. Takie podejście sprawia, że baza danych jest pojedynczym arbiterem konfliktu.
Unikaj warunków wyścigu typu „sprawdź, a potem ustaw” w pamięciach podręcznych. Wzorzec, który najpierw sprawdza klucz, a następnie ustawia go w dwóch oddzielnych operacjach, pozostawia okno czasowe, które umożliwia współbieżnym ponowieniom przejęcie klucza. Użyj atomowego zapisu warunkowego, takiego jak wstawienie, które kończy się niepowodzeniem w razie konfliktu, lub operacji set-if-absent, aby przejęcie klucza było pojedynczym krokiem atomowym.
Obsługa skutków ubocznych, które nie mogą dołączyć do transakcji
Niektóre procesy, takie jak wywoływanie interfejsu API firmy trzeciej lub zapisywanie w zewnętrznym magazynie danych, nie mogą uczestniczyć w transakcji bazy danych konsumenta. W przypadku tych procesów należy użyć następującego podejścia dwufazowego:
- Zapisz klucz w stanie w toku, a następnie wykonaj akcję zewnętrzną.
- Zaktualizuj rekord do ukończenia i zapisz wynik.
Przy ponownym dostarczeniu rekord o statusie completed informuje konsumenta, aby nie ponawiać wywołania. Rekord in-progress sygnalizuje, że poprzednia próba mogła zostać częściowo ukończona lub jest przetwarzana przez innego konsumenta. Odbiorca powinien uzgodnić nieaktualne rekordy lub przekazać nierozwiązane przypadki do interwencji przed potwierdzeniem ponownego dostarczenia.
Problemy i zagadnienia
Podczas podejmowania decyzji o zaimplementowaniu tego wzorca należy wziąć pod uwagę następujące kwestie:
Preferuj naturalnie idempotentne operacje. Niektóre operacje są z natury idempotentne i nie wymagają prowadzenia ewidencji na potrzeby deduplikacji. Operacja upsert oparta na identyfikatorze biznesowym, zapis ustawiający wartość bezwzględną zamiast przyrostu lub żądanie HTTP
PUTdo identyfikatora zasobu dają takie same wyniki niezależnie od tego, czy zostaną wykonane raz, czy wiele razy.Czasami można wykonać operację naturalnie idempotentną przy użyciu transferu stanu przenoszonego przez zdarzenia. Komunikat zawiera wynikowy stan bezwzględny, taki jak nowy status zamówienia, więc konsument przetwarza go jako operację upsert zamiast zmiany względnej.
Tip
Projektuj z myślą o naturalnej idempotentności, jeśli to możliwe, i stosuj techniki deduplikacji tylko w przypadku operacji, których nie da się w sposób naturalny uczynić idempotentnymi.
Użyj struktury obsługi komunikatów zamiast konfigurowania deduplikacji. Prawidłowe wdrożenie deduplikacji danych, zatwierdzania i czyszczenia jest podatne na błędy. Struktury oparte na komunikatach zapewniają ten wzorzec jako wbudowaną funkcję.
Na przykład NServiceBus deduplikuje przychodzące wiadomości na podstawie ich identyfikatorów i oferuje konfigurowalne okresy przechowywania oraz mechanizmy czyszczenia danych deduplikacji. Skrzynka nadawcza odbiorcy MassTransit śledzi komunikaty odebrane przez ich identyfikatory komunikatów, aby zapewnić dokładnie jednokrotne zachowanie użytkownika.
Zarządzaj cyklem życia rekordów deduplikacji. Rekordy deduplikacji gromadzą się, chyba że ustawisz dla nich czas wygaśnięcia. Zachowaj każdy rekord co najmniej tak długo, jak broker nadal będzie mógł ponownie przekazać oryginalną wiadomość. Rozmiar tego okna zależy od maksymalnej liczby prób dostarczenia przez brokera, czasu blokady lub limitu czasu widoczności oraz czasu życia komunikatu.
Ustaw czas życia rekordów deduplikacji tak, aby przekraczał to okno, dzięki czemu późne ponowne dostarczenie nadal mogło odnaleźć swój znacznik. Usuwanie rekordów za wcześnie powoduje ponowne otwarcie okna dla duplikatów. Należy uwzględnić komunikaty, które operatorzy ponownie kierują z kolejek dead-letter, ponieważ takie ponowne skierowania mogą nastąpić długo po zakończeniu standardowego okna ponownego dostarczenia.
Nie należy stosować deduplikacji brokera zamiast logiki idempotentnego konsumenta. Niektóre platformy filtrują duplikaty na warstwie transportowej. Na przykład funkcja Service Bus wykrywania duplikatów odrzuca komunikaty, które powtarzają ten sam
MessageIdw skonfigurowanym przedziale czasu, co zapobiega duplikowaniu komunikatów przy ponownych próbach ich wysłania przez producenta.Ta funkcja działa po stronie wysyłania i w ograniczonym oknie, więc nie uniemożliwia konsumentowi dwukrotnego przetwarzania tego samego komunikatu po ponownej instalacji. Nadal potrzebujesz idempotentnej logiki konsumenta. Użyj możliwości platformy, aby zmniejszyć liczbę duplikatów, a nie traktuj ich jako zamiennika wzorca konsumenta idempotentnego.
Konto do zamawiania komunikatów. Deduplikacja usuwa duplikaty, ale nie gwarantuje kolejności. Jeśli dla konsumenta istotna jest kolejność przetwarzania, połącz ten wzorzec z mechanizmem zapewniającym kolejność, takim jak sesje komunikatów usługi Service Bus message sessions, lub dołącz informacje o sekwencji lub wersji, aby konsument mógł odrzucać nieaktualne komunikaty.
Instrument do obserwacji. Emituj klucz deduplikacji i identyfikator korelacji w dziennikach strukturalnych oraz śledź metrykę dla wykrytych duplikatów. Rosnąca liczba duplikatów może wskazywać na błędną konfigurację producenta, niedostateczne potwierdzenie lub okno blokady albo niezdrowych odbiorców. Użyj śledzenia rozproszonego i korelacji , aby śledzić komunikat między usługami.
Propagacja idempotencji do wywołań podrzędnych. Nadanie konsumentowi komunikatów cechy idempotentności nie chroni usług, które wywołuje. Gdy użytkownik wywołuje usługi podrzędne w ramach przetwarzania, propaguje klucz idempotentności, aby każda warstwa usługi mogła deduplikować własną pracę.
Kiedy należy używać tego wzorca
Użyj tego wzorca, gdy:
Odbierasz komunikaty od brokera, który zapewnia dostarczenie co najmniej raz, co jest domyślne dla większości brokerów.
Ponowne przetwarzanie komunikatu może spowodować wygenerowanie nieprawidłowych wyników, takich jak zduplikowane transakcje finansowe, zduplikowane tworzenie zasobów lub powtarzające się powiadomienia.
Wielu konkurencyjnych odbiorców przetwarza ten sam kanał, co sprawia, że współbieżne zduplikowane dostarczanie jest bardziej prawdopodobne.
Ten wzorzec może nie być odpowiedni w następujących przypadkach:
Operacje wykonywane przez konsumenta są już z natury idempotentne, więc ponowne przetwarzanie nie powoduje szkód, a prowadzenie ewidencji deduplikacji generuje koszty bez żadnych korzyści.
Obciążenie robocze może tolerować skutki sporadycznego zduplikowanego przetwarzania, a koszt magazynu do deduplikacji przewyższa skutki wystąpienia duplikatu.
Przetwarzanie idempotentne poza komunikatami
Ten wzorzec stosuje idempotentność do odbiorców komunikatów, ale przetwarzanie idempotentne jest szerszą zasadą niezawodności, która może przynieść korzyści każdej operacji uruchamianej więcej niż raz w ramach identycznego zadania. Ta zasada obejmuje transformacje ETL (extract, transform, load), które ponownie przetwarzają odtwarzane dane, przetwarzanie strumieniowe wznawiane od punktu kontrolnego, zaplanowane zadania, które nakładają się na siebie lub są uruchamiane ponownie, oraz punkty końcowe webhooków lub HTTP, które odbierają zduplikowane żądania.
Ta sama podstawowa technika ma zastosowanie w każdym przypadku.
- Użyj stabilnego klucza, aby zidentyfikować jednostkę pracy.
- Zarejestruj proces.
- Pomijaj lub scalaj zduplikowane uruchomienia, aby ponowne wykonanie operacji nie zmieniało wyniku.
Mechanizmy w tym artykule, takie jak stabilne klucze, znaczniki atomowe i ograniczenia unikatowości, są przenoszone do tych kontekstów nawet wtedy, gdy broker komunikatów nie jest zaangażowany.
Projektowanie obciążenia pracy
Oceń, jak zastosować wzorzec Idempotent Consumer w projekcie obciążenia roboczego, aby uwzględnić cele i zasady omówione w filarach platformy Azure Well-Architected Framework. Poniższa tabela zawiera wskazówki dotyczące tego, jak ten wzorzec obsługuje cele poszczególnych filarów.
| Filar | Jak ten wzorzec obsługuje cele filaru |
|---|---|
| Decyzje projektowe dotyczące niezawodności pomagają obciążeniom stały się odporne na awarię i zapewniają, że zostanie ono przywrócone do w pełni funkcjonalnego stanu po wystąpieniu awarii. | Ten wzorzec pozwala obciążeniu używać co najmniej jednokrotnego dostarczania i bezpiecznych ponownych prób bez uszkodzenia danych, co przekształca zduplikowane dostarczanie z ryzyka poprawności na tolerowany warunek. - RE:07 Instynkt samozachowawczy - Błędy przejściowe |
Jeśli ten wzorzec wprowadza kompromisy w ramach filaru, rozważ je przed celami innych filarów.
Example
W poniższym przykładzie opisano idempotentnego konsumenta, który przetwarza zamówienia z usługi Service Bus i zapisuje stan w usłudze Azure Cosmos DB for NoSQL.
- Producent ustawia Service Bus
MessageIdna identyfikator zamówienia na poziomie biznesowym. - Użytkownik otrzymuje komunikat w trybie PeekLock , który udostępnia komunikat do ponownego dostarczenia, jeśli użytkownik nie rozstrzygnie go przed wygaśnięciem blokady.
- Kontener Azure Cosmos DB konsumenta jest partycjonowany według identyfikatora zamówienia
/orderIdi ustawia w dokumencie poleidna ten sam identyfikator zamówienia, dzięki czemu każda kopia danego zamówienia trafia do tej samej partycji logicznej, a samo zamówienieidsłuży jako znacznik deduplikacji.
Użytkownik wykonuje następujące kroki, aby przetworzyć każdy komunikat:
- Przeczytaj komunikat i użyj go
MessageIdjako klucza deduplikacji. - Spróbuj utworzyć dokument zamówienia, ustawiając zarówno
id, jak i klucz partycji na identyfikator zamówienia. - Jeśli operacja tworzenia zakończy się powodzeniem, oznacz komunikat jako ukończony, aby usługa Service Bus usunęła go z kolejki.
- Jeśli tworzenie zakończy się niepowodzeniem z kodem stanu HTTP 409 (Conflict), ponieważ dokument z tym
idjuż istnieje, odczytaj istniejący dokument i porównaj go z bieżącym komunikatem. - Jeśli zapisany skrót żądania lub niezmienne pola biznesowe są zgodne, potraktuj komunikat jako duplikat, oznacz go jako przetworzony i pomiń dalsze przetwarzanie.
- Jeśli zapisany skrót żądania lub niezmienne pola biznesowe nie zgadzają się, wyślij wiadomość do kolejki martwych komunikatów i wygeneruj alert, zamiast po cichu odrzucać komunikat. Producent mógł ponownie użyć identyfikatora dla innej zawartości lub szczegóły wiadomości mogły ulec zmianie od czasu pierwszego przetworzenia zamówienia.
- Jeśli przetwarzanie zakończy się niepowodzeniem z przyczyn przejściowych, porzuć komunikat, aby Service Bus dostarczył go ponownie, albo pozwól blokadzie wygasnąć, aby inny konsument mógł go odebrać.
Operacja tworzenia jest atomowa, więc służy zarówno jako sprawdzenie deduplikacji, jak i operacja zapisu. Dwóch odbiorców, którzy otrzymują kopie tej samej wiadomości, nie mogą utworzyć zamówienia. Jedna próba utworzenia zakończy się pomyślnie, a druga próba zwróci konflikt i bezpiecznie odrzuci jego duplikat.
Jeśli w ramach przetwarzania trzeba zapisać więcej niż jeden dokument, użyj partii transakcyjnej, która obejmuje zarówno klucz deduplikacji, jak i dokumenty biznesowe w obrębie tego samego klucza partycji. Ponieważ partia transakcyjna działa w obrębie jednej partycji logicznej, należy wybrać taki klucz partycji, który jest wspólny dla wszystkich dokumentów związanych z jednym komunikatem. W ramach partii zatwierdzane są albo wszystkie dokumenty naraz, albo żaden, więc awaria między przetworzeniem a potwierdzeniem nie może spowodować utraty spójności między znacznikiem deduplikacji a danymi biznesowymi. Partia próbująca utworzyć dokument, który już istnieje, zwraca kod stanu 409 (Conflict), co pozwala zidentyfikować duplikat.
Aby ten konsument idempotentny był również odporny na ponowne próby wysłania duplikatów, włącz w kolejce funkcję wykrywania duplikatów. W kolejce typu Standard lub Premium wykrywanie duplikatów blokuje ponowne wysyłanie w ramach swojego okna historii. Konsument idempotentny nadal obsługuje wszelkie duplikaty, które wykraczają poza to okno lub wynikają z ponownego dostarczenia.
Następny krok
- Projektowanie Azure Functions dla identycznych danych wejściowych zawiera wskazówki dotyczące tworzenia funkcji idempotentnych, które tolerują zduplikowane wywołania.
Powiązane zasoby
Opcje komunikacji asynchronicznej na platformie Azure opisuje wybory dotyczące infrastruktury obsługi komunikatów, które określają gwarancje dostarczenia i wymagania dotyczące obsługi duplikatów.
Wzorzec transakcyjnej skrzynki nadawczej, który niezawodnie publikuje komunikaty przez zatwierdzanie ich w tej samej transakcji co dane biznesowe, jest odpowiednikiem wzorca konsumenta idempotentnego po stronie producenta.
Wzorzec Retry umożliwia aplikacjom obsługę błędów przejściowych poprzez ponawianie operacji, co sprawia, że konieczne jest przetwarzanie idempotentne, ponieważ ponowienia mogą prowadzić do wielokrotnego dostarczenia.
Niezawodna architektura usługi Azure Event Hubs i Azure Functions wykorzystuje ten wzorzec w przypadku funkcji wyzwalanych przez usługę Event Hubs, w tym techniki deduplikacji dla strumieni zdarzeń.