Queues ohne Überstunden: C#, Azure Service Bus und AWS SQS in einem belastbaren Ablauf
Interaktiver Workshop zu C# 14, .NET 10, Azure Service Bus und Amazon SQS: Outbox, Idempotenz, Blob/S3-Claim-Check, DLQ, Ausfallkosten, animierte Karte und Quiz mit E-Mail-Bericht.
Der vollständige Artikel steht unten. Aktivieren Sie JavaScript für Quiz und Simulationen.
Ergebnis löschen
Freitag, 16:47
Eine Queue soll Ausfälle nicht verstecken. Sie verschafft Zeit für eine sichere Reaktion
Ein Kunde bestätigt einen Auftrag. Die API antwortet korrekt, das externe Abrechnungssystem wird aber langsam. Der Worker verliert nach der Ausführung die Verbindung; dieselbe Nachricht kommt erneut. Setzt der Entwurf genau eine Zustellung voraus, beginnt Samstagmorgen mit doppelter Wirkung und manueller Datenbereinigung.
Dies ist ein Workshop, keine erfundene Kundenerfolgsgeschichte. Wir bauen einen Ablauf für Wiederholungen, Verzögerungen und Teilausfälle: gemeinsamer C#-Port, Transportstrategie, Transactional Outbox, idempotenter Konsument, Retry, Dead-Letter Queue und Claim-Check für Dateien in Azure Blob Storage oder Amazon S3. Abschließend folgen zehn Prüfungsfragen und ein persönlicher E-Mail-Bericht.
Denkmodell
Eine Queue ist ein kontrollierter Warteraum, kein magischer Beschleuniger
Im synchronen System wartet der Nutzer auf alle abhängigen Schritte. Asynchron nimmt die API eine Absicht an, speichert eigenen Zustand und reicht die Ausführung weiter. Ein vorübergehender Integrationsausfall muss dann nicht den ganzen Pfad stoppen. Dafür muss das System Duplikate erkennen, Arbeit wieder aufnehmen, Rückstände beobachten und Fälle bewusst entscheiden, die Automatisierung nicht mehr reparieren kann.
Absicht speichern und eine stabile ID vergeben.
Nutzer nicht auf fremde Systeme warten lassen.
Retry ist Normalfall, keine Entwurfsausnahme.
Metrik, Log und Receipt müssen eine Wirkung belegen.
Karte zum Behalten
Vom Request zur Wirkung: ein Vertrag, zwei Transporte
Scanne die Karte und nutze sie als Review-Checkliste
Der Code führt direkt zu dieser animierten Karte. Öffne sie beim Worker-Entwurf oder bei der Incident-Analyse, ohne das Kapitel suchen zu müssen.
Karte ohne Scannen öffnen →Die API hat den Auftrag gespeichert, der Broker ist vorübergehend unerreichbar. Was tust du?
Ich warte und wiederhole die Veröffentlichung im HTTP-Request.
Ich speichere Zustand und Outbox in einer Transaktion; der Dispatcher setzt das Senden fort.
Wähle die Lösung, die keinen angenommenen Auftrag verliert.
Technologiestand bei Veröffentlichung
Neues C# repariert keine falschen Grenzen, kann sie aber einfacher ausdrücken
C# 14 kommt mit .NET 10, einem LTS-Release. Extension Members, Null-Conditional Assignment und verbesserte Span-Nutzung erleichtern den Code; Zuverlässigkeit entsteht weiterhin aus Vertrag und Daten. Wichtiger als syntaktische Kürze sind ID, Version, Korrelation, Ereigniszeit und minimale Konsumentendaten.
Microsoft kündigte das Supportende der älteren Bibliotheken
WindowsAzure.ServiceBus und Microsoft.Azure.ServiceBus
zum 30. September 2026 an. Neuer Code sollte Azure.Messaging.ServiceBusverwenden; bestehende Systeme brauchen eine geplante Migration mit Abnahmetest.
AWS SDK for .NET V4 bietet direkte SQS-APIs; das AWS Message Processing Framework for .NET kann Veröffentlichung und Verarbeitung ordnen. Es ersetzt jedoch weder Idempotenz noch korrekte Visibility Timeouts oder DLQ-Kontrolle.
Transportwahl
Azure Service Bus und Amazon SQS lösen verwandte Aufgaben mit unterschiedlichen Werkzeugen
| Bedarf | Azure Service Bus | Amazon SQS | Entwurfsentscheidung |
|---|---|---|---|
| Grundlegende Zustellung | PeekLock liefert At-least-once-Semantik | Standard Queue liefert At-least-once | Der Konsument muss Wiederholungen vertragen |
| Reihenfolge innerhalb einer Gruppe | Sessions mit SessionId | FIFO mit MessageGroupId | Nach Aggregat gruppieren, nicht systemweit |
| Veröffentlichungsduplikate | Duplicate Detection über MessageId | FIFO-Deduplizierung über Deduplication-ID | Ergänzende Schicht, kein Inbox-Ersatz |
| Ausführungszeit | Lock-Dauer und Erneuerung | Visibility Timeout und Anpassung | Oberhalb p95 setzen, Überschreitungen beobachten |
| Poison Messages | Eingebaute Dead-Letter Queue | Redrive Policy zur DLQ | Alarm, Verantwortliche, Retry-Policy und Replay-Verfahren |
| Große Daten | Blob Storage + Schlüssel in der Nachricht | S3 + Schlüssel in der Nachricht | Claim-Check, Prüfsumme und begrenzte Zugriffszeit |
„Exactly once“ darf kein Wunsch im Entwurf sein. Ein sicheres Ziel lautet: Die Nachricht kann erneut eintreffen, die beobachtbare Geschäftswirkung entsteht einmal.
C#-Vertrag
Erst der Nachrichtenumschlag, dann die Cloud-Wahl
Das Domänenmodell sollte keine Azure- oder AWS-SDK-Typen importieren. Zuerst definieren wir einen eigenen kleinen Vertrag. Der Transportadapter übersetzt ihn in Brokereigenschaften.
public sealed record WorkEnvelope<T>(
Guid MessageId,
Guid CorrelationId,
string TenantId,
int SchemaVersion,
DateTimeOffset OccurredAt,
T Payload);
public interface IWorkQueue
{
Task PublishAsync<T>(
WorkEnvelope<T> message,
QueueRoute route,
CancellationToken cancellationToken);
}
MessageId schützt vor wiederholter Wirkung, CorrelationId verbindet die Spur zwischen API und Worker, TenantId ermöglicht Isolation, und
SchemaVersion schafft Raum für sichere Weiterentwicklung. Secrets, vollständige Dokumente und Daten „für alle Fälle“ gehören nicht in den Umschlag.
Transportwahl durch eine Policy
public interface IQueueStrategy
{
bool Supports(CloudBroker broker);
Task PublishAsync<T>(
WorkEnvelope<T> message,
QueueRoute route,
CancellationToken ct);
}
public sealed class QueueRouter(
IEnumerable<IQueueStrategy> strategies)
{
public Task PublishAsync<T>(
CloudBroker broker,
WorkEnvelope<T> message,
QueueRoute route,
CancellationToken ct)
=> strategies.Single(x => x.Supports(broker))
.PublishAsync(message, route, ct);
}
Wann lohnt sich ein Router?
- Verschiedene Regionen oder Mandanten brauchen unterschiedliche Transporte,
- eine Cloud-Migration läuft,
- kontrolliertes Failover ist nötig,
- Tests nutzen In-Memory-Transport.
Nutzt das System immer einen Broker, ist ein Adapter einfacher. Abstraktion soll echte Variabilität beherrschen, nicht vorsorglich erzeugen.
Eine Nachricht soll ein 18-MB-PDF übertragen. Was kommt in die Queue?
Die gesamte Datei als Base64, damit der Worker sofort alles hat.
Objektschlüssel, Prüfsumme und Metadaten; die Datei liegt in Blob Storage oder S3.
Wähle einen kleinen, sicheren Vertrag.
Claim-Check
Blob Storage oder S3 trägt das Gewicht. Die Queue trägt die Absicht
Die API speichert unter einem unvorhersehbaren Schlüssel, berechnet SHA-256 und veröffentlicht nur die Referenz. Der Worker greift mit Managed Identity oder IAM-Rolle zu, prüft den Hash und verarbeitet als Stream. Öffentlicher Zugriff ist deaktiviert; Aufbewahrung und Verschlüsselung folgen der Datenklasse.
Atomarität
Die Outbox schließt die Lücke zwischen COMMIT und Veröffentlichung
PostgreSQL und Broker teilen keine verteilte Transaktion. Deshalb speichern wir Geschäftsänderung und Publikationsdatensatz in derselben lokalen Transaktion.
await using var transaction =
await db.Database.BeginTransactionAsync(ct);
var order = WorkOrder.Accept(command.RequestId, command.CustomerId);
db.WorkOrders.Add(order);
db.OutboxMessages.Add(OutboxMessage.Create(
messageId: command.RequestId,
type: "work-order.accepted.v1",
payload: serializer.Serialize(new WorkOrderAccepted(order.Id))));
await db.SaveChangesAsync(ct);
await transaction.CommitAsync(ct);
// Dispatcher odczyta OutboxMessages dopiero po COMMIT.
Der Dispatcher liest unbearbeitete Datensätze portionsweise, veröffentlicht und markiert sie. Zwei Dispatcher dürfen denselben Datensatz nicht ohne Nebenläufigkeitskontrolle übernehmen. PostgreSQL kann Zeilensperren mit SKIP LOCKED oder einen ausdrücklichen Lease-Status nutzen. Wichtig bleibt das Alter des ältesten Outbox-Eintrags: Die Anzahl allein zeigt nicht, wie lange ein Kunde wartet.
Idempotenter Empfang
Die Inbox entscheidet über Duplikate vor irreversiblen Wirkungen
Service Bus mit PeekLock und SQS Standard können Nachrichten erneut zustellen. Der Konsument prüft sein Receipt, führt die Wirkung aus und bestätigt erst nach dauerhaftem Speichern.
public async Task HandleAsync(
WorkEnvelope<WorkOrderAccepted> message,
CancellationToken ct)
{
if (await db.InboxReceipts.AnyAsync(
x => x.MessageId == message.MessageId, ct))
return;
await using var tx = await db.Database.BeginTransactionAsync(ct);
await pricing.CreateQuoteAsync(
message.Payload.WorkOrderId,
idempotencyKey: message.MessageId.ToString("N"),
ct);
db.InboxReceipts.Add(new InboxReceipt(message.MessageId));
await db.SaveChangesAsync(ct);
await tx.CommitAsync(ct);
}
Der Worker führte die Aktion aus, verlor aber vor Complete/Delete die Verbindung. Was tut er bei erneuter Zustellung?
Receipt anhand der MessageId erkennen und ohne erneute Wirkung abschließen.
Alles erneut ausführen, weil der Broker bewusst wiederholt hat.
Was belegt den Abschluss, wenn die Brokerbestätigung verloren ging?
Die Kosten eines Ausfalls
Eine Queue spart erst Geld, wenn sie manuelle Reaktionskosten senkt
Die folgende Rechnung ist ein Beispiel für eigene Zahlen, kein ArcCloud-Kundenfall. Angenommen: vier Vorfälle pro Monat, drei Beteiligte für durchschnittlich 2,5 Stunden und 180 PLN Vollkosten je Arbeitsstunde.
Hinzu kommen Konzentrationsverlust, Roadmap-Verzögerungen und Kundenrisiken. Vergleiche diese Kosten mit Broker, Telemetrie und automatischen Tests. Die günstigste Architektur hat nicht die kleinste Cloud-Rechnung, sondern die niedrigsten Gesamtkosten eines sicheren Ergebnisses.
Architekturspiel
Ordne sieben Schritte sicherer Verarbeitung
Klicke die Felder in Reihenfolge. Eine falsche Wahl kostet keinen Punkt, sondern zeigt die noch fehlende Verantwortung.
BrokerASB oder SQS speichert die Nachricht
CommandAPI nimmt eine stabile requestId an
GeschäftswirkungAusführung mit Idempotenzschlüssel
Dispatcherveröffentlicht die gespeicherte Outbox
SettlementComplete oder Delete nach dauerhaftem Ergebnis
DB + OutboxZustand und Nachricht in einem COMMIT
Consumer + InboxDuplikat- und Korrelationsprüfung
Beginne mit einer Absicht mit stabiler ID.
Neu ordnen
37 Nachrichten liegen in der DLQ. Was ist der erste richtige Schritt?
Sofort alle Nachrichten an die Hauptqueue zurücksenden.
Ursache klassifizieren und beheben, danach eine kleine Charge kontrolliert wiederholen.
Die DLQ ist eine Betriebsqueue, kein Papierkorb zum Leeren per Klick.
Incident-Simulator
Führe eine Nachricht durch Timeout, Retry und Duplikat
Der kontrollierte Ablauf prüft requestId, Outbox, Transport, Inbox, Geschäftswirkung, Receipt und Settlement.
Ablauf starten
Labor bereit. Starte den kontrollierten Ablauf.
Produktions-Runbook
Was du beobachten solltest, bevor nach Feierabend das Telefon klingelt
Nachrichtenalter
p50, p95 und älteste Nachricht. Queuelänge ohne Eingangsrate ist irreführend.
Retry-Rate
Versuche nach Fehlertyp, Abhängigkeit und Vertragsversion. Alarmiere Trendänderungen, nicht jeden einzelnen Retry.
DLQ-Eingang
Neue Einträge, Grund, Verantwortung und Zeit bis zur Entscheidung. Eine DLQ ohne Verantwortliche wächst unbemerkt.
Lock lost / Timeout
Zeigt unpassende Arbeits- und Sperrzeiten oder überlastete Konsumenten.
Verhinderte Duplikate
Der Zähler belegt, dass Idempotenz das Ergebnis tatsächlich schützt und nicht nur dokumentiert ist.
Outbox-Verzögerung
Alter des ältesten unveröffentlichten Datensatzes und Dispatcher-Durchsatz bilden grundlegende Integrations-SLOs.
Für Einsteiger
- Command, Event und Document Message unterscheiden.
- Verstehen, wann Complete/Delete sicher ist.
- MessageId, correlationId und einen Duplikattest ergänzen.
Für Fortgeschrittene
- Outbox und Inbox mit eindeutigem Index implementieren.
- Lock oder Visibility Timeout an p95 ausrichten.
- Retry mit Backoff, Jitter und kontrollierter DLQ bauen.
Für Seniors
- SLO, Fehlerbudget und Queue-Verantwortung festlegen.
- Schemaentwicklung und Replay historischer Daten testen.
- Betriebskosten berechnen und sicheres Rollback entwerfen.
Checkliste vor Produktion
Schon ein „weiß ich nicht“ bedeutet: Die Queue ist noch nicht bereit
- Hat jede Nachricht stabile MessageId und correlationId?
- Werden Zustand und Outbox atomar gespeichert?
- Liefert ein Duplikat dasselbe beobachtbare Ergebnis?
- Nutzen große Payloads Blob Storage/S3 und Claim-Check?
- Unterscheidet Retry vorübergehende und dauerhafte Fehler?
- Hat die DLQ Alarm, Verantwortliche und Replay-Verfahren?
- Passt Lock/Visibility Timeout zum tatsächlichen p95?
- Zeigt das Dashboard Nachrichtenalter statt nur Anzahl?
- Können wir den Konsumenten ohne Verlust angenommener Aufträge stoppen?
- Simuliert ein Test Timeout nach Wirkung, aber vor Settlement?
Baust du eine Queue oder verschiebst du nur den Ausfall?
Das Quiz behandelt Azure Service Bus, Amazon SQS, Outbox, Inbox, Claim-Check, Idempotenz, Retry und Telemetrie. Nach allen Antworten bestätigst du deine E-Mail per Einmalcode. Ergebnis, Niveau und nächste Schritte erhältst du in einem persönlichen Bericht. Die Adresse wird nicht im Blog veröffentlicht.
Technische Quellen
Der Beitrag basiert auf der bei Veröffentlichung aktuellen Herstellerdokumentation:
- Microsoft Learn: Neues in C# 14
- Microsoft Learn: Neues in .NET 10
- Azure Service Bus: Nachrichtentransfer, Sperren und Duplikate
- Azure Service Bus: Duplicate Detection
- Azure Service Bus: Message Sessions
- AWS SDK for .NET V4: Amazon-SQS-APIs
- AWS Message Processing Framework for .NET
- Amazon SQS: At-least-once-Zustellung
- Amazon SQS: Visibility Timeout
- Amazon SQS: Dead-Letter Queues