Freigeben über


Herausforderungen und Ansätze bei der Versionsverwaltung in Durable Functions

Funktionen werden im Laufe der Lebensdauer einer Anwendung zwangsläufig hinzugefügt, entfernt und geändert. mit Durable Functions können Sie Funktionen so verketten, dass sie nicht zuvor möglich waren, und diese Verkettung wirkt sich auf die Behandlung der Versionsverwaltung aus.

Tipp

In diesem Artikel werden allgemeine Versionsverwaltungsprobleme und Strategien auf Bereitstellungsebene für die Behandlung erläutert. Wenn Sie nach der integrierten Orchestrierungsversionierung suchen, die automatische Versionsisolation auf Laufzeitebene bietet, lesen Sie Orchestrierungsversionierung.

Von Bedeutung

Wenn Sie Codeänderungen bereitstellen, die sich auf die Ausführung von Orchestrierungen auswirken, können inkorrekte Bereitstellungen zu Ausfällen mit nicht deterministischen Fehlern führen, dauerhaft hängen bleiben oder Leistungsbeeinträchtigungen verursachen. Befolgen Sie die in diesem Artikel beschriebenen empfohlenen Entschärfungsstrategien , wenn Sie Änderungen vornehmen, die sich auf In-Flight-Orchestrierungen auswirken können.

Arten von Breaking Changes

Es gibt mehrere Beispiele für bruchbrechende Änderungen. In diesem Artikel werden die am häufigsten verwendeten Typen erläutert. Das Hauptthema dahinter ist, dass Änderungen an Funktionscode sowohl neue als auch vorhandene Funktions-Orchestrierungen betreffen.

Ändern von Aktivitäts- oder Entitätsfunktionssignaturen

Eine Signaturänderung bezieht sich auf eine Änderung des Namens, der Eingabe oder der Ausgabe einer Funktion. Wenn Sie diese Art von Änderung an einer Aktivitäts- oder Entitätsfunktion vornehmen, könnte sie jede Orchestratorfunktion unterbrechen, die davon abhängt. Dieses Verhalten gilt insbesondere für typsichere Sprachen. Wenn Sie die Orchestratorfunktion so aktualisieren, dass diese Änderung berücksichtigt wird, können Sie vorhandene In-Flight-Instanzen unterbrechen.

Betrachten Sie beispielsweise die folgende Orchestratorfunktion.

[FunctionName("FooBar")]
public static Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
    bool result = await context.CallActivityAsync<bool>("Foo");
    await context.CallActivityAsync("Bar", result);
}

Diese vereinfachende Funktion übernimmt das Ergebnis von Foo und übergibt sie an Bar. Angenommen, Sie müssen den Rückgabewert von Foo von einem booleschen In eine Zeichenfolge ändern, um eine größere Vielfalt von Ergebniswerten zu unterstützen. Das Ergebnis sieht wie folgt aus:

[FunctionName("FooBar")]
public static Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
    string result = await context.CallActivityAsync<string>("Foo");
    await context.CallActivityAsync("Bar", result);
}

Diese Änderung funktioniert für alle neuen Instanzen der Orchestratorfunktion ohne Probleme, aber für In-Flight-Instanzen können Fehler auftreten. Betrachten Sie z. B. den Fall, in dem eine Orchestrierungsinstanz eine Funktion namens Fooaufruft, einen booleschen Wert zurückgibt, und dann Prüfpunkte. Wenn die Signaturänderung an diesem Punkt bereitgestellt wird, schlägt die Instanz mit dem Prüfpunkt sofort fehl, wenn der Vorgang fortgesetzt und der Aufruf von Foo wiederholt wird. Dieser Fehler tritt auf, da das Ergebnis in der Verlaufstabelle ein boolescher Wert ist, aber der neue Code versucht, ihn in einen String-Wert zu deserialisieren, was zu unerwartetem Verhalten oder sogar einer Laufzeitausnahme für typsichere Sprachen führt.

Dieses Beispiel ist eine von vielen Möglichkeiten, wie eine Funktionssignaturänderung vorhandene Instanzen unterbrechen kann. Wenn ein Orchestrator im Allgemeinen die Art und Weise ändern muss, wie eine Funktion aufgerufen wird, ist die Änderung wahrscheinlich problematisch.

Ändern der Orchestratorlogik

Die andere Klasse von Versionsverwaltungsproblemen besteht darin, den Orchestratorfunktionscode so zu ändern, dass der Ausführungspfad für In-Flight-Instanzen geändert wird.

Betrachten Sie die folgende Orchestratorfunktion:

[FunctionName("FooBar")]
public static Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
    bool result = await context.CallActivityAsync<bool>("Foo");
    await context.CallActivityAsync("Bar", result);
}

Gehen Sie nun davon aus, dass Sie einen neuen Funktionsaufruf zwischen den beiden vorhandenen Funktionsaufrufen hinzufügen möchten.

[FunctionName("FooBar")]
public static Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
    bool result = await context.CallActivityAsync<bool>("Foo");
    if (result)
    {
        await context.CallActivityAsync("SendNotification");
    }

    await context.CallActivityAsync("Bar", result);
}

Diese Änderung fügt einen neuen Funktionsaufruf zu SendNotification zwischen Foo und Bar hinzu. Es sind keine Signaturänderungen vorhanden. Das Problem tritt auf, wenn die Ausführung einer vorhandenen Instanz nach dem Aufruf von Bar fortgesetzt wird. Wenn bei der Wiederholung der ursprüngliche Aufruf von Foo den Wert true zurückgegeben hat, ruft die Wiederholung des Orchestrators SendNotification auf, was sich nicht im Ausführungsverlauf befindet. Die Laufzeit erkennt diese Inkonsistenz und löst einen nicht deterministischen Orchestrierungsfehler aus, weil sie einen Aufruf an SendNotification festgestellt hat, als sie einen Aufruf an Bar erwartete. Dieselbe Art von Problem kann auftreten, wenn API-Aufrufe zu anderen dauerhaften Vorgängen hinzugefügt werden, z. B. das Erstellen dauerhafter Zeitgeber, das Warten auf externe Ereignisse oder das Aufrufen von Unter-Orchestrierungen.

Minderungsstrategien

Hier sind Strategien zum Umgang mit Versionsverwaltungsproblemen. Die Versionsverwaltung der Orchestrierung ist ein integriertes Laufzeitfeature, das die Versionsisolation automatisch verarbeitet, während die verbleibenden Optionen Problemumgehungen auf Bereitstellungsebene sind:

  • Nichts tun (nicht empfohlen)
  • Orchestrierungsversionierung (in den meisten Fällen empfohlen)
  • Beendigung aller ausgeführten Instanzen
  • Parallele Bereitstellungen

Keine Maßnahmen

Der naive Ansatz bei der Versionsverwaltung besteht darin, nichts zu tun und In-Flight-Orchestrierungsinstanzen fehlschlagen zu lassen. Je nach Änderungstyp können die folgenden Arten von Fehlern auftreten.

  • Orchestrierungen können mit einem nicht deterministischen Orchestrierungsfehler fehlschlagen.
  • Orchestrierungen können unendlich stecken bleiben und einen Running Status melden.
  • Wenn eine Funktion entfernt wird, kann jede Funktion, die versucht, sie aufzurufen, mit einem Fehler fehlschlagen.
  • Wenn eine Funktion entfernt wird, nachdem sie zur Ausführung geplant wurde, kann es zu Laufzeitfehlern auf niedriger Ebene in der Engine des 'Durable Task Frameworks' kommen, was zu einer erheblichen Leistungsverschlechterung führt.

Aufgrund dieser potenziellen Fehler wird die Strategie "Nichts tun" nicht empfohlen.

Orchestrierungsversionierung

Im Gegensatz zu den anderen Strategien in diesem Abschnitt ist die Orchestrierungsversionierung eine integrierte Laufzeitfunktion, die automatische Versionsisolation bereitstellt. Sie müssen keine separaten Bereitstellungen, Aufgabenhubs oder Speicherkonten verwalten. Stattdessen verfolgt die Laufzeit selbst Versionsinformationen und stellt sicher, dass Orchestrierungsinstanzen von kompatiblen Workern verarbeitet werden.

Mit Orchestrierungsversionierung:

  • Jede Orchestrierungsinstanz erhält eine Version, die bei der Erstellung dauerhaft damit verknüpft ist.
  • Orchestrator-Funktionen können ihre Version untersuchen und die Ausführung entsprechend verzweigen, wodurch alte und neue Codepfade in derselben Codebasis gehalten werden.
  • Mitarbeiter, die neuere Orchestratorfunktionsversionen ausführen, können weiterhin Orchestrierungsinstanzen ausführen, die von älteren Versionen erstellt wurden.
  • Die Laufzeit verhindert, dass Mitarbeiter, die ältere Orchestratorfunktionsversionen ausführen, Orchestrierungen neuerer Versionen ausführen.

Dieser Ansatz erfordert eine minimale Konfiguration (eine Versionszeichenfolge und eine optionale Übereinstimmungsstrategie) und ist mit jedem Speicheranbieter kompatibel. Dies ist die empfohlene Strategie für Anwendungen, die Breaking Changes unterstützen müssen und gleichzeitig Bereitstellungen ohne Ausfallzeiten gewährleisten sollen.

Ausführliche Anleitungen zur Konfiguration und Implementierung finden Sie unter Orchestration Versioning.

Beendigung aller ausgeführten Instanzen

Eine andere Möglichkeit besteht darin, alle ausgeführten Instanzen zu beenden. Wenn Sie den Standardanbieter Azure Storage für Durable Functions verwenden, beenden Sie alle Instanzen, indem Sie den Inhalt der internen control-queue und workitem-queueWarteschlange löschen. Alternativ können Sie die Funktions-App beenden, diese Warteschlangen löschen und die App neu starten. Die Warteschlangen werden automatisch neu erstellt, sobald die App neu gestartet wird. Die vorherigen Orchestrierungsinstanzen bleiben möglicherweise auf unbestimmte Zeit im Zustand "Ausführen", aber sie überladen Ihre Protokolle nicht mit Fehlermeldungen oder verursachen Schäden an Ihrer App. Dieser Ansatz eignet sich ideal für die schnelle Prototypentwicklung, einschließlich der lokalen Entwicklung.

Hinweis

Dieser Ansatz erfordert direkten Zugriff auf die zugrunde liegenden Speicherressourcen und ist möglicherweise nicht für alle Speicheranbieter geeignet, die von Durable Functions unterstützt werden.

Parallele Bereitstellungen

Die beste Möglichkeit zur Garantie, dass wichtige Änderungen auf sichere Weise bereitgestellt werden, ist die parallele Bereitstellung mit Ihren älteren Versionen. Verwenden Sie eine der folgenden Techniken:

  • Stellen Sie alle Updates als vollständig neue Funktionen bereit und lassen Sie die vorhandenen Funktionen as-isunverändert. Dieser Ansatz wird in der Regel aufgrund der Komplexität, die an der rekursiven Aktualisierung der Aufrufer der neuen Funktionsversionen beteiligt ist, nicht empfohlen.
  • Stellen Sie alle Updates als neue Funktions-App mit einem anderen Speicherkonto bereit.
  • Stellen Sie eine neue Kopie der Funktions-App mit demselben Speicherkonto, aber mit einem aktualisierten Aufgabenhubnamen bereit. Dieser Ansatz führt zur Erstellung neuer Speicherartefakte, die die neue Version Ihrer App verwenden kann. Die alte Version Ihrer App wird weiterhin mit dem vorherigen Satz von Speicherartefakten ausgeführt.

Die parallele Bereitstellung ist die empfohlene Technik für die Bereitstellung neuer Versionen Ihrer Funktions-Apps.

Hinweis

Dieser Leitfaden für die parallele Bereitstellungsstrategie verwendet Azure Storage spezifische Begriffe, gilt jedoch im Allgemeinen für alle unterstützten Durable Functions-Speicheranbieter.

Bereitstellungsslots

Stellen Sie bei parallelen Bereitstellungen in Azure Functions oder Azure App Service die neue Version der Funktions-App in einem neuen Deployment-Slot bereit. Bereitstellungsplätze ermöglichen es Ihnen, mehrere Kopien Ihrer Funktions-App nebeneinander auszuführen, wobei nur eine davon als aktiver Produktionsplatz verwendet wird. Wenn alles bereit ist, um die neue Orchestrierungslogik für Ihre vorhandene Infrastruktur verfügbar zu machen, kann dies ein einfacher Vorgang sein, weil ggf. nur die neue Version in den Produktionsslot eingefügt werden muss.

Hinweis

Diese Strategie funktioniert am besten, wenn Sie HTTP- und Webhook-Trigger für Orchestratorfunktionen verwenden. Bei Nicht-HTTP-Triggern, z. B. Warteschlangen oder Event Hubs, sollte die Triggerdefinition von einer App-Einstellung abgeleitet werden, die als Teil des Swapvorgangs aktualisiert wird.

Nächste Schritte