Versionsutmaningar och åtgärdsstrategier i Durable Functions

Versionshantering i Durable Functions är viktigt eftersom funktioner oundvikligen läggs till, tas bort och ändras under programmets livslängd. Durable Functions kan du länka ihop funktioner på sätt som inte tidigare var möjliga, och den här länkningen påverkar hur du hanterar versionshantering.

Den här artikeln hjälper dig:

Snabbstrategijämförelse

Om du redan vet att ändringen orsakar problem, använd den här tabellen för att välja en strategi för att mildra effekterna.

Strategi Bäst för Detaljer
Versionshantering för orkestrering (rekommenderas) De flesta applikationer med väsentliga förändringar. Inbyggd körningsfunktion, fungerar med valfri lagringsbackend. Hoppa till avsnittet
Distributioner sida vid sida Appar som inte kan använda orkestreringsversioner eller som behöver fullständig isolering via separata aktivitetshubbar eller lagringskonton. Hoppa till avsnittet
Stoppa alla instanser under flygning Prototyper och lokal utveckling där det är acceptabelt att förlora orkestreringar under flygning. Hoppa till avsnittet

Tips/Råd

Om du letar efter den inbyggda versionshanteringsfunktionen för orkestrering som ger automatisk versionsisolering på körningsnivå kan du läsa Versionshantering för orkestrering.

Viktigt!

Innan du distribuerar kontrollerar du om ändringen är en icke-bakåtkompatibel ändring:

  • Har du ändrat namn, indatatyp eller utdatatyp för en aktivitet eller entitetsfunktion?
  • Har du lagt till, ta bort eller ordna om anrop till aktiviteter, underorkestreringar, timers eller externa händelser i orchestrator-kod?
  • Bytte du namn på eller tog du bort en funktion som orkestreringar under flygning fortfarande kan anropa?

Om du svarade ja på något av dessa så använd en av riskreduceringsstrategierna nedan för att undvika misslyckanden vid körning av orkestreringar.

Typer av brytande ändringar

Det finns flera exempel på förändringar som bryter kompatibilitet. I den här artikeln beskrivs de vanligaste typerna. Huvudtemat bakom dem är att ändringar i funktionskoden påverkar både nya och befintliga funktionsorkestreringar.

Ändringar i aktivitets- eller entitetsfunktionens signatur

En signaturändring refererar till en ändring i namnet, indata eller utdata för en funktion. Om du gör den här typen av ändringar i en aktivitet eller entitetsfunktion kan den bryta alla orkestreringsfunktioner som är beroende av den. Det här beteendet gäller särskilt för typsäkra språk. Om du uppdaterar orchestrator-funktionen för att hantera den här ändringen kan du bryta befintliga instanser under flygning.

Tänk dig till exempel följande orchestrator-funktion.

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

Den här funktionen tar resultatet av Foo och skickar det till Bar. Anta att du behöver ändra returvärdet för Foo från ett booleskt värde till en sträng för att stödja en bredare mängd resultatvärden. Resultatet ser ut så här:

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

Den här ändringen fungerar bra för alla nya instanser av orchestrator-funktionen men kan bryta alla instanser under flygning. Tänk till exempel på fallet där en orkestreringsinstans anropar en funktion med namnet Foo, hämtar tillbaka ett booleskt värde och sedan kontrollpunkter. Om signaturändringen distribueras vid denna tidpunkt misslyckas den kontrollpunktslagrade instansen omedelbart när den återupptas och anropet återupprepas till Foo. Det här felet inträffar eftersom resultatet i historiktabellen är ett booleskt värde, men den nya koden försöker deserialisera den till ett Strängvärde, vilket resulterar i oväntat beteende eller till och med ett körningsundantag för typsäkra språk.

Det här exemplet är ett av många sätt som en funktionssignaturändring kan bryta befintliga instanser på. I allmänhet, om en orkestrerare behöver ändra hur den anropar en funktion, kommer ändringen sannolikt att vara problematisk.

Orkestrator-logikändringar

Den andra klassen av versionsproblem härrör från att ändra orkestratorfunktionskoden på ett sätt som ändrar utförandebanan för pågående instanser.

Överväg följande orchestrator-funktion:

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

Anta nu att du vill lägga till ett nytt funktionsanrop mellan de två befintliga funktionsanropen.

[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);
}

Den här ändringen lägger till ett nytt funktionsanrop till SendNotification mellan Foo och Bar. Det finns inga signaturändringar. Problemet uppstår när en befintlig instans fortsätter efter anropet till Bar. Om det ursprungliga anropet till Foo returnerades trueunder reprisen anropar orchestrator-reprisen till SendNotification, vilket inte finns i dess körningshistorik. Körtiden identifierar den här inkonsekvensen och genererar ett icke-deterministiskt orkestreringsfel eftersom den påträffade ett anrop till SendNotification när den förväntade sig att se ett anrop till Bar. Samma typ av problem kan uppstå när du lägger till API-anrop till andra varaktiga åtgärder, som att skapa varaktiga timers, vänta på externa händelser eller anropa underorkestreringar.

Mildringsstrategier

Varning

Om du implementerar brytande ändringar utan en åtgärdsstrategi (metoden "gör ingenting") kan det leda till att orkestreringar misslyckas med icke-deterministiska orkestreringsfel, fastnar på obestämd tid i ett Running-status, eller utlöser prestandanedbrytande körningsfel på låg nivå. Använd alltid någon av följande strategier när du distribuerar icke-bakåtkompatibla ändringar.

Till skillnad från de andra strategierna i det här avsnittet är orkestreringsversioner en inbyggd körningsfunktion som ger automatisk versionsisolering. Du behöver inte hantera separata distributioner, aktivitetshubbar eller lagringskonton. I stället spårar själva körningen versionsinformation och ser till att orkestreringsinstanser bearbetas av kompatibla arbetare.

Med versionshantering för orkestrering:

  • Varje orkestreringsinstans hämtar en version som är permanent associerad med den när den skapas.
  • Orchestrator-funktioner kan granska sin version och exekvera grenar i enlighet därmed, vilket behåller gamla och nya kodsökvägar i samma kodbas.
  • Arbetare som kör nyare orchestrator-funktionsversioner kan fortsätta köra orkestreringsinstanser som skapats av äldre versioner.
  • Körtiden förhindrar att arbetare som använder äldre versioner av orchestrator-funktioner utför orkestreringar med nyare versioner.

Den här metoden kräver minimal konfiguration (en versionssträng och valfri matchningsstrategi) och är kompatibel med alla lagringsleverantörer. Det är den rekommenderade strategin för applikationer som behöver stöda brytande ändringar samtidigt som driftsättningar utan avbrott bibehålls.

Detaljerad konfigurations- och implementeringsvägledning finns i Versionshantering för orkestrering.

Stoppa alla instanser under flygning

Ett annat alternativ är att stoppa alla instanser under flygning. Om du använder standardprovidern Azure Storage för Durable Functions, avslutar du alla instanser genom att rensa innehållet i de interna köerna control-queue och workitem-queue. Du kan också stoppa funktionsappen, ta bort dessa köer och starta om appen. Köerna återskapas automatiskt när appen startas om. De tidigare orkestreringsinstanserna kan förbli i tillståndet "Körs" på obestämd tid, men de stör inte loggarna med felmeddelanden eller orsakar skada för din app. Den här metoden är idealisk för snabb prototyputveckling, inklusive lokal utveckling.

Varning

Den här metoden kräver direkt åtkomst till de underliggande lagringsresurserna och är inte lämplig för alla lagringsleverantörer som stöds av Durable Functions.

Sida vid sida driftsättningar

Det mest felsäkra sättet att säkerställa att ändringar distribueras på ett säkert sätt är genom att distribuera dem parallellt med dina äldre versioner. Du kan använda någon av följande tekniker:

  • Olika lagringskonto: Distribuera alla uppdateringar som en ny funktionsapp med ett annat lagringskonto. Detta isolerar helt den nya versionens tillstånd från den gamla versionen.
  • Annan aktivitetshubb: Distribuera en ny kopia av funktionsappen med samma lagringskonto men med ett uppdaterat namn på aktivitetshubben . Den här metoden skapar nya lagringsartefakter för den nya versionen medan den gamla versionen fortsätter att använda sina befintliga artefakter.

När du gör distributioner sida vid sida i Azure kan du använda implementeringsplatser för att köra båda versionerna samtidigt med bara en som aktiv produktionsplats. När du är redo att exponera den nya orkestreringslogiken byter du den nya versionen till produktionsmiljön.

Anmärkning

Den här vägledningen använder Azure Storage specifika termer, men gäller vanligtvis för alla Durable Functions lagringsproviders som stöds.

Anmärkning

Distributionsfacksväxlingar fungerar bäst med HTTP- och webhook-utlösare. För icke-HTTP-utlösare som köer eller händelsehubbar ska utlösardefinitionen härledas från en appinställning som uppdateras som en del av växlingsåtgärden.

Nästa steg