Teilen über


Versionsverwaltungsprobleme und Ansätze in dauerhaften Funktionen (Azure Functions)

Es ist unvermeidlich, dass Funktionen über die Lebensdauer einer Anwendung hinzugefügt, entfernt und geändert werden. Dauerhafte Funktionen ermöglichen das Verketten von Funktionen in einer Weise, die zuvor nicht möglich war, und diese Verkettung wirkt sich darauf aus, wie Sie die Versionsverwaltung behandeln können.

Arten von Breaking Changes

Es gibt einige Beispiele für wichtige Änderungen, mit denen Sie vertraut sein sollten. In diesem Artikel werden die am häufigsten verwendeten behandelt. Das Hauptthema dahinter ist, dass sowohl neue als auch vorhandene Funktions-Orchestrierungen durch Änderungen am Funktionscode beeinflusst werden.

Ä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 diese Art von Änderung an einer Aktivitäts- oder Entitätsfunktion vorgenommen wird, kann sie jede Orchestratorfunktion unterbrechen, die davon abhängt. Dies 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.

Nehmen wir beispielsweise an, wir haben 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 die Ergebnisse von Foo und übergibt sie an Bar. Angenommen, wir 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, kann aber bei bereits laufenden Instanzen zu Problemen führen. 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, tritt für die Instanz mit dem Prüfpunkt sofort ein Fehler auf, wenn der Vorgang fortgesetzt und der Aufruf von Foo wiedergegeben 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 Laufzeitausnahme für typsichere Sprachen führt.

Dieses Beispiel ist nur eine von vielen verschiedenen 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 Orchestrator-Funktionscode 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);
}

Angenommen, Sie möchten eine Änderung vornehmen, um einen neuen Funktionsaufruf zwischen den beiden vorhandenen Funktionsaufrufen hinzuzufügen.

[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 gibt keine Signaturänderungen. Das Problem tritt auf, wenn die Ausführung einer vorhandenen Instanz nach dem Aufruf von Bar fortgesetzt wird. Wenn für den ursprünglichen Aufruf von Foo das Ergebnis true zurückgegeben wurde, wird bei der Wiedergabe des Orchestrators das SendNotification-Element aufgerufen, das im dazugehörigen Ausführungsverlauf nicht enthalten ist. Die Runtime erkennt diese Inkonsistenz und löst einen Fehler aufgrund einer nicht deterministischen Orchestrierung aus, da ein Aufruf von SendNotification ermittelt wurde, obwohl ein Aufruf von Bar erwartet wurde. 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, das Aufrufen von Unter-Orchestrierungen usw.

Minderungsstrategien

Hier sind einige der Strategien für den Umgang mit Versionsverwaltungsproblemen:

  • 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 reagieren möglicherweise auf unbestimmte Zeit nicht und melden den Status Running.
  • Wenn eine Funktion entfernt wird und eine andere Funktion versucht, sie aufzurufen, kann dies zu einem Fehler führen.
  • Wenn eine Funktion nach der geplanten Ausführung entfernt wird, kann es bei der App während der Laufzeit zu Fehlern auf niedrigerer Ebene in der Engine "Durable Task Framework" kommen, was zu erheblicher Leistungsverschlechterung führt.

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

Orchestrierungsversionierung

Hinweis

Die Orchestrierungsversionsverwaltung befindet sich derzeit in der öffentlichen Vorschau.

Die Orchestrierungsversionsfunktion ermöglicht es verschiedenen Versionen von Orchestrierungen, gleichzeitig ohne Konflikte und Nicht-Determinismus-Probleme zu koexistieren und auszuführen, sodass Updates bereitgestellt werden können, während In-Flight-Orchestrierungsinstanzen ohne manuelle Eingriffe ausgeführt werden können.

Mit Orchestrierungsversionierung:

  • Jede Orchestrierungsinstanz erhält eine Version, die dauerhaft damit verknüpft ist, sobald sie erstellt wird.
  • Orchestrator-Funktionen können ihre Version und Verzweigungsausführung entsprechend untersuchen
  • 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.

Diese Strategie wird für Anwendungen empfohlen, die unterbrechungsfreie Änderungen unterstützen müssen, während Bereitstellungen ohne Ausfallzeiten beibehalten werden.

Ausführliche Anleitungen zur Konfiguration und Implementierung finden Sie unter "Orchestrierungsversionierung" in "Durable Functions".

Beendigung aller ausgeführten Instanzen

Eine andere Möglichkeit besteht darin, alle ausgeführten Instanzen zu beenden. Wenn Sie den standardmäßigen Azure Storage-Anbieter für Durable Functions verwenden, können alle Instanzen beendet werden, indem Sie den Inhalt der internen Kontrollwarteschlange und Arbeitsaufgabenwarteschlange löschen. Alternativ können Sie die Funktions-App beenden, diese Warteschlangen löschen und die App erneut 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 "Wird ausgeführt", aber sie überladen Ihre Protokolle nicht mit Fehlermeldungen und schaden Ihrer App nicht. Dieser Ansatz ist ideal bei der schnellen 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 sicherste Methode, um sicherzustellen, dass Breaking Changes sicher bereitgestellt werden, besteht darin, sie nebeneinander mit Ihren älteren Versionen bereitzustellen. Dazu können Sie eine der folgenden Techniken verwenden:

  • Stellen Sie alle Updates als vollständig neue Funktionen bereit und lassen Sie die vorhandenen Funktionen as-isunverändert. Dies 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. Dies führt zur Erstellung neuer Speicherartefakte, die von der neuen Version Ihrer App verwendet werden können. 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 Speicheranbieter für dauerhafte Funktionen.

Bereitstellungsslots

Bei parallelen Bereitstellungen in Azure Functions oder Azure App Service wird empfohlen, die neue Version der Funktions-App in einem neuen Bereitstellungsplatz bereitzustellen. Bereitstellungsplätze ermöglichen es Ihnen, mehrere Kopien Ihrer Funktions-App nebeneinander auszuführen, wobei nur einer 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