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, aby przetwarzanie tego samego komunikatu więcej niż raz miało taki sam efekt jak przetwarzanie go raz. Systemy obsługi komunikatów, które gwarantują co najmniej jednokrotne dostarczanie, mogą wielokrotnie dostarczać ten sam komunikat. Bez odporności na duplikaty ponowne przetwarzanie komunikatu może tworzyć zduplikowane rekordy, dwukrotnie obciążać klienta lub mieć inne niepożądane skutki.
Kontekst i problem
Aplikacje rozproszone często wymieniają zadania za pośrednictwem brokera komunikatów, zamiast wykonywać bezpośrednie, synchroniczne wywołania. Większość brokerów, w tym Azure Service Bus, Azure Event Hubs, Apache Kafka i RabbitMQ, zapewnia co najmniej jednokrotne dostarczanie. Ta gwarancja gwarantuje, że komunikat dociera do konsumenta nawet wtedy, gdy wystąpią błędy, ale oznacza to również, że broker może dostarczyć ten sam komunikat więcej niż raz.
Duplikaty wynikają z kilku źródeł:
Ponowne próby producenta
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 odbioru
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 w połowie przetwarzania
Użytkownik ukończy zapis bazy danych, ale ulega awarii, zanim potwierdzi komunikat. Gdy inna instancja podejmie ponownie dostarczoną wiadomość, ponawia zapis.
Dostarczenie dokładnie raz w systemie rozproszonym jest niemożliwe do zagwarantowania w praktyce. Nawet brokerzy, którzy deklarują semantykę „dokładnie raz”, gwarantują jedynie operacje, które bezpośrednio kontrolują, takie jak dostarczanie komunikatów odbiorcom lub zapisywanie danych z powrotem w brokerze. Nie mogą zagwarantować efektów ubocznych wykonywanych przez użytkowników w innych systemach. Docelowym rozwiązaniem nie jest eliminacja duplikatów dostarczeń. Chodzi o to, by konsument to tolerował. Po połączeniu co najmniej jednokrotnego dostarczania z konsumentem, który ignoruje duplikaty, można skutecznie osiągnąć dokładnie jednokrotne przetwarzanie.
Rozwiązanie
Uczyń odbiorcę idempotentnym, prowadząc rejestr przetworzonych wiadomości i pomijając każdą wiadomość, którą przetworzył już wcześniej. Konsument opiera tę decyzję na stabilnym identyfikatorze, który pozostaje niezmienny mimo ponownego dostarczenia, sprawdza trwały magazyn danych, aby ustalić, czy ten identyfikator został już przetworzony, a następnie albo przetwarza komunikat, albo odrzuca go jako duplikat.
W poniższych krokach opisano przepływ podstawowy:
- Przeczytaj komunikat i wyodrębnij jego klucz deduplikacji.
- Sprawdź magazyn deduplikatów pod kątem tego klucza.
- Jeśli klucz istnieje, traktuj komunikat jako duplikat. Potwierdź go i zatrzymaj, opcjonalnie zwracając wcześniej zarejestrowany wynik.
- Jeśli klucz nie istnieje, przetwórz komunikat i zapisz klucz w ramach jednej operacji atomowej, a następnie potwierdź komunikat.
Wybieranie stabilnego klucza deduplikacji
Klucz musi jednoznacznie i spójnie identyfikować logiczny komunikat przy każdym ponownym dostarczeniu. Użyj identyfikatora komunikatu przypisanego przez producenta lub klucza idempotentności na poziomie biznesowym, który identyfikuje konkretną operację logiczną, a nie wspólnego kontekstu korelacji, który może być współdzielony przez kilka komunikatów. W usłudze Azure Service Bus właściwość MessageId służy temu celowi, ponieważ jednoznacznie identyfikuje komunikat i jego treść. Nie używaj CorrelationId jako klucza, ponieważ grupuje powiązane komunikaty, takie 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 należy opierać się na identyfikatorach warstwy transportowej, które broker regeneruje przy ponownym dostarczeniu, ani na wartościach pochodzących z prób dostarczenia, ponieważ wartości te zmieniają się między duplikatami i uniemożliwiają ich wykrywanie. 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 zgodnie z założeniami przetwarza własną kopię komunikatu i musi niezależnie śledzić zakończenie przetwarzania tego komunikatu. Jeśli ci konsumenci współdzielą jeden magazyn deduplikacyjny, oprzyj klucz rekordu na kluczu złożonym z identyfikatora konsumenta i identyfikatora komunikatu. Magazyn, którego kluczem jest wyłącznie tożsamość komunikatu, pozwala pierwszemu konsumentowi zablokować przetwarzanie przez wszystkich pozostałych.
Wybieranie miejsca przechowywania przetworzonych kluczy
Dostępne są dwie typowe opcje:
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, gdy wiele typów komunikatów współużytkuje jeden 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 wiąże deduplikację ze strukturą danych biznesowych.
Zatwierdź znacznik i skutki uboczne atomowo
Schemat „najpierw sprawdź, potem przetwórz” ma okno awarii. Jeśli konsument przetwarza komunikat, a następnie w osobnym kroku zapisuje klucz, awaria między tymi dwiema operacjami powoduje, że skutki uboczne zostają zastosowane, ale klucz nie zostaje zapisany, więc przy następnym dostarczeniu komunikat jest przetwarzany ponownie.
Wyeliminuj to okno awarii, zapisując znacznik deduplikacji i biznesowe efekty uboczne w tej samej transakcji. Gdy oba elementy są zatwierdzane razem albo wcale, ponowne dostarczenie albo znajduje zatwierdzony znacznik i pomija przetwarzanie, albo nie znajduje żadnego znacznika, ponieważ transakcja została wycofana, i bezpiecznie przetwarza dane ponownie. Ten transakcyjny wariant 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 z wieloma rywalizującymi konsumentami, dwie instancje mogą jednocześnie otrzymać kopie tego samego komunikatu. Oba procesy mogą przejść sprawdzenie istnienia, zanim którykolwiek z nich zatwierdzi zmiany, więc samo sprawdzenie nie zapobiega podwójnemu przetwarzaniu.
Wymuś poprawność w magazynie danych zamiast w logice aplikacji:
Użyj unikatowego ograniczenia klucza deduplikacji. Obie transakcje próbują wstawić klucz, ale tylko jednej się to uda. Druga nie spełnia ograniczenia i traktuje wiadomość jako duplikat. Takie podejście sprawia, że baza danych jest pojedynczym arbiterem wyścigu.
Unikaj warunków wyścigu typu „sprawdź, a potem ustaw” w pamięciach podręcznych. Wzorzec, który sprawdza klucz, a następnie ustawia go w dwóch oddzielnych operacjach, ma okno, które umożliwia równoczesne ponawianie próby obu oświadczeń klucza. Użyj atomowego zapisu warunkowego, takiego jak wstawienie, które kończy się niepowodzeniem w razie konfliktu, lub operacja ustawienia tylko wtedy, gdy wartość nie istnieje, tak 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ć podejścia dwufazowego:
- Zapisz klucz ze stanem w trakcie, zanim wykonasz działanie zewnętrzne.
- Wykonaj proces.
- Zaktualizuj rekord do ukończenia i zapisz wynik.
W przypadku ponownego dostarczenia ukończony rekord umożliwia pominięcie powtarzania wywołania. Rekord w toku sygnalizuje, że poprzednia próba mogła zostać częściowo ukończona lub jest przetwarzana przez innego konsumenta.
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ą księgowania deduplikacji. Operacja upsert oparta na identyfikatorze biznesowym, operacja zapisu, która ustawia wartość bezwzględną zamiast wartości przyrostowej, lub żądanie HTTP
PUTdo identyfikatora zasobu daje ten sam wynik niezależnie od tego, czy zostanie wykonana raz, czy wiele razy.Czasami można sprawić, by operacja była z natury idempotentna poprzez przenoszenie stanu w zdarzeniu, gdzie komunikat zawiera wynikowy stan docelowy, taki jak nowy status zamówienia, dzięki czemu odbiorca przetwarza go jako operację upsert zamiast zmiany względnej.
Tip
Najpierw projektuj operacje tak, aby były naturalnie idempotentne, a techniki deduplikacji stosuj tylko w przypadku operacji, których nie da się uczynić naturalnie idempotentnymi.
Zarządzaj cyklem życia rekordów deduplikacji. Rekordy deduplikacji gromadzą się, chyba że ustawisz dla nich wygaśnięcie. Zachowaj każdy rekord co najmniej tak długo, jak broker może ponownie przekazać oryginalną wiadomość. To okno zależy od maksymalnej liczby prób dostarczenia brokera, limitu czasu blokady lub widoczności oraz czasu życia komunikatu. Ustaw czas życia rekordów deduplikacji na wartość dłuższą niż to okno, aby 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 operator ponownie wysyła z kolejki komunikatów niedostarczonych, ponieważ takie ponowne wysłanie może nastąpić długo po upływie standardowego okresu ponownego dostarczenia.
Użyj frameworka do obsługi komunikatów zamiast samodzielnie implementować deduplikację. Poprawne zaimplementowanie magazynu usuwania duplikatów, atomowego zatwierdzania i czyszczenia rekordów jest obarczone dużym ryzykiem błędów. Struktury oparte na komunikatach zapewniają ten wzorzec jako wbudowaną funkcję.
Na przykład funkcja deduplikacji NServiceBus deduplikuje komunikaty przychodzące według ich identyfikatora komunikatu i zapewnia konfigurowalne przechowywanie i czyszczenie danych deduplikacji. Skrzynka odbiorcza massTransit śledzi komunikaty odebrane przez ich identyfikator komunikatu, aby zapewnić dokładnie jednokrotne zachowanie użytkownika.
Deduplikacja po stronie brokera zmniejsza, ale nie eliminuje potrzeby stosowania idempotentnej logiki konsumenta. Niektóre platformy filtrują duplikaty na warstwie transportowej. Azure Service Bus wykrywanie duplikatów odrzuca komunikaty, które zawierają powtórzony identyfikator
MessageIdw skonfigurowanym przedziale czasu, co eliminuje duplikaty spowodowane ponownymi próbami wysłania przez producenta. Ta funkcja działa po stronie nadawcy i w ramach ograniczonego okna. Nie zapobiega to temu, że konsument po ponownym dostarczeniu przetworzy tę samą wiadomość dwa razy, więc nadal potrzebna jest logika idempotentności po stronie konsumenta. Traktuj funkcje platformy jako pierwszą warstwę obrony, która zmniejsza liczbę duplikatów, a nie jako zamiennik tego wzorca.Konto do zamawiania komunikatów. Deduplikacja usuwa duplikaty, ale nie gwarantuje kolejności. Jeśli działanie odbiorcy zależy od kolejności przetwarzania, połącz ten wzorzec z mechanizmem zapewniającym kolejność, takim jak Azure Service Bus sesje komunikatów, lub dołącz dane sekwencji albo wersji, które pozwolą odbiorcy 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 i korelacji od końca do końca, aby śledzić komunikat w różnych usługach.
Propagacja idempotencji do wywołań podrzędnych. Uczynienie jednego konsumenta idempotentnym nie chroni usług, które wywołuje. Gdy użytkownik wywołuje usługi podrzędne w ramach przetwarzania, propaguje klucz idempotencji, aby każda warstwa 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 powoduje wygenerowanie nieprawidłowych wyników, takich jak zduplikowane transakcje finansowe, zduplikowane tworzenie zasobów lub powtarzające się powiadomienia.
Wielu konkurujących konsumentów przetwarza ten sam kanał, co sprawia, że prawdopodobne jest równoczesne wielokrotne dostarczenie tego samego komunikatu.
Ten wzorzec może nie być odpowiedni w następujących przypadkach:
Każda operacja wykonywana przez konsumenta jest już z natury idempotentna, więc ponowne przetwarzanie jest nieszkodliwe, a ewidencjonowanie na potrzeby deduplikacji generuje koszty, nie przynosząc żadnych korzyści.
Obciążenie robocze może tolerować skutki sporadycznego powtórnego 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. Każda operacja, która może być uruchamiana więcej niż raz w ramach identycznego zadania, korzysta z niego. 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.
W każdym przypadku ta sama podstawowa technika ma zastosowanie:
- Zidentyfikuj jednostkę pracy ze stabilnym kluczem.
- Zarejestruj, co już przetworzyłeś.
- Pomiń lub absorbuj duplikaty, aby powtarzanie pracy nie zmieniało wyniku.
Mechanizmy w tym artykule, takie jak stabilne klucze, znaczniki atomowe i unikatowe ograniczenia, 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 raz dostarczania i bezpiecznych ponownych prób bez uszkodzenia danych, co zamienia zduplikowane dostarczanie z ryzyka poprawności na tolerowany warunek. - RE:07 Instynkt samozachowawczy - Obsługa błędów przejściowych |
Jeśli ten wzorzec wprowadza kompromisy w ramach filaru, rozważ je przed celami innych filarów.
Example
W poniższym przykładzie przedstawiono konsumenta idempotentnego, który przetwarza zamówienia z Azure Service Bus i zapisuje stan w Azure Cosmos DB for NoSQL.
Producent ustawia Service Bus MessageId na identyfikator zamówienia na poziomie biznesowym. Odbiorca odbiera komunikaty w trybie PeekLock, który ponownie dostarcza komunikat, jeśli odbiorca nie ukończy go w czasie trwania blokady. Kontener Azure Cosmos DB konsumenta jest partycjonowany według identyfikatora zamówienia (/orderId) i ustawia w dokumencie id tę samą wartość identyfikatora zamówienia, dzięki czemu każda kopia danego zamówienia trafia do tej samej partycji logicznej, a sam rekord zamówienia służy jako znacznik deduplikacji.
Odbiorca przetwarza każdy komunikat w następujący sposób:
- Przeczytaj komunikat i użyj go
MessageIdjako klucza deduplikacji. - Utwórz dokument zamówienia, ustawiając zarówno
id, jak i klucz partycjonowania na identyfikator zamówienia. - Jeśli operacja tworzenia zakończy się pomyślnie, 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, zakończ jego obsługę i pomiń przetwarzanie. Jeśli się nie zgadzają, producent mógł ponownie użyć identyfikatora dla innych treści albo szczegóły zamówienia mogły ulec zmianie od momentu pierwszego przetworzenia, więc zamiast po cichu odrzucać komunikat, należy przenieść go do kolejki wiadomości martwych lub wywołać alert. - Jeśli przetwarzanie zakończy się niepowodzeniem z przyczyn przejściowych, porzuć komunikat, aby usługa Service Bus dostarczyła go ponownie, albo pozwól blokadzie wygasnąć, aby odebrał go inny odbiorca.
Operacja tworzenia jest atomowa, dlatego służy zarówno do sprawdzenia pod kątem duplikatów, jak i jako operacja zapisu. Dwóch odbiorców, którzy otrzymują kopie tej samej wiadomości, nie mogą utworzyć zamówienia. Jedna z nich wygrywa, a druga zwraca konflikt i bezpiecznie odrzuca jego duplikaty.
Gdy podczas przetwarzania trzeba zapisać więcej niż jeden dokument, użyj partii transakcyjnej, która obejmuje zarówno dokument deduplikacji, jak i dokumenty biznesowe w obrębie tego samego klucza partycji. Ponieważ partia transakcyjna działa w obrębie jednej partycji logicznej, wybierz klucz partycji współdzielony przez wszystkie dokumenty jednej wiadomości. Partia zatwierdza wszystkie dokumenty razem albo żadnego, więc awaria między przetwarzaniem a potwierdzeniem nie może sprawić, że znacznik deduplikacji i dane aplikacyjne utracą synchronizację. Partia próbująca utworzyć dokument, który już istnieje, zwraca kod stanu 409 (Conflict), co pozwala zidentyfikować duplikat.
Aby ten konsument był odporny również na ponowione próby wysłania duplikatów, włącz dla kolejki funkcję wykrywania duplikatów. Wykrywanie duplikatów zapobiega wielokrotnemu wysyłaniu w ramach swojego okna historii, a konsument idempotentny obsługuje wszelkie duplikaty, które pojawią się poza tym oknem lub wynikają z ponownego dostarczenia.
Następny krok
- 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.
Powiązane zasoby
Wzorzec Transactional Outbox to część tego modelu po stronie wydawcy. Niezawodnie publikuje komunikaty, zatwierdzając je w tej samej transakcji co dane biznesowe.
Wzorzec ponawiania umożliwia aplikacjom obsługę błędów przejściowych poprzez ponawianie operacji, co sprawia, że przetwarzanie idempotentne jest konieczne, ponieważ ponowienia mogą powodować wielokrotne dostarczenie.
Odporna architektura usługi Azure Event Hubs i Azure Functions stosuje ten wzorzec do funkcji wyzwalanych przez usługę Azure Event Hubs, w tym techniki deduplikacji dla strumieni zdarzeń.
Projektowanie Azure Functions dla identycznych danych wejściowych zawiera wskazówki dotyczące tworzenia funkcji idempotentnych, które tolerują zduplikowane wywołania.