Versionshantering i Durable Functions (Azure Functions)

Det är oundvikligt att funktioner läggs till, tas bort och ändras under ett programs livslängd. Durable Functions möjliggör sammanlänkning av funktioner på sätt som inte tidigare var möjliga, och den här länkningen påverkar hur du kan hantera versionshantering.

Hantera icke-bakåtkompatibla ändringar

Det finns flera exempel på icke-bakåtkompatibla ändringar att känna till. I den här artikeln beskrivs de vanligaste. Huvudtemat bakom dem alla är att både nya och befintliga funktionsorkestreringar påverkas av ändringar i funktionskoden.

Ändra aktivitets- eller entitetsfunktionssignaturer

En signaturändring refererar till en ändring i namnet, indata eller utdata för en funktion. Om den här typen av ändring görs i en aktivitet eller entitetsfunktion kan den bryta alla orkestreringsfunktioner som är beroende av den. Detta 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.

Anta till exempel att vi har 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 förenklade funktionen tar resultaten från Foo och skickar den till Bar. Anta att vi 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 den här tidpunkten misslyckas den kontrollpunktsinstansen omedelbart när den återupptas och anropet spelas upp igen 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 körningsundantag för typsäkra språk.

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

Ändra orkestreringslogik

Den andra klassen av versionsproblem kommer från att ändra orchestrator-funktionskoden på ett sätt som ändrar körningssökvägen för instanser under flygning.

Ö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 göra en ändring för att 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 återupptas från anropet till fältet. Om det ursprungliga anropet till Foo returnerades under reprisen anropar trueorchestrator-reprisen SendNotification, som inte finns i dess körningshistorik. Körningen identifierar den här inkonsekvensen och genererar ett icke-deterministiskt orkestreringsfel eftersom det påträffade ett anrop till SendNotification när det förväntades se ett anrop till fältet. 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, anropa underorkestreringar osv.

Riskreduceringsstrategier

Här är några av strategierna för att hantera versionsutmaningar:

  • Gör ingenting (rekommenderas inte)
  • Stoppa alla instanser under flygning
  • Distributioner sida vid sida

Gör ingenting

Den naiva metoden för versionshantering är att inte göra någonting och låta orkestreringsinstanser under flygning misslyckas. Beroende på typen av ändring kan följande typer av fel inträffa.

  • Orkestreringar kan misslyckas med ett icke-deterministiskt orkestreringsfel .
  • Orkestreringar kan fastna på obestämd tid och rapportera status Running .
  • Om en funktion tas bort kan alla funktioner som försöker anropa den misslyckas med ett fel.
  • Om en funktion tas bort efter att den har schemalagts att köras kan appen uppleva körningsfel på låg nivå i Durable Task Framework-motorn, vilket kan leda till allvarlig prestandaförsämring.

På grund av dessa potentiella fel rekommenderas inte strategin "gör ingenting".

Stoppa alla instanser under flygning

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

Anteckning

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

Distributioner sida vid sida

Det mest felsäkra sättet att säkerställa att icke-bakåtkompatibla ändringar distribueras på ett säkert sätt är genom att distribuera dem sida vid sida med dina äldre versioner. Detta kan göras med någon av följande tekniker:

  • Distribuera alla uppdateringar som helt nya funktioner och lämna befintliga funktioner som de är. Detta rekommenderas vanligtvis inte på grund av komplexiteten i att rekursivt uppdatera anroparna för de nya funktionsversionerna.
  • Distribuera alla uppdateringar som en ny funktionsapp med ett annat lagringskonto.
  • Distribuera en ny kopia av funktionsappen med samma lagringskonto men med ett uppdaterat namn på aktivitetshubben . Detta resulterar i skapandet av nya lagringsartefakter som kan användas av den nya versionen av din app. Den gamla versionen av din app fortsätter att köras med hjälp av den tidigare uppsättningen lagringsartefakter.

Distribution sida vid sida är den rekommenderade tekniken för att distribuera nya versioner av dina funktionsappar.

Anteckning

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

Distributionsfack

När du gör distributioner sida vid sida i Azure Functions eller Azure App Service rekommenderar vi att du distribuerar den nya versionen av funktionsappen till ett nytt distributionsfack. Med distributionsfack kan du köra flera kopior av funktionsappen sida vid sida med bara en av dem som aktiv produktionsplats . När du är redo att exponera den nya orkestreringslogik för din befintliga infrastruktur kan det vara så enkelt som att växla den nya versionen till produktionsplatsen.

Anteckning

Den här strategin fungerar bäst när du använder HTTP- och webhook-utlösare för orchestrator-funktioner. För icke-HTTP-utlösare, till exempel köer eller händelsehubbar, bör utlösardefinitionen härledas från en appinställning som uppdateras som en del av växlingsåtgärden.

Nästa steg