Bulkhead-Muster

Isolieren Sie die Elemente einer Anwendung in Pools, sodass die anderen Elemente weiterhin funktionieren, wenn ein Element fehlschlägt. Dieser Ansatz, auch als zellbasierte Architektur bezeichnet, macht eine Anwendung fehlertolerant und verhindert, dass sich ein Fehler in einem Teil des Systems auf den Rest ausweitet.

Tip

Dieses Muster wird nach den abschnittierten Partitionen (Bulkheads) eines Schiffsrumpfs benannt. Wenn eine Schottwand eines Schiffs beschädigt ist, füllt sich nur der beschädigte Abschnitt mit Wasser, sodass das Schiff nicht sinkt.

Kontext und Problem

Eine cloudbasierte Anwendung kann mehrere Dienste enthalten, und jeder Dienst verfügt über einen oder mehrere Verbraucher. Übermäßige Auslastung oder Fehler in einem Dienst wirken sich auf alle Verbraucher des Diensts aus.

Außerdem kann ein Verbraucher Anforderungen an mehrere Dienste gleichzeitig senden und Ressourcen für jede Anforderung verwenden. Wenn der Verbraucher eine Anforderung an einen falsch konfigurierten oder nicht reagierenden Dienst sendet, bleiben die Ressourcen, die die Anforderung des Clients verwendet, möglicherweise für einen längeren Zeitraum nicht verfügbar. Wenn anforderungen an den Dienst fortgesetzt werden, sind diese Ressourcen möglicherweise erschöpft. Der Verbindungspool des Clients kann z. B. erschöpft sein. Zu diesem Zeitpunkt sind die Anfragen des Verbrauchers an andere Dienste betroffen. Schließlich kann der Verbraucher keine Anfragen an andere Dienste senden, nicht nur den ursprünglichen nicht reagierenden Dienst.

Die Ressourcenausschöpfung wirkt sich auf Dienste aus, die mehrere Verbraucher haben. Viele Anforderungen von einem Client können die verfügbaren Ressourcen im Dienst ausschöpfen. Die Ressourcenerschöpfung kann bedeuten, dass andere Verbraucher den Dienst nicht nutzen können, was zu einem kaskadierenden Fehlereffekt führt.

Lösung

Unterteilen Sie Dienstinstanzen in unterschiedliche Gruppen, abhängig von den Anforderungen an Consumerlast und Verfügbarkeit. Dieses Design hilft beim Isolieren von Fehlern. Sie können die Dienstfunktionalität für einige Verbraucher auch während eines Ausfalls erhalten.

Ein Verbraucher kann auch Ressourcen partitionieren, um sicherzustellen, dass ressourcen, die zum Aufrufen eines Diensts verwendet werden, keine Auswirkungen auf die Ressourcen haben, die zum Aufrufen eines anderen Diensts verwendet werden. Beispielsweise wird einem Verbraucher, der mehrere Dienste aufruft, für jeden Dienst möglicherweise ein Verbindungspool zugewiesen. Wenn ein Dienst fehlschlägt, wirkt es sich nur auf den Verbindungspool aus, der diesem Dienst zugewiesen ist. Der Verbraucher kann weiterhin andere Dienste nutzen.

Dieses Muster bietet folgende Vorteile:

  • Verbraucher und Dienste werden vor kaskadierenden Fehlern isoliert. Ein Problem, das einen Verbraucher oder Dienst betrifft, kann innerhalb eines eigenen Abgrenzungsbereichs isoliert werden, um zu verhindern, dass die gesamte Lösung möglicherweise fehlschlägt.

  • Behält einige Funktionen bei, wenn ein Dienstfehler auftritt. Andere Dienste und Features der Anwendung funktionieren weiterhin.

  • Bietet unterschiedliche Quality of Service Levels bei der Nutzung von Anwendungen. Sie können einen Verbraucherpool mit hoher Priorität für die Verwendung von Diensten mit hoher Priorität konfigurieren.

Im folgenden Diagramm sind Bulkheads dargestellt, die Verbindungspools umgeben, die einzelne Dienste aufrufen. Wenn Dienst A fehlschlägt oder ein Problem verursacht, ist der Verbindungspool isoliert, sodass nur Arbeitslasten betroffen sind, die dem Dienst A zugewiesenen Threadpool verwenden. Arbeitsauslastungen, die Service B und C verwenden, sind nicht betroffen und können ohne Unterbrechung weiterarbeiten.

Diagramm, das zeigt, wie Trennwände um Verbindungspools strukturiert sind, die einzelne Dienste aufrufen.

Diagramm mit zwei Workloads, Workload 1 und Workload 2 und drei Diensten, Service A, Service B und Service C. Workload 1 verwendet einen Verbindungspool, der Dienst A zugewiesen ist. Workload 2 verwendet zwei Verbindungspools. Einem Verbindungspool wird Service B zugewiesen, und der andere wird Service C zugewiesen. Der Verbindungspool, den Workload 1 verwendet, ist isoliert. Die verbindungspools, die Workload 2 verwendet, können weiterhin Service B und Service C aufrufen.

Das folgende Diagramm zeigt mehrere Clients, die einen einzelnen Dienst aufrufen. Jedem Client wird eine separate Dienstinstanz zugewiesen. Client 1 führt zu viele Anforderungen aus und überfordert seine Instanz. Da jede Dienstinstanz von den anderen isoliert ist, können die anderen Clients weiterhin Anrufe tätigen.

Diagramm, das mehrere Clients zeigt, die einen einzelnen Dienst aufrufen.

Diagramm mit drei Clients, Client 1, Client 2 und Client 3 sowie drei Dienstinstanzen, die jeweils Teil von Service A sind. Jeder Client stellt eine Verbindung mit einer eigenen Dienstinstanz bereit. Die Dienstinstanzen sind isoliert. Wenn Client 1 seine Instanz überfordert, sind Clients 2 und 3 nicht betroffen.

Probleme und Überlegungen

Berücksichtigen Sie die folgenden Punkte, wenn Sie sich für die Implementierung dieses Musters entscheiden:

  • Definieren Sie Partitionen entsprechend den geschäftlichen und technischen Anforderungen der Anwendung.

  • Wenn Sie taktisches domänengesteuertes Design zum Entwerfen von Mikroservices verwenden, sollten Partitionsgrenzen an den gebundenen Kontexten ausgerichtet sein.

  • Wenn Sie Dienste oder Consumer in Bulkheads unterteilen, berücksichtigen Sie den Grad der von der Technologie gebotenen Isolation sowie den Mehraufwand an Kosten, Leistung und Verwaltbarkeit.

  • Um eine differenziertere Fehlerbehandlung zu ermöglichen, empfiehlt es sich möglicherweise, Bulkheads mit Wiederholungs-, Trennschalter- und Drosselungsmustern zu kombinieren.

  • Wenn Sie Consumer in Bulkheads unterteilen, sollten Sie möglicherweise Prozesse, Threadpools und Semaphore verwenden. Projekte wie resilience4j und Polly bieten ein Framework zur Erstellung von Schutzmechanismen für Verbraucher an.

  • Wenn Sie Dienste in Bulkheads partitionieren, sollten Sie sie in separaten virtuellen Computern, Containern oder Prozessen bereitstellen. Container bieten eine gut ausgewogene Ressourcenisolation mit relativ geringem Mehraufwand.

  • Dienste, die mithilfe asynchroner Nachrichten kommunizieren, können über verschiedene Warteschlangengruppen isoliert werden. Jede Warteschlange kann über einen dedizierten Satz von Instanzen verfügen, die Nachrichten in der Warteschlange verarbeiten, oder eine einzelne Gruppe von Instanzen, die einen Algorithmus verwenden, um die Verarbeitung zu entqueue und zu verteilen.

  • Bestimmen Sie den Grad der Granularität für die Bulkheads. Wenn Sie z. B. Mandanten über Partitionen verteilen möchten, können Sie jeden Mandanten in einer separaten Partition platzieren oder mehrere Mandanten in einer Partition platzieren.

  • Überwachen Sie die Leistungsfähigkeit und die Service-Level-Vereinbarung (SLA) der einzelnen Partitionen.

  • Verwenden Sie integrierte Plattformsteuerelemente wie Azure API Management Rate Limits, Azure Cosmos DB Request Unit (RU) Isolation und Ressourcengrenzwerte in Azure Kubernetes Service (AKS) oder Azure Container Apps. Erstellen Sie diese Drosselungs- und Isolationsmechanismen nicht in Ihrem Anwendungscode neu.

  • KI- und Rückschlussworkloads erfordern häufig strenge Bulkheads aufgrund von Kontingenten auf Bereitstellungsebene und Parallelitätsgrenzwerten. Isolieren Sie beispielsweise Modellbereitstellungen oder Foundry-Ressourcen pro Workload oder pro Mandant.

Wann Sie dieses Muster verwenden sollten

Verwenden Sie dieses Muster in folgenden Fällen:

  • Sie möchten Ressourcen für bestimmte Abhängigkeiten isolieren, sodass sich eine Unterbrechung in einem Dienst nicht auf die gesamte Anwendung auswirkt.
  • Sie möchten kritische Verbraucher von Standardkonsumenten isolieren.
  • Sie müssen die Anwendung vor kaskadierenden Fehlern schützen.

Dieses Muster ist möglicherweise nicht geeignet, wenn:

  • Eine weniger effiziente Nutzung von Ressourcen ist im Projekt möglicherweise nicht akzeptabel.
  • Die zusätzliche Komplexität ist nicht erforderlich.

Workloadentwurf

Bewerten Sie, wie Sie das Bulkhead-Muster im Design einer Workload einsetzen, um die in den Säulen des Azure Well-Architected Framework behandelten Ziele und Prinzipien zu erfüllen. Die folgende Tabelle enthält Anleitungen dazu, wie dieses Muster die Ziele jeder Säule unterstützt.

Säule So unterstützt dieses Muster die Säulenziele
Zuverlässigkeitsentwurfsentscheidungen helfen Ihrer Arbeitsauslastung, ausfallsicher zu werden und sicherzustellen, dass sie nach auftreten eines Fehlers wieder in einen voll funktionsfähigen Zustand versetzt wird. Die Fehlerisolationsstrategie, die durch die absichtliche und vollständige Segmentierung zwischen Komponenten eingeführt wird, versucht, Fehler nur auf den Bulkhead zu beschränken, bei dem das Problem auftritt, und verhindert so Auswirkungen auf andere Bulkheads.

- RE:02 Kritische Abläufe
- RE:07 Selbsterhaltung
Sicherheitsdesignentscheidungen tragen dazu bei, die Vertraulichkeit, Integrität und Verfügbarkeit der Daten und Systeme Ihrer Workload sicherzustellen. Die Segmentierung zwischen Komponenten trägt dazu bei, Sicherheitsvorfälle auf den gefährdete Bulkhead zu beschränken.

- SE:04 Segmentierung
Performance Efficiency hilft Ihrem Workload durch Optimierungen bei Skalierung, Daten und Code, die Anforderungen effizient zu erfüllen . Jeder Bulkhead kann individuell skalierbar sein, um die Anforderungen der im Bulkhead gekapselten Aufgabe effizient zu erfüllen.

- PE:02 Kapazitätsplanung
- PE:05 Skalierung und Partitionierung

Wenn dieses Muster Kompromisse innerhalb einer Säule einführt, sollten Sie sie gegen die Ziele der anderen Säulen berücksichtigen.

Beispiel

Die folgende Kubernetes-Konfigurationsdatei erstellt einen isolierten Container zum Ausführen eines einzelnen Diensts, mit eigenen CPU- und Arbeitsspeicherressourcen und eigenen CPU- und Arbeitsspeicherlimits.

apiVersion: v1
kind: Pod
metadata:
  name: drone-management
spec:
  containers:
  - name: drone-management-container
    image: drone-service
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "1"

Nächste Schritte