Freigeben über


Queue-Centric Arbeitsmuster (Erstellen Real-World Cloud-Apps mit Azure)

von Rick Anderson, Tom Dykstra

Download Fix It Project oder E-Book herunterladen

Das E-Book Building Real World Cloud Apps with Azure basiert auf einer Präsentation, die von Scott Guthrie entwickelt wurde. Es werden 13 Muster und Methoden erläutert, die Ihnen helfen können, Web-Apps für die Cloud erfolgreich zu entwickeln. Informationen zum E-Book finden Sie im ersten Kapitel.

Zuvor haben wir gesehen, dass die Verwendung mehrerer Dienste zu einer "zusammengesetzten" SLA führen kann, bei der die effektive SLA der App das Ergebnis der einzelnen SLAs ist. Die Fix It-App verwendet beispielsweise Websites, Speicher und SQL-Datenbank. Wenn einer dieser Dienste fehlschlägt, gibt die App einen Fehler an den Benutzer zurück.

Das Zwischenspeichern ist eine gute Möglichkeit, vorübergehende Fehler für schreibgeschützte Inhalte zu behandeln. Was aber, wenn Ihre Anwendung funktionieren muss? Wenn der Benutzer beispielsweise eine neue Fix It-Aufgabe übermittelt, kann die App die Aufgabe nicht einfach in den Cache ablegen. Die App muss den Task Fix It in einen persistenten Datenspeicher schreiben, damit er verarbeitet werden kann.

Hier setzt das warteschlangenorientierte Arbeitsmuster an. Dieses Muster ermöglicht eine lose Kopplung zwischen einer Webebene und einem Back-End-Dienst.

Hier erfahren Sie, wie das Muster funktioniert. Wenn die Anwendung eine Anforderung erhält, legt sie ein Arbeitselement in eine Warteschlange und gibt sofort die Antwort zurück. Ein separater Back-End-Prozess ruft dann Arbeitselemente aus der Warteschlange ab und erledigt die Arbeit.

Das warteschlangenorientierte Arbeitsmuster ist nützlich für:

  • Zeitaufwendige Arbeit (hohe Latenz).
  • Arbeit, die einen externen Dienst erfordert, der möglicherweise nicht immer verfügbar ist.
  • Ressourcenintensive Arbeit (hohe CPU).
  • Arbeit, die von der Ratenausgleichung profitieren würde (bei plötzlichen Lastausbrüchen).

Reduzierte Latenz

Warteschlangen sind nützlich, wenn Sie zeitaufwendig arbeiten. Wenn eine Aufgabe einige Sekunden oder länger dauert, legen Sie das Arbeitselement in eine Warteschlange, anstatt den Endbenutzer zu blockieren. Informieren Sie den Benutzer mit "Wir arbeiten daran", und verwenden Sie dann einen Warteschlangenlistener, um die Aufgabe im Hintergrund zu verarbeiten.

Wenn Sie beispielsweise etwas bei einem Onlinehändler kaufen, bestätigt die Website Ihre Bestellung sofort. Das bedeutet aber nicht, dass sich Ihre Sachen bereits in einem Lkw befindet, der geliefert wird. Sie haben eine Aufgabe in eine Warteschlange gestellt, und im Hintergrund führen sie die Bonitätsprüfung durch, bereiten Ihre Artikel für den Versand vor usw.

Bei Szenarien mit kurzer Latenz kann die gesamte End-to-End-Zeit bei Verwendung einer Warteschlange länger sein als die synchrone Durchführung der Aufgabe. Aber auch dann können die anderen Vorteile diesen Nachteil überwiegen.

Höhere Zuverlässigkeit

In der Version von Fix It, die wir uns bisher ansehen, ist das Web-Front-End eng mit dem SQL-Datenbank Back-End gekoppelt. Wenn der SQL-Datenbankdienst nicht verfügbar ist, erhält der Benutzer eine Fehlermeldung. Wenn Wiederholungen nicht funktionieren (d. h. der Fehler ist mehr als vorübergehend), können Sie nur einen Fehler anzeigen und den Benutzer bitten, es später erneut zu versuchen.

Diagramm: Fehler beim Web-Front-End bei SQL-Datenbank Back-End-Fehlern

Wenn ein Benutzer mithilfe von Warteschlangen einen Fix It-Task sendet, schreibt die App eine Nachricht in die Warteschlange. Die Nachrichtennutzlast ist eine JSON-Darstellung der Aufgabe. Sobald die Nachricht in die Warteschlange geschrieben wird, kehrt die App zurück und zeigt dem Benutzer sofort eine Erfolgsmeldung an.

Wenn einer der Back-End-Dienste – z. B. die SQL-Datenbank oder der Warteschlangenlistener – offline geht, können Benutzer weiterhin neue Fix It-Aufgaben übermitteln. Die Nachrichten werden nur in die Warteschlange gestellt, bis die Back-End-Dienste wieder verfügbar sind. An diesem Punkt werden die Back-End-Dienste das Backlog nachholen.

Diagramm, das zeigt, dass das Web-Front-End weiterhin funktioniert, wenn ein SQL-Datenbank Fehler auftritt

Darüber hinaus können Sie jetzt mehr Back-End-Logik hinzufügen, ohne sich Um die Resilienz des Front-End kümmern zu müssen. Sie können beispielsweise eine E-Mail oder SMS-Nachricht an den Besitzer senden, wenn ein neues Fix It zugewiesen wird. Wenn der E-Mail- oder SMS-Dienst nicht mehr verfügbar ist, können Sie alles andere verarbeiten und dann eine Nachricht in eine separate Warteschlange zum Senden von E-Mail-/SMS-Nachrichten einfügen.

Zuvor war unsere effektive SLA Web-Apps × Storage × SQL-Datenbank = 99,7 %. (Siehe Entwerfen zum Überleben von Fehlern.)

Wenn wir die App so ändern, dass sie eine Warteschlange verwendet, hängt das Web-Front-End nur von Web-Apps und Storage ab, für eine zusammengesetzte SLA von 99,8 %. (Beachten Sie, dass Warteschlangen Teil des Azure-Speicherdiensts sind, sodass sie in derselben SLA wie Blob Storage enthalten sind.)

Wenn Sie noch mehr als 99,8 % benötigen, können Sie zwei Warteschlangen in zwei verschiedenen Regionen erstellen. Legen Sie eine als primäre und die andere als sekundär fest. Führen Sie in Ihrer App ein Failover zur sekundären Warteschlange aus, wenn die primäre Warteschlange nicht verfügbar ist. Die Wahrscheinlichkeit, dass beide gleichzeitig nicht verfügbar sind, ist sehr gering.

Ratenausgleich und unabhängige Skalierung

Warteschlangen sind auch nützlich für etwas, das als Ratenausgleich oder Lastenausgleich bezeichnet wird.

Web-Apps sind häufig anfällig für plötzliche Datenverkehrsausbrüche. Während Sie die automatische Skalierung verwenden können, um automatisch Webserver hinzuzufügen, um erhöhten Webdatenverkehr zu verarbeiten, kann die automatische Skalierung möglicherweise nicht schnell genug reagieren, um abrupte Auslastungsspitzen zu bewältigen. Wenn die Webserver einen Teil der Arbeit, die sie erledigen müssen, durch Schreiben einer Nachricht in eine Warteschlange auslagern können, können sie mehr Datenverkehr verarbeiten. Ein Back-End-Dienst kann dann Nachrichten aus der Warteschlange lesen und verarbeiten. Die Tiefe der Warteschlange wird größer oder kleiner, wenn die eingehende Last variiert.

Da ein Großteil der zeitaufwendigen Arbeit in einen Back-End-Dienst entladen wird, kann die Webebene leichter auf plötzliche Datenverkehrsspitzen reagieren. Und Sie sparen Geld, da eine bestimmte Menge an Datenverkehr von weniger Webservern verarbeitet werden kann.

Sie können die Webebene und den Back-End-Dienst unabhängig skalieren. Beispielsweise benötigen Sie möglicherweise drei Webserver, aber nur einen Server, der Warteschlangennachrichten verarbeitet. Wenn Sie eine computeintensive Aufgabe im Hintergrund ausführen, benötigen Sie möglicherweise weitere Back-End-Server.

Diagramm: Darstellung der Skalierungsebenen bei der Verarbeitung der Aufgaben in der Warteschlange

Die automatische Skalierung funktioniert sowohl mit Back-End-Diensten als auch mit der Webebene. Sie können die Anzahl der VMs, die die Aufgaben in der Warteschlange verarbeiten, basierend auf der CPU-Auslastung der Back-End-VMs hoch- oder herunterskalieren. Sie können auch automatisch skalieren, basierend darauf, wie viele Elemente sich in einer Warteschlange befinden. Beispielsweise können Sie die automatische Skalierung anweisen, nicht mehr als 10 Elemente in der Warteschlange zu behalten. Wenn die Warteschlange mehr als 10 Elemente enthält, fügt die automatische Skalierung VMs hinzu. Wenn sie nachholen, reißt die automatische Skalierung die zusätzlichen VMs ab.

Hinzufügen von Warteschlangen zur Fix It-Anwendung

Um das Warteschlangenmuster zu implementieren, müssen wir zwei Änderungen an der Fix It-App vornehmen.

  • Wenn ein Benutzer eine neue Fix It-Aufgabe übermittelt, legen Sie die Aufgabe in die Warteschlange, anstatt sie in die Datenbank zu schreiben.
  • Erstellen Sie einen Back-End-Dienst, der Nachrichten in der Warteschlange verarbeitet.

Für die Warteschlange verwenden wir den Azure Queue Storage-Dienst. Eine weitere Möglichkeit besteht darin, Azure Service Bus zu verwenden.

Um zu entscheiden, welcher Warteschlangendienst verwendet werden soll, überlegen Sie, wie Ihre App die Nachrichten in der Warteschlange senden und empfangen muss:

  • Wenn Sie über kooperierende Hersteller und konkurrierende Verbraucher verfügen, sollten Sie den Azure Queue Storage-Dienst in Erwägung ziehen. "Kooperierende Produzenten" bedeutet, dass mehrere Prozesse Nachrichten zu einer Warteschlange hinzufügen. "Konkurrierende Verbraucher" bedeutet, dass mehrere Prozesse Nachrichten aus der Warteschlange ziehen, um sie zu verarbeiten, aber jede bestimmte Nachricht kann nur von einem "Consumer" verarbeitet werden. Wenn Sie mehr Durchsatz benötigen als bei einer einzelnen Warteschlange, verwenden Sie zusätzliche Warteschlangen und/oder zusätzliche Speicherkonten.
  • Wenn Sie ein Veröffentlichungs-/Abonnementmodell benötigen, sollten Sie Azure Service Bus Warteschlangen verwenden.

Die Fix It-App passt sich dem kooperierenden Hersteller- und konkurrierenden Verbrauchermodell an.

Ein weiterer Aspekt ist die Anwendungsverfügbarkeit. Der Warteschlangenspeicherdienst ist Teil desselben Diensts, den wir für Blobspeicher verwenden, sodass sich die Verwendung nicht auf unsere SLA auswirkt. Azure Service Bus ist ein separater Dienst mit eigener SLA. Wenn wir Service Bus-Warteschlangen verwenden würden, müssten wir einen zusätzlichen SLA-Prozentsatz berücksichtigen, und unsere zusammengesetzte SLA wäre niedriger. Wenn Sie einen Warteschlangendienst auswählen, stellen Sie sicher, dass Sie die Auswirkungen Ihrer Wahl auf die Anwendungsverfügbarkeit verstehen. Weitere Informationen finden Sie im Abschnitt Ressourcen .

Erstellen von Warteschlangennachrichten

Um eine Fix It-Aufgabe in der Warteschlange zu platzieren, führt das Web-Front-End die folgenden Schritte aus:

  1. Erstellen Sie einen CloudQueueClient-instance. Die CloudQueueClient instance wird verwendet, um Anforderungen für den Warteschlangendienst auszuführen.
  2. Erstellen Sie die Warteschlange, wenn sie noch nicht vorhanden ist.
  3. Serialisieren Sie den Task "Fix It".
  4. Rufen Sie CloudQueue.AddMessageAsync auf, um die Nachricht in der Warteschlange zu platzieren.

Diese Arbeit wird im Konstruktor und SendMessageAsync der Methode einer neuen FixItQueueManager Klasse ausgeführt.

public class FixItQueueManager : IFixItQueueManager
{
    private CloudQueueClient _queueClient;
    private IFixItTaskRepository _repository;

    private static readonly string fixitQueueName = "fixits";

    public FixItQueueManager(IFixItTaskRepository repository)
    {
        _repository = repository;
        CloudStorageAccount storageAccount = StorageUtils.StorageAccount;
        _queueClient = storageAccount.CreateCloudQueueClient();
    }

    // Puts a serialized fixit onto the queue.
    public async Task SendMessageAsync(FixItTask fixIt)
    {
        CloudQueue queue = _queueClient.GetQueueReference(fixitQueueName);
        await queue.CreateIfNotExistsAsync();

        var fixitJson = JsonConvert.SerializeObject(fixIt);
        CloudQueueMessage message = new CloudQueueMessage(fixitJson);

        await queue.AddMessageAsync(message);
    }

    // Processes any messages on the queue.
    public async Task ProcessMessagesAsync()
    {
        CloudQueue queue = _queueClient.GetQueueReference(fixitQueueName);
        await queue.CreateIfNotExistsAsync();

        while (true)
        {
            CloudQueueMessage message = await queue.GetMessageAsync();
            if (message == null)
            {
                break;
            }
            FixItTask fixit = JsonConvert.DeserializeObject<FixItTask>(message.AsString);
            await _repository.CreateAsync(fixit);
            await queue.DeleteMessageAsync(message);
        }
    }
}

Hier verwenden wir die Json.NET-Bibliothek , um den Fixit in das JSON-Format zu serialisieren. Sie können den von Ihnen bevorzugten Serialisierungsansatz verwenden. JSON hat den Vorteil, dass es für Menschen lesbar ist, während es weniger ausführlich als XML ist.

Code in Produktionsqualität fügt Fehlerbehandlungslogik hinzu, hält an, wenn die Datenbank nicht verfügbar ist, behandelt die Wiederherstellung sauberer, erstellt die Warteschlange beim Anwendungsstart und verwaltet "nicht verarbeitbare" Nachrichten. (Eine nicht verarbeitbare Nachricht ist eine Nachricht, die aus irgendeinem Grund nicht verarbeitet werden kann. Sie möchten nicht, dass sich nicht verarbeitbare Nachrichten in der Warteschlange befinden, in der die Workerrolle ständig versucht, sie zu verarbeiten, fehlschlagen, erneut versuchen, fehlschlagen usw.)

In der Front-End-MVC-Anwendung müssen wir den Code aktualisieren, der eine neue Aufgabe erstellt. Rufen Sie die oben gezeigte Methode auf, anstatt die SendMessageAsync Aufgabe im Repository zu platzieren.

public async Task<ActionResult> Create(FixItTask fixittask, HttpPostedFileBase photo)
{
    if (ModelState.IsValid)
    {
        fixittask.CreatedBy = User.Identity.Name;
        fixittask.PhotoUrl = await photoService.UploadPhotoAsync(photo);
        //previous code:
        //await fixItRepository.CreateAsync(fixittask);
        //new code:
        await queueManager.SendMessageAsync(fixittask);
        return RedirectToAction("Success");
    }
    return View(fixittask);
}

Verarbeiten von Warteschlangennachrichten

Um Nachrichten in der Warteschlange zu verarbeiten, erstellen wir einen Back-End-Dienst. Der Back-End-Dienst führt eine Endlosschleife aus, die die folgenden Schritte ausführt:

  1. Rufen Sie die nächste Nachricht aus der Warteschlange ab.
  2. Deserialisieren Sie die Nachricht in eine Fix It-Aufgabe.
  3. Schreiben Sie den Task Fix It in die Datenbank.

Zum Hosten des Back-End-Diensts erstellen wir einen Azure-Clouddienst, der eine Workerrolle enthält. Eine Workerrolle besteht aus mindestens einem virtuellen Computer, der die Back-End-Verarbeitung durchführen kann. Der Code, der auf diesen virtuellen Computern ausgeführt wird, pullt Nachrichten aus der Warteschlange, sobald sie verfügbar sind. Für jede Nachricht deserialisieren wir die JSON-Nutzlast und schreiben eine instance der Fix It Task-Entität in die Datenbank, wobei wir dasselbe Repository verwenden, das wir zuvor auf der Webebene verwendet haben.

Die folgenden Schritte zeigen, wie Sie einer Projektmappe mit einem Standardwebprojekt ein Workerrollenprojekt hinzufügen. Diese Schritte wurden bereits im Projekt "Fix It" ausgeführt, das Sie herunterladen können.

Fügen Sie zunächst der Visual Studio-Projektmappe ein Clouddienstprojekt hinzu. Klicken Sie mit der rechten Maustaste auf die Projektmappe, und wählen Sie Hinzufügen und dann Neues Projekt aus. Erweitern Sie im linken Bereich Visual C# , und wählen Sie Cloud aus.

Screenshot der Schritte zum Hinzufügen eines neuen Projektmenüs in .NET Framework

Erweitern Sie im Dialogfeld Neuer Azure-Clouddienst den Knoten Visual C# im linken Bereich. Wählen Sie Workerrolle aus, und klicken Sie auf das Symbol nach rechts.

Der folgende Screenshot zeigt eine Fortsetzung der vorherigen Abbildung und zeigt die verschiedenen für den Azure Cloud Service verfügbaren Auswahlmöglichkeiten, wobei die richtige hervorgehoben wird.

(Beachten Sie, dass Sie auch eine Webrolle hinzufügen können. Wir könnten das Fix It-Front-End im gleichen Clouddienst ausführen, anstatt ihn auf einer Azure-Website auszuführen. Dies hat einige Vorteile, da verbindungen zwischen Front-End und Back-End einfacher koordiniert werden können. Um diese Demo jedoch einfach zu halten, behalten wir das Front-End in einer Azure App Service Web-App bei und führen das Back-End nur in einem Clouddienst aus.)

Der Workerrolle wird ein Standardname zugewiesen. Um den Namen zu ändern, zeigen Sie mit der Maus auf die Workerrolle im rechten Bereich, und klicken Sie dann auf das Stiftsymbol.

Screenshot: Workerrollenprojekt mit den verschiedenen Zuweisungen und ändern der Namen

Klicken Sie auf OK , um das Dialogfeld abzuschließen. Dadurch werden der Visual Studio-Projektmappe zwei Projekte hinzugefügt.

  • ein Azure-Projekt, das den Clouddienst definiert, einschließlich Konfigurationsinformationen.
  • Ein Workerrollenprojekt, das die Workerrolle definiert.

Screenshot: Workerrolle, Definieren der Rolle und Liste der Projektmappenoptionen

Weitere Informationen finden Sie unter Erstellen eines Azure-Projekts mit Visual Studio.

Innerhalb der Workerrolle rufen wir nachrichten ab, indem wir die ProcessMessageAsync -Methode der -Klasse aufrufen, die FixItQueueManager wir zuvor gesehen haben.

public class WorkerRole : RoleEntryPoint
{
    public override void Run()
    {
        Task task = RunAsync(tokenSource.Token);
        try
        {
            task.Wait();
        }
        catch (Exception ex)
        {
            logger.Error(ex, "Unhandled exception in FixIt worker role.");
        }
    }

    private async Task RunAsync(CancellationToken token)
    {
        using (var scope = container.BeginLifetimeScope())
        {
            IFixItQueueManager queueManager = scope.Resolve<IFixItQueueManager>();
            while (!token.IsCancellationRequested)
            {
                try
                {
                    await queueManager.ProcessMessagesAsync();
                }
                catch (Exception ex)
                {
                    logger.Error(ex, "Exception in worker role Run loop.");
                }
                await Task.Delay(1000);
            }
        }
    }
    // Other code not shown.
}

Die ProcessMessagesAsync -Methode überprüft, ob eine Nachricht wartet. Falls vorhanden, deserialisiert sie die Nachricht in einer FixItTask Entität und speichert die Entität in der Datenbank. Die Schleife wird so lange ausgeführt, bis die Warteschlange leer ist.

public async Task ProcessMessagesAsync()
{
    CloudQueue queue = _queueClient.GetQueueReference(fixitQueueName);
    await queue.CreateIfNotExistsAsync();
    while (true)
    {
        CloudQueueMessage message = await queue.GetMessageAsync();
        if (message == null)
        {
            break;
        }
        FixItTask fixit = JsonConvert.DeserializeObject<FixItTask>(message.AsString);
        await _repository.CreateAsync(fixit);
        await queue.DeleteMessageAsync(message);
    }
}

Das Abfragen von Warteschlangennachrichten verursacht eine geringe Transaktionsgebühr. Wenn also keine Nachricht auf die Verarbeitung wartet, wartet die Methode der Workerrolle RunAsync eine Sekunde, bevor sie erneut durch Aufrufen Task.Delay(1000)von abruft.

In einem Webprojekt kann das Hinzufügen von asynchronem Code die Leistung automatisch verbessern, da IIS einen eingeschränkten Threadpool verwaltet. Dies ist in einem Workerrollenprojekt nicht der Fall. Um die Skalierbarkeit der Workerrolle zu verbessern, können Sie Multithreadcode schreiben oder asynchronen Code verwenden, um parallele Programmierung zu implementieren. Das Beispiel implementiert keine parallele Programmierung, zeigt aber, wie der Code asynchron wird, damit Sie parallele Programmierung implementieren können.

Zusammenfassung

In diesem Kapitel haben Sie erfahren, wie Sie die Reaktionsfähigkeit, Zuverlässigkeit und Skalierbarkeit von Anwendungen verbessern können, indem Sie das warteschlangenzentrierte Arbeitsmuster implementieren.

Dies ist das letzte der 13 Muster, die in diesem E-Book behandelt werden, aber es gibt natürlich viele andere Muster und Methoden, die Ihnen helfen können, erfolgreiche Cloud-Apps zu erstellen. Das letzte Kapitel enthält Links zu Ressourcen für Themen, die in diesen 13 Mustern nicht behandelt wurden.

Ressourcen

Weitere Informationen zu Warteschlangen finden Sie in den folgenden Ressourcen.

Dokumentation:

Video:

  • FailSafe: Erstellen skalierbarer, resilienter Cloud Services. Neunteilige Videoreihe von Ulrich Homann, Marc Mercuri und Mark Simms. Präsentiert allgemeine Konzepte und Architekturprinzipien auf sehr zugängliche und interessante Weise, mit Geschichten, die aus der Erfahrung des Microsoft Customer Advisory Teams (CAT) mit tatsächlichen Kunden stammen. Eine Einführung in den Azure Storage-Dienst und die Warteschlangen finden Sie in Episode 5 ab 35:13.