Wzorzec konkurujących odbiorców

Azure Functions
Azure Service Bus

Umożliwianie wielu równoczesnym odbiorcom przetwarzania komunikatów odebranych w tym samym kanale obsługi komunikatów. W przypadku wielu równoczesnych użytkowników system może przetwarzać wiele komunikatów jednocześnie, aby zoptymalizować przepływność, zwiększyć skalowalność i dostępność oraz zrównoważyć obciążenie.

Kontekst i problem

Aplikacja działająca w chmurze powinna obsługiwać dużą liczbę żądań. Zamiast przetwarzania synchronicznego poszczególnych żądań zwykle stosuje się technikę, w której aplikacja przekazuje żądania za pośrednictwem systemu obsługi komunikatów do innej usługi (usługi konsumenta), która je obsługuje asynchronicznie. Ta strategia pomaga zapewnić, że logika biznesowa w aplikacji nie jest blokowana, podczas gdy żądania są przetwarzane.

Liczba żądań może się znacznie zmieniać w czasie z wielu powodów. Gwałtowny wzrost aktywności użytkownika lub przesyłanie zagregowanych żądań z wielu dzierżawców może spowodować nieprzewidywalne zmiany obciążenia. W godzinach szczytu system może wymagać przetwarzania wielu setek żądań na sekundę, podczas gdy w innych przypadkach liczba może być bardzo mała. Ponadto charakter pracy wykonywanej w celu obsłużenia tych żądań może być bardzo zmienny. Korzystając z pojedynczego wystąpienia usługi konsumenta, możesz spowodować, że to wystąpienie stanie się zalane żądaniami. Lub system obsługi komunikatów może być przeciążony przez napływ komunikatów pochodzących z aplikacji. Aby obsłużyć te wahania obciążenia, w systemie można uruchomić wiele wystąpień usługi konsumenta. Działania tych konsumentów muszą być jednak skoordynowane, aby upewnić się, że każdy komunikat jest dostarczany tylko do jednego konsumenta. Obciążenie musi być również równoważone między konsumentami, aby zapobiec powstawania wąskich gardeł w wystąpieniach.

Rozwiązanie

Używaj kolejki komunikatów do implementowania kanału komunikacji między aplikacją a wystąpieniami usługi klienta. Aplikacja publikuje żądania w kolejce w postaci komunikatów, a wystąpienia usługi konsumenta odbierają te komunikaty z kolejki i przetwarzają je. Dzięki takiemu podejściu jedna pula wystąpień usługi konsumenta może obsługiwać komunikaty z dowolnego wystąpienia aplikacji. Na rysunku przedstawiono sposób wykorzystania kolejki komunikatów do dostarczania pracy do wystąpień usługi.

Wykorzystanie kolejki komunikatów do dostarczania pracy do wystąpień usługi

Uwaga

Chociaż istnieje wielu odbiorców tych komunikatów, nie jest to takie samo jak wzorzec publikowania subskrypcji (pub/sub). W przypadku podejścia Konkurujący konsumenci każdy komunikat jest przekazywany do pojedynczego konsumenta do przetwarzania, podczas gdy w przypadku podejścia Pub/Sub wszyscy konsumenci otrzymują każdy komunikat.

To rozwiązanie ma następujące korzyści:

  • Udostępnia ono system wyrównywania obciążeń, który może obsługiwać duże wahania liczby żądań wysyłanych przez wystąpienia aplikacji. Kolejka pełni rolę buforu między wystąpieniami aplikacji a wystąpieniami usługi konsumenta. Ten bufor może pomóc zminimalizować wpływ na dostępność i czas odpowiedzi zarówno dla aplikacji, jak i wystąpień usługi. Aby uzyskać więcej informacji, zobacz Wzorzec bilansowania obciążenia opartego na kolejce. Obsługa komunikatu wymagającego długotrwałego przetwarzania nie uniemożliwia jednoczesnej obsługi innych komunikatów przez pozostałe wystąpienia usługi konsumenta.

  • Zwiększa niezawodność. Jeśli, zamiast korzystać z tego wzorca, producent komunikuje się bezpośrednio z konsumentem, ale go nie monitoruje, istnieje wysokie prawdopodobieństwo, że komunikaty mogą zostać utracone lub nie uda się ich przetworzyć, jeśli konsument ulegnie awarii. W przypadku tego wzorca komunikaty nie są wysyłane do konkretnego wystąpienia usługi. Zakończone niepowodzeniem wystąpienie usługi nie będzie blokować producenta, a komunikaty mogą być przetwarzane przez dowolne działające wystąpienie usługi.

  • Nie wymaga to złożonej koordynacji między konsumentami lub między wystąpieniami producenta i konsumenta. Kolejka komunikatów gwarantuje, że każdy komunikat zostanie dostarczony co najmniej raz.

  • Jest skalowalne. Podczas stosowania automatycznego skalowania system może dynamicznie zwiększać lub zmniejszać liczbę wystąpień usługi konsumenta w miarę wahań liczby komunikatów.

  • Może to zwiększyć odporność, jeśli kolejka komunikatów udostępnia transakcyjne operacje odczytu. Jeśli wystąpienie usługi konsumenta ulegnie awarii podczas odczytywania i przetwarzania komunikatu w ramach operacji transakcyjnej, ten wzorzec może zapewnić, że komunikat zostanie zwrócony do kolejki i będzie możliwy do pobrania i obsłużenia przez inne wystąpienie usługi konsumenta. Aby ograniczyć ryzyko ciągłego niepowodzenia komunikatu, zalecamy użycie kolejek utraconych komunikatów.

Problemy i kwestie do rozważenia

Podczas podejmowania decyzji o sposobie wdrożenia tego wzorca należy rozważyć następujące punkty:

  • Kolejność komunikatów. Kolejność, w jakiej wystąpienia usługi konsumenta odbierają komunikaty, nie jest gwarantowana i niekoniecznie odzwierciedla kolejność, w jakiej komunikaty zostały utworzone. Zaprojektuj system w taki sposób, aby zagwarantować idempotentne przetwarzanie komunikatów. Takie podejście ułatwi wyeliminowanie wszelkich zależności od kolejności, w jakiej komunikaty są obsługiwane. Aby uzyskać więcej informacji, zobacz blog Idempotency Patterns on Jonathon Oliver's blog (Wzorce idempotentności na blogu Jonathona Olivera).

    Za pomocą kolejek usługi Microsoft Azure Service Bus można zaimplementować gwarantowaną kolejność komunikatów „pierwszy na wejściu — pierwszy na wyjściu” dzięki wykorzystaniu sesji komunikatów. Aby uzyskać więcej informacji, zobacz Messaging Patterns Using Sessions (Wzorce obsługi komunikatów z użyciem sesji).

  • Projektowanie usług pod kątem odporności na awarie. Jeśli system został zaprojektowany do wykrywania i ponownego uruchamiania zakończonych niepowodzeniem wystąpień usług, może być konieczne zaimplementowanie przetwarzania wykonywanego przez wystąpienia usług jako operacji idempotentnych, aby zminimalizować wpływ wielokrotnego przetwarzania i pobierania pojedynczego komunikatu.

  • Wykrywanie skażonych komunikatów. Zniekształcony komunikat lub zadanie wymagające dostępu do zasobów, które nie są dostępne, może spowodować niepowodzenie wystąpienia usługi. System powinien zapobiegać zwracaniu takich komunikatów do kolejki i przechwytywać oraz zapisywać szczegóły takich komunikatów w innym miejscu, aby można było w razie potrzeby dokonać ich analizy.

  • Obsługa wyników. Wystąpienie usługi obsługujące komunikat jest całkowicie niezależne od logiki aplikacji, która generuje komunikat, i może się zdarzyć, że oba te elementy nie mogą się bezpośrednio komunikować. Jeśli wystąpienie usługi generuje wyniki, które muszą zostać przekazane z powrotem do logiki aplikacji, te informacje muszą być przechowywane w lokalizacji dostępnej dla obu tych elementów. Aby uniemożliwić pobranie niepełnych danych przez logikę aplikacji, system musi wskazywać, kiedy przetwarzanie zostało ukończone.

    W przypadku korzystania z platformy Azure proces roboczy może przekazywać wyniki z powrotem do logiki aplikacji za pomocą dedykowanej kolejki odpowiedzi komunikatów. Logika aplikacji musi mieć możliwość skorelowania tych wyników z oryginalnym komunikatem. Ten scenariusz jest opisany bardziej szczegółowo w artykule Asynchronous Messaging Primer (Podstawy asynchronicznej obsługi komunikatów).

  • Skalowanie systemu obsługi komunikatów. W rozwiązaniu wielkoskalowym pojedyncza kolejka komunikatów może zostać przeciążona przez dużą liczbę komunikatów i stać się wąskim gardłem w systemie. W takiej sytuacji należy rozważyć możliwość podzielenia systemu obsługi komunikatów w taki sposób, aby komunikaty z konkretnego producenta były wysyłane do określonej kolejki. Można też użyć równoważenia obciążenia, aby dystrybuować komunikaty do wielu kolejek komunikatów.

  • Zapewnienie niezawodności systemu obsługi komunikatów. Aby zagwarantować, że po umieszczeniu przez aplikację komunikatu w kolejce nie zostanie on utracony, należy użyć niezawodnego systemu obsługi komunikatów. Ten system jest niezbędny do zapewnienia, że wszystkie komunikaty są dostarczane co najmniej raz.

Kiedy używać tego wzorca

Użyj tego wzorca, gdy:

  • Obciążenie aplikacji jest podzielone na zadania, które mogą być uruchamiane asynchronicznie.
  • Zadania są niezależne i mogą być uruchamiane równolegle.
  • Ilość pracy jest wysoce zmienna, co wymaga użycia skalowalnego rozwiązania.
  • Rozwiązanie musi zapewnić wysoką dostępność i musi być odporne na niepowodzenia w przetwarzaniu zadań.

Ten wzorzec może nie być przydatny w następujących sytuacjach:

  • Nie jest łatwe rozdzielenie obciążenia aplikacji na osobne zadania lub między zadaniami występuje wysoki stopień zależności.
  • Zadania muszą być wykonywane synchronicznie, a logika aplikacji przed kontynuowaniem musi poczekać na zakończenie zadania.
  • Zadania muszą być wykonywane w określonej kolejności.

Niektóre systemy obsługi komunikatów obsługują sesje, które umożliwiają producentowi grupowanie komunikatów i zapewniają obsługę wszystkich komunikatów przez tego samego konsumenta. Tego mechanizmu można użyć razem z komunikatami z ustawionym priorytetem (jeśli są obsługiwane) do zaimplementowania funkcji porządkowania komunikatów, która dostarcza komunikaty od producenta do pojedynczego konsumenta w określonej kolejności.

Projekt obciążenia

Architekt powinien ocenić, w jaki sposób wzorzec konkurujących konsumentów może być używany w projekcie obciążenia, aby sprostać celom i zasadom opisanym w filarach platformy Azure Well-Architected Framework. Na przykład:

Filar Jak ten wzorzec obsługuje cele filaru
Decyzje projektowe dotyczące niezawodności pomagają obciążeniu stać się odporne na awarię i zapewnić, że zostanie przywrócony do w pełni funkcjonalnego stanu po wystąpieniu awarii. Ten wzorzec tworzy nadmiarowość w przetwarzaniu kolejek, traktując użytkowników jako repliki, więc awaria wystąpienia nie uniemożliwia innym użytkownikom przetwarzania komunikatów w kolejce.

- Nadmiarowość RE:05
- RE:07 Zadania w tle
Optymalizacja kosztów koncentruje się na utrzymaniu i poprawie zwrotu obciążenia z inwestycji. Ten wzorzec może pomóc zoptymalizować koszty, włączając skalowanie oparte na głębokości kolejki, do zera, gdy kolejka jest pusta. Może również zoptymalizować koszty, umożliwiając ograniczenie maksymalnej liczby współbieżnych wystąpień konsumentów.

- CO:05 Optymalizacja szybkości
- KOSZT SKŁADNIKA CO:07
Wydajność pomagawydajnie sprostać zapotrzebowaniu dzięki optymalizacjom skalowania, danych, kodu. Dystrybucja obciążenia we wszystkich węzłach odbiorców zwiększa wykorzystanie i dynamiczne skalowanie na podstawie głębokości kolejki minimalizuje nadmierną aprowizację.

- PE:05 Skalowanie i partycjonowanie
- PE:07 Kod i infrastruktura

Podobnie jak w przypadku każdej decyzji projektowej, należy rozważyć wszelkie kompromisy w stosunku do celów innych filarów, które mogą zostać wprowadzone przy użyciu tego wzorca.

Przykład

Platforma Azure udostępnia kolejki usługi Service Bus i wyzwalacze kolejki funkcji platformy Azure, które w połączeniu są bezpośrednią implementacją tego wzorca projektowania chmury. Usługa Azure Functions integruje się z usługą Azure Service Bus za pośrednictwem wyzwalaczy i powiązań. Integracja z usługą Service Bus umożliwia tworzenie funkcji korzystających z komunikatów kolejek wysyłanych przez wydawców. Aplikacje do publikowania będą publikować komunikaty do kolejki, a użytkownicy zaimplementowani jako usługa Azure Functions mogą pobierać komunikaty z tej kolejki i obsługiwać je.

W przypadku odporności kolejka usługi Service Bus umożliwia użytkownikowi używanie PeekLock trybu podczas pobierania komunikatu z kolejki. Ten tryb nie powoduje usunięcia komunikatu, ale po prostu ukrywa go przed innymi użytkownikami. Środowisko uruchomieniowe usługi Azure Functions odbiera komunikat w trybie PeekLock, jeśli funkcja zakończy pomyślnie wywoła funkcję Complete w komunikacie lub może wywołać polecenie Abandon, jeśli funkcja zakończy się niepowodzeniem, a komunikat stanie się widoczny ponownie, co umożliwi innemu użytkownikowi pobranie go. Jeśli funkcja działa przez okres dłuższy niż limit czasu PeekLock, blokada zostanie automatycznie odnowiona, o ile funkcja jest uruchomiona.

Usługa Azure Functions może skalować w poziomie/w oparciu o głębokość kolejki, a wszystkie działają jako konkurujący odbiorcy kolejki. Jeśli tworzone są wiele wystąpień funkcji, wszystkie one konkurują przez niezależne ściąganie i przetwarzanie komunikatów.

Aby uzyskać szczegółowe informacje na temat używania kolejek usługi Azure Service Bus, zobacz Kolejki, tematy i subskrypcje usługi Service Bus.

Aby uzyskać informacje na temat usługi Azure Functions wyzwalanej przez kolejkę, zobacz Wyzwalacz usługi Azure Service Bus dla usługi Azure Functions.

Poniższy kod pokazuje, jak utworzyć nowy komunikat i wysłać go do kolejki usługi Service Bus przy użyciu ServiceBusClient wystąpienia.

private string serviceBusConnectionString = ...;
...

  public async Task SendMessagesAsync(CancellationToken  ct)
  {
   try
   {
    var msgNumber = 0;

    var serviceBusClient = new ServiceBusClient(serviceBusConnectionString);

    // create the sender
    ServiceBusSender sender = serviceBusClient.CreateSender("myqueue");

    while (!ct.IsCancellationRequested)
    {
     // Create a new message to send to the queue
     string messageBody = $"Message {msgNumber}";
     var message = new ServiceBusMessage(messageBody);

     // Write the body of the message to the console
     this._logger.LogInformation($"Sending message: {messageBody}");

     // Send the message to the queue
     await sender.SendMessageAsync(message);

     this._logger.LogInformation("Message successfully sent.");
     msgNumber++;
    }
   }
   catch (Exception exception)
   {
    this._logger.LogException(exception.Message);
   }
  }

Poniższy przykładowy kod przedstawia użytkownika napisanego jako funkcja platformy Azure w języku C#, która odczytuje metadane komunikatów i rejestruje komunikat kolejki usługi Service Bus. Zwróć uwagę, ServiceBusTrigger jak atrybut jest używany do powiązania go z kolejką usługi Service Bus.

[FunctionName("ProcessQueueMessage")]
public static void Run(
    [ServiceBusTrigger("myqueue", Connection = "ServiceBusConnectionString")]
    string myQueueItem,
    Int32 deliveryCount,
    DateTime enqueuedTimeUtc,
    string messageId,
    ILogger log)
{
    log.LogInformation($"C# ServiceBus queue trigger function consumed message: {myQueueItem}");
    log.LogInformation($"EnqueuedTimeUtc={enqueuedTimeUtc}");
    log.LogInformation($"DeliveryCount={deliveryCount}");
    log.LogInformation($"MessageId={messageId}");
}

Następne kroki

  • Asynchronous Messaging Primer (Podstawy asynchronicznej obsługi komunikatów). Kolejki komunikatów to mechanizm komunikacji asynchronicznej. Jeśli usługa konsumenta musi wysyłać odpowiedź do aplikacji, może być konieczne zaimplementowanie pewnego rodzaju obsługi odpowiedzi na komunikaty. W artykule Asynchronous Messaging Primer (Podstawy asynchronicznej obsługi komunikatów) podano informacje, jak zaimplementować obsługę komunikatów typu żądanie/odpowiedź, korzystając z kolejek komunikatów.

  • Autoscaling Guidance (Wskazówki dotyczące skalowania automatycznego). Istnieje możliwość uruchamiania i zatrzymywania wystąpień usługi konsumenta w odpowiedzi na zmiany długości kolejki, w której aplikacja umieszcza komunikaty. Skalowanie automatyczne może ułatwić utrzymanie przepływności w godzinach szczytu przetwarzania.

Podczas implementowania tego wzorca mogą być istotne następujące wzorce i wskazówki:

  • Wzorzec konsolidacji zasobów obliczeniowych. Istnieje możliwość skonsolidowania wielu wystąpień usługi konsumenta w jeden proces w celu ograniczenia wydatków i kosztów zarządzania. W artykule Wzorzec konsolidacji zasobów obliczeniowych opisano zalety i wady tego podejścia.

  • Wzorzec wyrównywania obciążeń przy użyciu kolejki. Zaimplementowanie kolejki komunikatów może zwiększyć odporność systemu, co umożliwi wystąpieniom usługi obsługę zmieniającej się w szerokim zakresie liczby żądań z wystąpień aplikacji. Kolejka komunikatów pełni rolę buforu, który równoważy obciążenia. Ten scenariusz został opisany bardziej szczegółowo w artykule Wzorzec wyrównywania obciążeń przy użyciu kolejki.