Wzorzec nowoczesnej aplikacji internetowej dla języka Java

Azure App Service
Azure Front Door
Azure Cache for Redis

W tym artykule pokazano, jak zaimplementować wzorzec nowoczesnej aplikacji internetowej. Wzorzec nowoczesnej aplikacji internetowej definiuje sposób modernizacji aplikacji internetowych w chmurze i wprowadzenie architektury zorientowanej na usługę. Wzorzec nowoczesnej aplikacji internetowej zawiera normatywne wskazówki dotyczące architektury, kodu i konfiguracji, które są zgodne z zasadami platformy Azure Well-Architected Framework i opiera się na wzorcu niezawodnej aplikacji internetowej.

Dlaczego warto używać wzorca nowoczesnej aplikacji internetowej?

Wzorzec nowoczesnej aplikacji internetowej ułatwia optymalizowanie obszarów wysokiego zapotrzebowania aplikacji internetowej. Oferuje szczegółowe wskazówki dotyczące oddzielenia tych obszarów, co umożliwia niezależne skalowanie na potrzeby optymalizacji kosztów. Takie podejście umożliwia przydzielanie dedykowanych zasobów do krytycznych składników, zwiększając ogólną wydajność. Oddzielenie usług separowalnych może zwiększyć niezawodność, zapobiegając spowolnieniu w jednej części aplikacji wpływania na inne i niezależnie włączać przechowywanie wersji poszczególnych składników aplikacji.

Jak zaimplementować wzorzec nowoczesnej aplikacji internetowej

Ten artykuł zawiera wskazówki dotyczące architektury, kodu i konfiguracji w celu zaimplementowania wzorca nowoczesnej aplikacji internetowej. Skorzystaj z poniższych linków, aby przejść do potrzebnych wskazówek:

  • Wskazówki dotyczące architektury: Dowiedz się, jak modularyzować składniki aplikacji internetowej i wybierać odpowiednie rozwiązania platformy jako usługi (PaaS).
  • Wskazówki dotyczące kodu: Zaimplementuj cztery wzorce projektowe, aby zoptymalizować składniki rozdzielone: rysowanie stranglera, bilansowanie obciążenia oparte na kolejce, konkurujący konsumenci i wzorce monitorowania punktu końcowego kondycji.
  • Wskazówki dotyczące konfiguracji: Konfigurowanie uwierzytelniania, autoryzacji, skalowania automatycznego i konteneryzacji dla składników rozdzielonych.

Napiwek

Logo usługi GitHubIstnieje implementacja referencyjna (przykładowa aplikacja) wzorca nowoczesnej aplikacji internetowej. Reprezentuje stan końcowy implementacji nowoczesnej aplikacji internetowej. Jest to aplikacja internetowa klasy produkcyjnej, która zawiera wszystkie aktualizacje kodu, architektury i konfiguracji omówione w tym artykule. Wdrażanie i używanie implementacji referencyjnej w celu kierowania implementacją wzorca nowoczesnej aplikacji internetowej.

Wskazówki dotyczące architektury

Wzorzec nowoczesnej aplikacji internetowej opiera się na wzorcu niezawodnej aplikacji internetowej. Implementacja wymaga kilku dodatkowych składników architektury. Potrzebujesz kolejki komunikatów, platformy kontenera, usługi magazynu i rejestru kontenerów (zobacz rysunek 1).

Diagram przedstawiający architekturę bazową wzorca nowoczesnej aplikacji internetowej.Rysunek 1. Podstawowe elementy architektury wzorca nowoczesnej aplikacji internetowej.

Aby uzyskać wyższy cel poziomu usług (SLO), możesz dodać drugi region do architektury aplikacji internetowej. Skonfiguruj moduł równoważenia obciążenia tak, aby kierował ruch do drugiego regionu w celu obsługi konfiguracji aktywne-aktywne lub aktywne-pasywne w zależności od potrzeb biznesowych. Oba regiony wymagają tych samych usług, z wyjątkiem jednego regionu, ma sieć wirtualną koncentratora, która nawiązuje połączenie. Wdrażanie topologii sieci piasty i szprych w celu scentralizowania i udostępniania zasobów, takich jak zapora sieciowa. Uzyskaj dostęp do repozytorium kontenerów za pośrednictwem sieci wirtualnej koncentratora. Jeśli masz maszyny wirtualne, dodaj hosta bastionu do sieci wirtualnej koncentratora, aby zarządzać nimi bezpiecznie (zobacz rysunek 2).

Diagram przedstawiający architekturę wzorca nowoczesnej aplikacji internetowej z topologią sieci piasty i szprych.Rysunek 2. Architektura wzorca nowoczesnej aplikacji internetowej z topologią sieci piasty i szprych.

Architektura rozdzielania

Aby zaimplementować wzorzec nowoczesnej aplikacji internetowej, należy rozdzielić istniejącą architekturę aplikacji internetowej. Oddzielenie architektury polega na podzieleniu aplikacji monolitycznej na mniejsze, niezależne usługi, z których każda odpowiada za określoną funkcję lub funkcjonalność. Ten proces obejmuje ocenę bieżącej aplikacji internetowej, zmodyfikowanie architektury i wyodrębnienie kodu aplikacji internetowej na platformę kontenera. Celem jest systematyczne identyfikowanie i wyodrębnianie usług aplikacji, które najbardziej korzystają z oddzielenia. Aby rozdzielić architekturę, wykonaj następujące zalecenia:

  • Identyfikowanie granic usług Stosowanie zasad projektowania opartych na domenie w celu identyfikowania powiązanych kontekstów w aplikacji monolitycznej. Każdy ograniczony kontekst reprezentuje granicę logiczną i może być kandydatem do oddzielnej usługi. Usługi, które reprezentują odrębne funkcje biznesowe i mają mniej zależności, są dobrymi kandydatami do oddzielenia.

  • Ocena korzyści z usługi. Skoncentruj się na usługach, które korzystają najbardziej z niezależnego skalowania. Na przykład zależność zewnętrzna, na przykład dostawca usługi poczty e-mail w aplikacji LOB, może wymagać większej izolacji od awarii. Rozważ usługi, które są poddawane częstym aktualizacjom lub zmianom. Oddzielenie tych usług umożliwia niezależne wdrażanie i zmniejsza ryzyko wpływu na inne części aplikacji.

  • Ocena możliwości technicznych. Sprawdź bieżącą architekturę, aby zidentyfikować ograniczenia techniczne i zależności, które mogą mieć wpływ na proces oddzielenia. Planowanie sposobu zarządzania danymi i udostępniania ich w usługach. Oddzielone usługi powinny zarządzać własnymi danymi i zminimalizować bezpośredni dostęp do bazy danych w granicach usługi.

  • Wdrażanie usług platformy Azure. Wybierz i wdróż usługi platformy Azure potrzebne do obsługi usługi aplikacji internetowej, którą chcesz wyodrębnić. Skorzystaj z poniższej sekcji Wybierz odpowiednie usługi platformy Azure, aby uzyskać wskazówki.

  • Rozdziel usługę aplikacji internetowej. Zdefiniuj jasne interfejsy i interfejsy API dla nowo wyodrębnionych usług aplikacji internetowych w celu interakcji z innymi częściami systemu. Zaprojektuj strategię zarządzania danymi, która umożliwia każdej usłudze zarządzanie własnymi danymi przy jednoczesnym zapewnieniu spójności i integralności. Aby użyć określonych strategii implementacji i wzorców projektowych podczas tego procesu wyodrębniania, zapoznaj się z sekcją Wskazówki dotyczące kodu.

  • Użyj niezależnego magazynu na potrzeby usług rozdzielonych. Każda usługa oddzielona powinna mieć własne magazyny danych, aby ułatwić przechowywanie wersji i wdrażanie. Na przykład implementacja referencyjna oddziela usługę poczty e-mail od aplikacji internetowej i eliminuje konieczność uzyskania dostępu do bazy danych przez usługę. Zamiast tego usługa komunikuje stan dostarczania wiadomości e-mail z powrotem do aplikacji internetowej za pośrednictwem komunikatu usługi Azure Service Bus, a aplikacja internetowa zapisuje notatkę w bazie danych.

  • Zaimplementuj oddzielne potoki wdrażania dla każdej usługi oddzielonej. Oddzielne potoki wdrażania umożliwiają aktualizowanie każdej usługi we własnym tempie. Jeśli różne zespoły lub organizacje w firmie posiadają różne usługi, posiadanie oddzielnych potoków wdrażania zapewnia każdemu zespołowi kontrolę nad własnymi wdrożeniami. Użyj narzędzi ciągłej integracji i ciągłego dostarczania (CI/CD), takich jak Jenkins, GitHub Actions lub Azure Pipelines, aby skonfigurować te potoki.

  • Popraw mechanizmy kontroli zabezpieczeń. Upewnij się, że mechanizmy kontroli zabezpieczeń są aktualizowane tak, aby uwzględniały nową architekturę, w tym reguły zapory i mechanizmy kontroli dostępu.

Wybieranie odpowiednich usług platformy Azure

W przypadku każdej usługi platformy Azure w architekturze zapoznaj się z odpowiednim przewodnikiem dotyczącym usługi platformy Azure w strukturze dobrze zaprojektowanej. W przypadku wzorca nowoczesnej aplikacji internetowej potrzebny jest system obsługi komunikatów asynchronicznych, platforma aplikacji obsługująca konteneryzację i repozytorium obrazów kontenerów.

  • Wybierz kolejkę komunikatów. Kolejka komunikatów jest ważnym elementem architektur zorientowanych na usługę. Umożliwia oddzielenie nadawców komunikatów i odbiorników w celu włączenia asynchronicznej obsługi komunikatów. Skorzystaj ze wskazówek dotyczących wybierania usługi obsługi komunikatów platformy Azure, aby wybrać system obsługi komunikatów platformy Azure, który obsługuje twoje potrzeby projektowe. Platforma Azure ma trzy usługi obsługi komunikatów: Azure Event Grid, Azure Event Hubs i Service Bus. Zacznij od usługi Service Bus jako domyślnego wyboru i użyj pozostałych dwóch opcji, jeśli usługa Service Bus nie spełnia Twoich potrzeb.

    Usługa Przypadek użycia
    Service Bus Wybierz usługę Service Bus, aby zapewnić niezawodne, uporządkowane i ewentualnie transakcyjne dostarczanie komunikatów o wysokiej wartości w aplikacjach dla przedsiębiorstw.
    Event Grid Wybierz usługę Event Grid, jeśli chcesz wydajnie obsługiwać dużą liczbę zdarzeń dyskretnych. Usługa Event Grid jest skalowalna w przypadku aplikacji opartych na zdarzeniach, w których wiele małych, niezależnych zdarzeń (takich jak zmiany stanu zasobu) musi być kierowanych do subskrybentów w modelu z małym opóźnieniem i modelem publikowania-subskrybowania.
    Event Hubs Wybierz usługę Event Hubs na potrzeby masowego pozyskiwania danych o wysokiej przepływności, takich jak dane telemetryczne, dzienniki lub analiza w czasie rzeczywistym. Usługa Event Hubs jest zoptymalizowana pod kątem scenariuszy przesyłania strumieniowego, w których dane zbiorcze muszą być pozyskiwane i przetwarzane w sposób ciągły.
  • Zaimplementuj usługę kontenera. W przypadku części aplikacji, które chcesz konteneryzować, potrzebujesz platformy aplikacji obsługującej kontenery. Skorzystaj ze wskazówek dotyczących wybierania usługi kontenera platformy Azure, aby pomóc w podjęciu decyzji. Platforma Azure ma trzy główne usługi kontenerów: Azure Container Apps, Azure Kubernetes Service (AKS) i aplikacja systemu Azure Service. Zacznij od usługi Container Apps jako domyślnego wyboru i użyj pozostałych dwóch opcji, jeśli usługa Container Apps nie spełnia Twoich potrzeb.

    Usługa Przypadek użycia
    Aplikacje kontenera Wybierz pozycję Container Apps, jeśli potrzebujesz bezserwerowej platformy, która automatycznie skaluje kontenery i zarządza nimi w aplikacjach opartych na zdarzeniach.
    AKS Wybierz usługę AKS, jeśli potrzebujesz szczegółowej kontroli nad konfiguracjami platformy Kubernetes i zaawansowanymi funkcjami skalowania, sieci i zabezpieczeń.
    Web Apps for Container Wybierz pozycję Web App for Containers w usłudze App Service, aby uzyskać najprostsze środowisko PaaS.
  • Zaimplementuj repozytorium kontenerów. W przypadku korzystania z dowolnej usługi obliczeniowej opartej na kontenerach należy mieć repozytorium do przechowywania obrazów kontenerów. Możesz użyć publicznego rejestru kontenerów, takiego jak Docker Hub lub zarządzanego rejestru, takiego jak Azure Container Registry. Skorzystaj z przewodnika Wprowadzenie do rejestrów kontenerów na platformie Azure, aby pomóc w podjęciu decyzji.

Wskazówki dotyczące kodu

Aby pomyślnie oddzielić i wyodrębnić niezależną usługę, należy zaktualizować kod aplikacji internetowej przy użyciu następujących wzorców projektowych: wzorzec ściągnięty, wzorzec bilansowania obciążenia opartego na kolejce, wzorzec konkurujących konsumentów, wzorzec monitorowania punktu końcowego kondycji i wzorzec ponawiania prób.

Diagram przedstawiający rolę wzorców projektowych w architekturze wzorca nowoczesnej aplikacji internetowej.Rysunek 3. Rola wzorców projektowych.

  1. Wzorzec fig stranglera: Wzorzec figowy strangler przyrostowo migruje funkcje z aplikacji monolitycznej do usługi oddzielonej. Zaimplementuj ten wzorzec w głównej aplikacji internetowej, aby stopniowo migrować funkcje do niezależnych usług, kierując ruch na podstawie punktów końcowych.

  2. Wzorzec bilansowania obciążenia opartego na kolejce: wzorzec bilansowania obciążenia opartego na kolejce zarządza przepływem komunikatów między producentem a odbiorcą przy użyciu kolejki jako buforu. Zaimplementuj ten wzorzec w części producenta usługi oddzielonej, aby zarządzać przepływem komunikatów asynchronicznie przy użyciu kolejki.

  3. Wzorzec konkurujących odbiorców: Wzorzec konkurujących odbiorców umożliwia niezależne odczytywanie z tej samej kolejki komunikatów wielu wystąpień usługi rozdzielanej i konkurowanie o przetwarzanie komunikatów. Zaimplementuj ten wzorzec w oddzielonej usłudze, aby dystrybuować zadania między wieloma wystąpieniami.

  4. Wzorzec monitorowania punktu końcowego kondycji: Wzorzec monitorowania punktu końcowego kondycji uwidacznia punkty końcowe na potrzeby monitorowania stanu i kondycji różnych części aplikacji internetowej. (4a) Zaimplementuj ten wzorzec w głównej aplikacji internetowej. (4b) Ponadto zaimplementuj ją w oddzielonej usłudze w celu śledzenia kondycji punktów końcowych.

  5. Wzorzec ponawiania: wzorzec ponawiania obsługuje błędy przejściowe, ponawiając próby operacji, które mogą sporadycznie zakończyć się niepowodzeniem. (5a) Zaimplementuj ten wzorzec dla wszystkich wywołań wychodzących do innych usług platformy Azure w głównej aplikacji internetowej, takich jak wywołania kolejki komunikatów i prywatnych punktów końcowych. (5b) Ponadto zaimplementuj ten wzorzec w oddzielonej usłudze, aby obsługiwać błędy przejściowe w wywołaniach prywatnych punktów końcowych.

Każdy wzorzec projektu zapewnia korzyści, które są zgodne z co najmniej jednym filarem struktury Well-Architected Framework (zobacz poniższą tabelę).

Wzorzec projektowania Lokalizacja implementacji Niezawodność (RE) Zabezpieczenia (SE) Optymalizacja kosztów (CO) Doskonałość operacyjna (OE) Wydajność (PE) Obsługa dobrze zaprojektowanych zasad struktury
Wzorzec figowy stranglera Główna aplikacja internetowa RE:08
CO:07
CO:08
OE:06
OE:11
Wzorzec wyrównywania obciążeń przy użyciu kolejki Producent usługi oddzielonej RE:06
RE:07
CO:12
PE:05
Wzorzec konkurujących odbiorców Odłączona usługa RE:05
RE:07
CO:05
CO:07
PE:05
PE:07
Wzorzec monitorowania punktu końcowego kondycji Główna aplikacja internetowa i odłączona usługa RE:07
RE:10
OE:07
PE:05
Wzorzec ponawiania prób Główna aplikacja internetowa i odłączona usługa RE:07

Implementowanie wzorca figowego stranglera

Użyj wzorca figowego stranglera, aby stopniowo migrować funkcje z monolitycznej bazy kodu do nowych niezależnych usług. Wyodrębnij nowe usługi z istniejącej bazy kodu monolitycznego i powoli modernizuj krytyczne części aplikacji internetowej. Aby zaimplementować wzorzec Fig stranglera, wykonaj następujące zalecenia:

  • Konfigurowanie warstwy routingu. W bazie kodu monolitycznej aplikacji internetowej zaimplementuj warstwę routingu, która kieruje ruch na podstawie punktów końcowych. Użyj niestandardowej logiki routingu zgodnie z potrzebami, aby obsługiwać określone reguły biznesowe na potrzeby kierowania ruchem. Jeśli na przykład masz /users punkt końcowy w aplikacji monolitycznej i przeniesiono te funkcje do usługi oddzielonej, warstwa routingu kieruje wszystkie żądania do /users nowej usługi.

  • Zarządzanie wdrażaniem funkcji. Zaimplementuj flagi funkcji i wdrożenie etapowe , aby stopniowo wdrażać oddzielone usługi. Istniejący routing aplikacji monolitycznych powinien kontrolować liczbę żądań odbieranych przez oddzielone usługi. Zacznij od niewielkiej liczby żądań i zwiększ użycie w miarę upływu czasu, gdy zyskujesz pewność co do jego stabilności i wydajności.

    Na przykład implementacja referencyjna wyodrębnia funkcję dostarczania wiadomości e-mail do autonomicznej usługi, która może być stopniowo wprowadzana w celu obsługi większej części żądań wysyłania wiadomości e-mail zawierających przewodniki pomocy technicznej firmy Contoso. Ponieważ nowa usługa udowodni swoją niezawodność i wydajność, może ostatecznie przejąć cały zestaw obowiązków związanych z pocztą e-mail od monolitu, kończąc przejście.

  • Użyj usługi fasady (w razie potrzeby). Usługa fasady jest przydatna, gdy pojedyncze żądanie musi wchodzić w interakcje z wieloma usługami lub gdy chcesz ukryć złożoność systemu bazowego od klienta. Jeśli jednak oddzielona usługa nie ma żadnych publicznych interfejsów API, usługa fasady może nie być konieczna.

    W monolitycznej bazie kodu aplikacji internetowej zaimplementuj usługę fasady w celu kierowania żądań do odpowiedniego zaplecza (monolitycznego lub mikrousługi). W nowej odłączonej usłudze upewnij się, że nowa usługa może obsługiwać żądania niezależnie po korzystaniu z fasady.

Implementowanie wzorca bilansowania obciążenia opartego na kolejce

Zaimplementuj wzorzec bilansowania obciążenia opartego na kolejce w części producenta usługi oddzielonej do asynchronicznego obsługi zadań, które nie wymagają natychmiastowych odpowiedzi. Ten wzorzec zwiększa ogólną szybkość reakcji i skalowalność systemu przy użyciu kolejki do zarządzania dystrybucją obciążeń. Umożliwia ona oddzielenie usługi do przetwarzania żądań z stałą szybkością. Aby skutecznie zaimplementować ten wzorzec, postępuj zgodnie z następującymi zaleceniami:

  • Użyj funkcji kolejkowania komunikatów nieblokujących. Upewnij się, że proces, który wysyła komunikaty do kolejki, nie blokuje innych procesów podczas oczekiwania na oddzielenie usługi do obsługi komunikatów w kolejce. Jeśli proces wymaga wyniku operacji odłączonej usługi, zaimplementuj alternatywny sposób obsługi sytuacji podczas oczekiwania na zakończenie operacji w kolejce. Na przykład w środowisku Spring Boot można użyć StreamBridge klasy do asynchronicznego publikowania komunikatów w kolejce bez blokowania wątku wywołującego (zobacz przykładowy kod):

    private final StreamBridge streamBridge;
    
    public SupportGuideQueueSender(StreamBridge streamBridge) {
        this.streamBridge = streamBridge;
    }
    
    // Asynchronously publish a message without blocking the calling thread
    @Override
    public void send(String to, String guideUrl, Long requestId) {
        EmailRequest emailRequest = EmailRequest.newBuilder()
                .setRequestId(requestId)
                .setEmailAddress(to)
                .setUrlToManual(guideUrl)
                .build();
    
        log.info("EmailRequest: {}", emailRequest);
    
        var message = emailRequest.toByteArray();
        streamBridge.send(EMAIL_REQUEST_QUEUE, message);
    
        log.info("Message sent to the queue");
    }
    

    W tym przykładzie języka Java użyto StreamBridge metody do asynchronicznego wysyłania komunikatów. Takie podejście zapewnia, że główna aplikacja pozostaje elastyczna i może obsługiwać inne zadania jednocześnie, podczas gdy oddzielona usługa przetwarza żądania w kolejce z możliwością zarządzania.

  • Zaimplementuj ponawianie i usuwanie komunikatów. Zaimplementuj mechanizm ponawiania próby przetworzenia komunikatów w kolejce, których nie można pomyślnie przetworzyć. Jeśli awarie będą się powtarzać, te komunikaty powinny zostać usunięte z kolejki. Na przykład usługa Service Bus ma wbudowane funkcje kolejki ponawiania prób i utraconych komunikatów.

  • Konfigurowanie przetwarzania komunikatów idempotentnych. Logika, która przetwarza komunikaty z kolejki, musi być idempotentna w celu obsługi przypadków, w których komunikat może być przetwarzany więcej niż raz. W środowisku Spring Boot można użyć @StreamListener lub @KafkaListener z unikatowym identyfikatorem komunikatu, aby zapobiec zduplikowanemu przetwarzaniu. Możesz też zorganizować proces biznesowy w taki sposób, aby działał w sposób funkcjonalny za pomocą usługi Spring Cloud Stream, gdzie consume metoda jest definiowana w sposób, który generuje ten sam wynik, gdy jest uruchamiany wielokrotnie. Przeczytaj artykuł Spring Cloud Stream with Service Bus (Usługa Spring Cloud Stream z usługą Service Bus ), aby uzyskać dalszą listę ustawień, które zarządzają zachowaniem sposobu korzystania z komunikatów.

  • Zarządzanie zmianami w środowisku. Przetwarzanie asynchroniczne może prowadzić do braku natychmiastowego ukończenia zadań. Użytkownicy powinni być świadomi, kiedy ich zadanie jest nadal przetwarzane w celu ustawienia prawidłowych oczekiwań i uniknięcia nieporozumień. Użyj wizualnych wskazówek lub komunikatów, aby wskazać, że zadanie jest w toku. Nadaj użytkownikom możliwość otrzymywania powiadomień po zakończeniu zadania, takiego jak wiadomość e-mail lub powiadomienie wypychane.

Implementowanie wzorca konkurujących odbiorców

Zaimplementuj wzorzec konkurujących odbiorców w oddzielonej usłudze, aby zarządzać zadaniami przychodzącymi z kolejki komunikatów. Ten wzorzec obejmuje dystrybucję zadań w wielu wystąpieniach usług oddzielonych. Te usługi przetwarzają komunikaty z kolejki, zwiększając równoważenie obciążenia i zwiększając pojemność systemu w celu obsługi równoczesnych żądań. Wzorzec konkurujących odbiorców jest skuteczny, gdy:

  • Sekwencja przetwarzania komunikatów nie ma kluczowego znaczenia.
  • Kolejka pozostaje nienaruszona przez źle sformułowane komunikaty.
  • Operacja przetwarzania jest idempotentna, co oznacza, że można ją stosować wiele razy bez zmiany wyniku poza początkową aplikacją.

Aby zaimplementować wzorzec konkurujących odbiorców, postępuj zgodnie z następującymi zaleceniami:

  • Obsługa współbieżnych komunikatów. Podczas odbierania komunikatów z kolejki upewnij się, że system jest skalowany przewidywalnie, konfigurując współbieżność w taki sposób, aby był zgodny z projektem systemu. Wyniki testu obciążeniowego ułatwiają podjęcie decyzji o odpowiedniej liczbie współbieżnych komunikatów do obsłużenia i można zacząć od jednego, aby zmierzyć sposób działania systemu.

  • Wyłącz pobieranie wstępne. Wyłącz wstępne pobieranie komunikatów, aby użytkownicy pobierali komunikaty tylko wtedy, gdy są gotowe.

  • Używaj niezawodnych trybów przetwarzania komunikatów. Użyj niezawodnego trybu przetwarzania, takiego jak PeekLock (lub jego odpowiednik), który automatycznie ponawia próby komunikatów, które kończą się niepowodzeniem. Ten tryb zwiększa niezawodność w przypadku metod najpierw usuwania. Jeśli jeden proces roboczy nie obsługuje komunikatu, inny musi być w stanie przetworzyć go bez błędów, nawet jeśli komunikat jest przetwarzany wiele razy.

  • Implementowanie obsługi błędów. Kierowanie nieprawidłowo sformułowanych lub nieprzetworzonych komunikatów do oddzielnej kolejki utraconych komunikatów. Ten projekt uniemożliwia powtarzalne przetwarzanie. Można na przykład przechwytywać wyjątki podczas przetwarzania komunikatów i przenosić problematyczny komunikat do oddzielnej kolejki. W przypadku usługi Service Bus komunikaty są przenoszone do kolejki dead-leter po określonej liczbie prób dostarczenia lub jawnym odrzuceniu przez aplikację.

  • Obsługa komunikatów poza kolejnością. Zaprojektuj użytkowników, aby przetwarzali komunikaty wychodzące z sekwencji. Wielu odbiorców równoległych oznacza, że mogą przetwarzać komunikaty poza kolejnością.

  • Skalowanie na podstawie długości kolejki. Usługi korzystające z komunikatów z kolejki powinny być skalowane automatycznie na podstawie długości kolejki. Skalowanie automatyczne oparte na skali umożliwia wydajne przetwarzanie skoków przychodzących komunikatów.

  • Użyj kolejki odpowiedzi z komunikatami. Jeśli system wymaga powiadomień dotyczących przetwarzania po komunikatach, skonfiguruj dedykowaną odpowiedź lub kolejkę odpowiedzi. Ta konfiguracja dzieli komunikaty operacyjne z procesów powiadomień.

  • Korzystanie z usług bezstanowych. Rozważ użycie usług bezstanowych do przetwarzania żądań z kolejki. Umożliwia łatwe skalowanie i efektywne użycie zasobów.

  • Konfigurowanie rejestrowania. Integrowanie rejestrowania i obsługi określonych wyjątków w przepływie pracy przetwarzania komunikatów. Skoncentruj się na przechwytywaniu błędów serializacji i kierowaniu tych problematycznych komunikatów do mechanizmu utraconych komunikatów. Te dzienniki zapewniają cenne szczegółowe informacje dotyczące rozwiązywania problemów.

Na przykład implementacja referencyjna używa wzorca konkurujących odbiorców w usłudze bezstanowej uruchomionej w usłudze Container Apps do przetwarzania żądań dostarczania wiadomości e-mail z kolejki usługi Service Bus.

Procesor rejestruje szczegóły przetwarzania komunikatów, ułatwiając rozwiązywanie problemów i monitorowanie. Przechwytuje błędy deserializacji i zapewnia szczegółowe informacje potrzebne podczas debugowania procesu. Usługa jest skalowana na poziomie kontenera, co pozwala na wydajną obsługę skoków komunikatów na podstawie długości kolejki (zobacz następujący kod).

@Configuration
public class EmailProcessor {

    private static final Logger log = LoggerFactory.getLogger(EmailProcessor.class);

    @Bean
    Function<byte[], byte[]> consume() {
        return message -> {

            log.info("New message received");

            try {
                EmailRequest emailRequest = EmailRequest.parseFrom(message);
                log.info("EmailRequest: {}", emailRequest);

                EmailResponse emailResponse = EmailResponse.newBuilder()
                        .setEmailAddress(emailRequest.getEmailAddress())
                        .setUrlToManual(emailRequest.getUrlToManual())
                        .setRequestId(emailRequest.getRequestId())
                        .setMessage("Email sent to " + emailRequest.getEmailAddress() + " with URL to manual " + emailRequest.getUrlToManual())
                        .setStatus(Status.SUCCESS)
                        .build();

                return emailResponse.toByteArray();

            } catch (InvalidProtocolBufferException e) {
                throw new RuntimeException("Error parsing email request message", e);
            }
        };
    }
}

Implementowanie wzorca monitorowania punktu końcowego kondycji

Zaimplementuj wzorzec monitorowania punktu końcowego kondycji w kodzie głównej aplikacji i odłączony kod usługi w celu śledzenia kondycji punktów końcowych aplikacji. Koordynatorzy, tacy jak AKS lub Container Apps, mogą sondować te punkty końcowe, aby zweryfikować kondycję usługi i ponownie uruchomić wystąpienia w złej kondycji. Platforma Spring Boot zapewnia wbudowaną obsługę kontroli kondycji za pomocą siłownika Spring Boot, która może uwidaczniać punkty końcowe sprawdzania kondycji dla kluczowych zależności, takich jak bazy danych, brokerzy komunikatów i systemy magazynowania. Aby zaimplementować wzorzec monitorowania punktu końcowego kondycji, wykonaj następujące zalecenia:

  • Implementowanie kontroli kondycji. Użyj siłownika Spring Boot, aby zapewnić punkty końcowe sprawdzania kondycji. Siłownik Spring Boot uwidacznia punkt końcowy /actuator/health , który zawiera wbudowane wskaźniki kondycji i niestandardowe kontrole różnych zależności. Aby włączyć punkt końcowy kondycji, dodaj spring-boot-starter-actuator zależność w pliku pom.xml lub build.gradle .

    <!-- Add Spring Boot Actuator dependency -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    

    Skonfiguruj punkt końcowy kondycji w programie application.properties , jak pokazano w implementacji referencyjnej: txt management.endpoints.web.exposure.include=metrics,health,info,retry,retryevents

  • Zweryfikuj zależności. Siłownik Spring Boot zawiera wskaźniki kondycji dla różnych zależności, takich jak bazy danych, brokerzy komunikatów (RabbitMQ lub Kafka) i usługi magazynowania. Aby zweryfikować dostępność usług platformy Azure, takich jak Azure Blob Storage lub Service Bus, użyj wtyczek społeczności, takich jak azure Spring Apps lub integracji mikrometrów, które zapewniają wskaźniki kondycji dla tych usług. Jeśli są potrzebne kontrole niestandardowe, można je zaimplementować, tworząc niestandardową HealthIndicator fasolę.

    import org.springframework.boot.actuate.health.Health;
    import org.springframework.boot.actuate.health.HealthIndicator;
    import org.springframework.stereotype.Component;
    
    @Component
    public class CustomAzureServiceBusHealthIndicator implements HealthIndicator {
        @Override
        public Health health() {
            // Implement your health check logic here (e.g., ping Service Bus)
            boolean isServiceBusHealthy = checkServiceBusHealth();
            return isServiceBusHealthy ? Health.up().build() : Health.down().build();
        }
    
        private boolean checkServiceBusHealth() {
            // Implement health check logic (pinging or connecting to the service)
            return true; // Placeholder, implement actual logic
        }
    }
    
  • Konfigurowanie zasobów platformy Azure. Skonfiguruj zasób platformy Azure, aby używać adresów URL kontroli kondycji aplikacji w celu potwierdzenia aktualności i gotowości. Możesz na przykład użyć narzędzia Terraform, aby użyć adresów URL kontroli kondycji w celu potwierdzenia aktualności i gotowości aplikacji wdrożonych w usłudze Container Apps. Aby uzyskać więcej informacji, zobacz Sondy kondycji w usłudze Container Apps.

Implementowanie wzorca ponawiania prób

Wzorzec ponawiania umożliwia aplikacjom odzyskiwanie po błędach przejściowych. Wzorzec ponawiania jest kluczowy dla wzorca Niezawodnej aplikacji internetowej, więc aplikacja internetowa powinna już używać wzorca ponawiania. Zastosuj wzorzec ponawiania prób do żądań do systemów obsługi komunikatów i żądań wystawionych przez oddzielone usługi wyodrębnione z aplikacji internetowej. Aby zaimplementować wzorzec ponawiania prób, wykonaj następujące zalecenia:

  • Skonfiguruj opcje ponawiania prób. Podczas integracji z kolejką komunikatów należy skonfigurować klienta odpowiedzialnego za interakcje z kolejką z odpowiednimi ustawieniami ponawiania prób. Określ parametry, takie jak maksymalna liczba ponownych prób, opóźnienie między ponowną próbą i maksymalnym opóźnieniem.

  • Użyj wycofywania wykładniczego. Zaimplementuj strategię wycofywania wykładniczego na potrzeby ponownych prób. Oznacza to zwiększenie czasu między poszczególnymi ponownymi próbami wykładniczo, co pomaga zmniejszyć obciążenie systemu w okresach wysokich współczynników awarii.

  • Użyj funkcji ponawiania prób zestawu SDK. W przypadku usług z wyspecjalizowanymi zestawami SDK, takimi jak Service Bus lub Blob Storage, użyj wbudowanych mechanizmów ponawiania prób. Wbudowane mechanizmy ponawiania prób są zoptymalizowane pod kątem typowych przypadków użycia usługi i mogą efektywniej obsługiwać ponawianie prób przy mniejszej wymaganej konfiguracji.

  • Wdrażanie standardowych bibliotek odporności dla klientów HTTP. W przypadku klientów HTTP można używać funkcji Resilience4* wraz z interfejsem RestTemplate platformy Spring lub elementem WebClient do obsługi ponownych prób w komunikacji HTTP. Interfejs RestTemplate platformy Spring można opakować za pomocą logiki ponawiania prób platformy Resilience4j w celu efektywnego obsługi przejściowych błędów HTTP.

  • Obsługa blokowania komunikatów. W przypadku systemów opartych na komunikatach zaimplementuj strategie obsługi komunikatów, które obsługują ponawianie prób bez utraty danych, takie jak używanie trybów "peek-lock", jeśli są dostępne. Upewnij się, że komunikaty, które zakończyły się niepowodzeniem, są skutecznie ponawiane i przenoszone do kolejki utraconych komunikatów po powtarzających się awariach.

Wskazówki dotyczące konfiguracji

Poniższe sekcje zawierają wskazówki dotyczące implementowania aktualizacji konfiguracji. Każda sekcja jest zgodna z co najmniej jednym filarem dobrze zaprojektowanej struktury.

Konfigurowanie Niezawodność (RE) Zabezpieczenia (SE) Optymalizacja kosztów (CO) Doskonałość operacyjna (OE) Wydajność (PE) Obsługa dobrze zaprojektowanych zasad struktury
Konfigurowanie uwierzytelniania i autoryzacji SE:05
OE:10
Implementowanie niezależnego skalowania automatycznego RE:06
CO:12
PE:05
Konteneryzowanie wdrożenia usługi CO:13
PE:09
PE:03

Konfigurowanie uwierzytelniania i autoryzacji

Aby skonfigurować uwierzytelnianie i autoryzację dla wszystkich nowych usług platformy Azure (tożsamości obciążeń) dodanych do aplikacji internetowej, postępuj zgodnie z następującymi zaleceniami:

  • Użyj tożsamości zarządzanych dla każdej nowej usługi. Każda niezależna usługa powinna mieć własną tożsamość i używać tożsamości zarządzanych do uwierzytelniania między usługami. Tożsamości zarządzane eliminują konieczność zarządzania poświadczeniami w kodzie i zmniejszają ryzyko wycieku poświadczeń. Pomaga to uniknąć umieszczania poufnych informacji, takich jak parametry połączenia w kodzie lub plikach konfiguracji.

  • Przyznaj każdemu nowemu usłudze najmniej uprawnień. Przypisz tylko niezbędne uprawnienia do każdej nowej tożsamości usługi. Jeśli na przykład tożsamość musi być wypychana tylko do rejestru kontenerów, nie daj jej uprawnień ściągnięcia. Regularnie przeglądaj te uprawnienia i dostosuj je w razie potrzeby. Użyj różnych tożsamości dla różnych ról, takich jak wdrażanie i aplikacja. Ogranicza to potencjalne szkody w przypadku naruszenia zabezpieczeń jednej tożsamości.

  • Wdrażanie infrastruktury jako kodu (IaC). Użyj narzędzi Bicep lub podobnych narzędzi IaC, takich jak Terraform, aby zdefiniować zasoby w chmurze i zarządzać nimi. Infrastruktura IaC zapewnia spójne stosowanie konfiguracji zabezpieczeń we wdrożeniach i umożliwia kontrolowanie wersji konfiguracji infrastruktury.

Aby skonfigurować uwierzytelnianie i autoryzację dla użytkowników (tożsamości użytkowników), wykonaj następujące zalecenia:

  • Przyznawanie użytkownikom najniższych uprawnień. Podobnie jak w przypadku usług, upewnij się, że użytkownicy otrzymują tylko uprawnienia, których potrzebują do wykonywania swoich zadań. Regularnie sprawdzaj i dostosowuj te uprawnienia.

  • Przeprowadzanie regularnych inspekcji zabezpieczeń. Regularnie sprawdzaj i przeprowadzaj inspekcję konfiguracji zabezpieczeń. Poszukaj wszelkich błędów konfiguracji lub niepotrzebnych uprawnień i natychmiast je sprostuj.

Implementacja referencyjna używa usługi IaC do przypisywania tożsamości zarządzanych do dodanych usług i określonych ról do każdej tożsamości. Definiuje role i uprawnienia dostępu do wdrożenia, definiując role dla wypychania i ściągania usługi Container Registry (zobacz następujący kod).

resource "azurerm_role_assignment" "container_app_acr_pull" {
  principal_id         = var.aca_identity_principal_id
  role_definition_name = "AcrPull"
  scope                = azurerm_container_registry.acr.id
}

resource "azurerm_user_assigned_identity" "container_registry_user_assigned_identity" {
  name                = "ContainerRegistryUserAssignedIdentity"
  resource_group_name = var.resource_group
  location            = var.location
}

resource "azurerm_role_assignment" "container_registry_user_assigned_identity_acr_pull" {
  scope                = azurerm_container_registry.acr.id
  role_definition_name = "AcrPull"
  principal_id         = azurerm_user_assigned_identity.container_registry_user_assigned_identity.principal_id
}


# For demo purposes, allow current user access to the container registry
# Note: when running as a service principal, this is also needed
resource "azurerm_role_assignment" "acr_contributor_user_role_assignement" {
  scope                = azurerm_container_registry.acr.id
  role_definition_name = "Contributor"
  principal_id         = data.azuread_client_config.current.object_id
}

Konfigurowanie niezależnego skalowania automatycznego

Wzorzec nowoczesnej aplikacji internetowej zaczyna rozpadać architekturę monolityczną i wprowadza oddzielenie usług. W przypadku oddzielenia architektury aplikacji internetowej można niezależnie skalować oddzielone usługi. Skalowanie usług platformy Azure w celu obsługi niezależnej usługi aplikacji internetowej, a nie całej aplikacji internetowej, optymalizuje skalowanie kosztów przy jednoczesnym spełnieniu wymagań. Aby automatycznie skalować kontenery, wykonaj następujące zalecenia:

  • Korzystanie z usług bezstanowych. Upewnij się, że usługi są bezstanowe. Jeśli aplikacja internetowa zawiera stan sesji procesu, należy ją zewnętrznie połączyć z rozproszoną pamięcią podręczną, na przykład Redis lub bazą danych, na przykład SQL Server.

  • Konfigurowanie reguł skalowania automatycznego. Użyj konfiguracji skalowania automatycznego, które zapewniają najbardziej ekonomiczną kontrolę nad usługami. W przypadku usług konteneryzowanych skalowanie oparte na zdarzeniach, takie jak narzędzie Kubernetes Event-Driven Autoscaler (KEDA) często zapewnia szczegółową kontrolę, umożliwiając skalowanie na podstawie metryk zdarzeń. Usługa Container Apps i usługa AKS obsługują usługę KEDA. W przypadku usług, które nie obsługują usługi KEDA, takich jak usługa App Service, użyj funkcji skalowania automatycznego udostępnianych przez samą platformę. Te funkcje często obejmują skalowanie na podstawie reguł opartych na metrykach lub ruchu HTTP.

  • Skonfiguruj minimalną liczbę replik. Aby zapobiec zimnemu startowi, skonfiguruj ustawienia skalowania automatycznego, aby zachować co najmniej jedną replikę. Zimny start polega na zainicjowaniu usługi ze stanu zatrzymania, co często powoduje opóźnienie odpowiedzi. Jeśli minimalizacja kosztów jest priorytetem i można tolerować opóźnienia uruchamiania zimnego, ustaw minimalną liczbę replik na 0 podczas konfigurowania skalowania automatycznego.

  • Skonfiguruj okres ochładzania. Zastosuj odpowiedni okres ochładzania, aby wprowadzić opóźnienie między zdarzeniami skalowania. Celem jest zapobieganie nadmiernym skalowaniu działań wyzwalanych przez tymczasowe skoki obciążenia.

  • Konfigurowanie skalowania opartego na kolejce. Jeśli aplikacja używa kolejki komunikatów, takiej jak Service Bus, skonfiguruj ustawienia skalowania automatycznego w celu skalowania na podstawie długości kolejki z komunikatami żądania. Program scaler ma na celu utrzymanie jednej repliki usługi dla każdego N komunikatu w kolejce (zaokrąglone w górę).

Na przykład implementacja referencyjna używa modułu skalowania KEDA usługi Service Bus do automatycznego skalowania aplikacji kontenera na podstawie długości kolejki usługi Service Bus. Reguła skalowania o nazwie service-bus-queue-length-ruledostosowuje liczbę replik usługi w zależności od liczby komunikatów w określonej kolejce usługi Service Bus. Parametr messageCount jest ustawiony na 10, co oznacza, że program scaler dodaje jedną replikę dla każdego 10 komunikatów w kolejce. Maksymalna liczba replik (max_replicas) jest ustawiona na 10, a minimalna liczba replik jest niejawnie równa 0, chyba że zostaną zastąpione, co umożliwia usłudze skalowanie w dół do zera, gdy nie ma żadnych komunikatów w kolejce. Parametry połączenia kolejki usługi Service Bus jest bezpiecznie przechowywana jako wpis tajny na platformie Azure o nazwie azure-servicebus-connection-string, który jest używany do uwierzytelniania modułu skalowania w usłudze Service Bus.

    max_replicas = 10
    min_replicas = 1

    custom_scale_rule {
      name             = "service-bus-queue-length-rule"
      custom_rule_type = "azure-servicebus"
      metadata = {
        messageCount = 10
        namespace    = var.servicebus_namespace
        queueName    = var.email_request_queue_name
      }
      authentication {
        secret_name       = "azure-servicebus-connection-string"
        trigger_parameter = "connection"
      }
    }

Konteneryzowanie wdrożenia usługi

Konteneryzacja oznacza, że wszystkie zależności aplikacji do działania są hermetyzowane w uproszczonym obrazie, który można niezawodnie wdrożyć na wielu hostach. Aby konteneryzować wdrożenie, wykonaj następujące zalecenia:

  • Zidentyfikuj granice domeny. Zacznij od zidentyfikowania granic domeny w aplikacji monolitycznej. Pomaga to określić, które części aplikacji można wyodrębnić do oddzielnych usług.

  • Tworzenie obrazów platformy Docker. Podczas tworzenia obrazów platformy Docker dla usług Java użyj oficjalnych obrazów podstawowych openJDK. Te obrazy zawierają tylko minimalny zestaw pakietów potrzebnych do uruchomienia języka Java, co minimalizuje zarówno rozmiar pakietu, jak i obszar powierzchni podatnej na ataki.

  • Użyj wieloetapowych plików Dockerfile. Użyj wieloetapowych plików Dockerfile, aby oddzielić zasoby czasu kompilacji od obrazu kontenera środowiska uruchomieniowego. Pomaga to zachować małe i bezpieczne obrazy produkcyjne. Możesz również użyć wstępnie skonfigurowanego serwera kompilacji i skopiować plik jar do obrazu kontenera.

  • Uruchom polecenie jako użytkownik niebędący użytkownikiem głównym. Uruchom kontenery Java jako użytkownik niebędący użytkownikiem głównym (za pomocą nazwy użytkownika lub identyfikatora UID, $APP_UID), aby dopasować się do zasady najniższych uprawnień. Ogranicza ona potencjalne skutki naruszenia zabezpieczeń kontenera.

  • Nasłuchiwanie na porcie 8080. W przypadku uruchamiania jako użytkownik niebędący użytkownikiem głównym skonfiguruj aplikację do nasłuchiwania na porcie 8080. Jest to powszechna konwencja dla użytkowników niebędących użytkownikami głównymi.

  • Hermetyzowanie zależności. Upewnij się, że wszystkie zależności aplikacji do działania są hermetyzowane w obrazie kontenera platformy Docker. Hermetyzacja umożliwia niezawodne wdrażanie aplikacji na wielu hostach.

  • Wybierz odpowiednie obrazy podstawowe. Wybrany obraz podstawowy zależy od środowiska wdrażania. Jeśli wdrażasz na przykład w usłudze Container Apps, musisz użyć obrazów platformy Docker systemu Linux.

Implementacja referencyjna demonstruje proces kompilacji platformy Docker na potrzeby konteneryzowania aplikacji Java. Ten plik Dockerfile używa kompilacji jednoetapowej z podstawowym obrazem OpenJDK (mcr.microsoft.com/openjdk/jdk:17-ubuntu), który zapewnia niezbędne środowisko uruchomieniowe Języka Java.

Plik Dockerfile obejmuje następujące kroki:

  1. Deklaracja woluminu: zdefiniowano wolumin tymczasowy (/tmp), co umożliwia przechowywanie plików tymczasowych niezależnie od głównego systemu plików kontenera.
  2. Kopiowanie artefaktów: plik JAR aplikacji (email-processor.jar) jest kopiowany do kontenera wraz z agentem usługi Application Insights (applicationinsights-agent.jar) na potrzeby monitorowania.
  3. Ustawianie punktu wejścia: kontener jest skonfigurowany do uruchamiania aplikacji z włączonym agentem usługi Application Insights przy użyciu funkcji java -javaagent monitorowania aplikacji w czasie wykonywania.

Ten plik Dockerfile zapewnia, że obraz jest oparty tylko na zależnościach środowiska uruchomieniowego, odpowiedni dla środowisk wdrażania, takich jak Container Apps, które obsługują kontenery oparte na systemie Linux.

# Use OpenJDK 17 base image on Ubuntu as the foundation
FROM mcr.microsoft.com/openjdk/jdk:17-ubuntu

# Define a volume to allow temporary files to be stored separately from the container's main file system
VOLUME /tmp

# Copy the packaged JAR file into the container
COPY target/email-processor.jar app.jar

# Copy the Application Insights agent for monitoring
COPY target/agent/applicationinsights-agent.jar applicationinsights-agent.jar

# Set the entry point to run the application with the Application Insights agent
ENTRYPOINT ["java", "-javaagent:applicationinsights-agent.jar", "-jar", "/app.jar"]

Wdrażanie implementacji referencyjnej

Wdróż implementację referencyjną wzorca nowoczesnej aplikacji internetowej dla języka Java. W repozytorium znajdują się instrukcje dotyczące wdrażania programistycznego i produkcyjnego. Po wdrożeniu można symulować i obserwować wzorce projektowe.

Diagram przedstawiający architekturę implementacji referencyjnej.Rysunek 3. Architektura implementacji referencyjnej. Pobierz plik programu Visio tej architektury.