Versiebeheer in Durable Functions (Azure Functions)

Het is onvermijdelijk dat functies worden toegevoegd, verwijderd en gewijzigd gedurende de levensduur van een toepassing. Durable Functions staat het koppelen van functies toe op manieren die voorheen niet mogelijk waren, en deze ketening is van invloed op de manier waarop u versiebeheer kunt afhandelen.

Belangrijke wijzigingen afhandelen

Er zijn verschillende voorbeelden van wijzigingen die fouten veroorzaken. In dit artikel worden de meest voorkomende besproken. Het belangrijkste thema achter al deze indelingen is dat zowel nieuwe als bestaande functieindelingen worden beïnvloed door wijzigingen in de functiecode.

Handtekeningen voor activiteits- of entiteitsfuncties wijzigen

Een handtekeningwijziging verwijst naar een wijziging in de naam, invoer of uitvoer van een functie. Als dit soort wijziging wordt aangebracht in een activiteit of entiteitsfunctie, kan dit elke orchestratorfunctie die ervan afhankelijk is, breken. Dit geldt met name voor typeveilige talen. Als u de orchestrator-functie bijwerkt om deze wijziging aan te passen, kunt u bestaande in-flight-exemplaren breken.

Stel dat we de volgende orchestratorfunctie hebben.

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

Deze simplistische functie neemt de resultaten van Foo en geeft deze door aan Bar. Laten we aannemen dat we de retourwaarde van Foo moeten wijzigen van een Booleaanse waarde in een tekenreeks om een grotere verscheidenheid aan resultaatwaarden te ondersteunen. Het resultaat ziet er als volgt uit:

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

Deze wijziging werkt prima voor alle nieuwe exemplaren van de orchestrator-functie, maar kan eventuele in-flight-exemplaren breken. Denk bijvoorbeeld aan het geval waarbij een orchestration-exemplaar een functie aanroept met de naam Foo, een booleaanse waarde terugkrijgt en vervolgens controlepunten. Als de handtekeningwijziging op dit punt wordt geïmplementeerd, mislukt het controlepuntexemplaar onmiddellijk wanneer de aanroep naar Foowordt hervat en opnieuw wordt afgespeeld. Deze fout treedt op omdat het resultaat in de geschiedenistabel een Booleaanse waarde is, maar de nieuwe code deze probeert te deserialiseren in een tekenreekswaarde, wat resulteert in onverwacht gedrag of zelfs een runtime-uitzondering voor typeveilige talen.

Dit voorbeeld is slechts een van de vele verschillende manieren waarop een wijziging in de functiehandtekening bestaande exemplaren kan breken. Over het algemeen geldt dat als een orchestrator de manier moet wijzigen waarop een functie wordt aangeroepen, de wijziging waarschijnlijk problematisch is.

Orchestratorlogica wijzigen

De andere klasse van versiebeheerproblemen zijn afkomstig van het wijzigen van de orchestrator-functiecode op een manier die het uitvoeringspad voor in-flight-exemplaren wijzigt.

Houd rekening met de volgende orchestratorfunctie:

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

Laten we nu aannemen dat u een wijziging wilt aanbrengen om een nieuwe functie-aanroep toe te voegen tussen de twee bestaande functie-aanroepen.

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

Met deze wijziging wordt een nieuwe functie-aanroep toegevoegd aan SendNotification tussen Foo en Bar. Er zijn geen wijzigingen in de handtekening. Het probleem doet zich voor wanneer een bestaand exemplaar wordt hervat vanuit de aanroep naar Balk. Als tijdens het afspelen de oorspronkelijke aanroep naar Foo is geretourneerd true, wordt sendNotification door de orchestrator opnieuw afgespeeld, wat niet in de uitvoeringsgeschiedenis staat. De runtime detecteert deze inconsistentie en genereert een niet-deterministische indelingsfout omdat er een aanroep van SendNotification is opgetreden terwijl er een aanroep naar Bar werd verwacht. Hetzelfde type probleem kan optreden bij het toevoegen van API-aanroepen aan andere duurzame bewerkingen, zoals het maken van duurzame timers, het wachten op externe gebeurtenissen, het aanroepen van subindelingen, enzovoort.

Strategieën voor risicobeperking

Hier volgen enkele van de strategieën voor het oplossen van problemen met versiebeheer:

  • Niets doen (niet aanbevolen)
  • Alle in-flight-exemplaren stoppen
  • Implementaties naast elkaar

Niets doen

De naïeve benadering van versiebeheer is om niets te doen en in-flight indelingsexemplaren te laten mislukken. Afhankelijk van het type wijziging kunnen de volgende typen fouten optreden.

  • Indelingen kunnen mislukken met een niet-deterministische indelingsfout .
  • Indelingen kunnen voor onbepaalde tijd vastlopen en een Running status rapporteren.
  • Als een functie wordt verwijderd, kan elke functie die deze probeert aan te roepen, mislukken met een fout.
  • Als een functie wordt verwijderd nadat deze volgens de planning is uitgevoerd, kan de app te maken hebben met runtimefouten op laag niveau in de Durable Task Framework-engine, wat kan leiden tot ernstige prestatievermindering.

Vanwege deze potentiële fouten wordt de strategie 'niets doen' afgeraden.

Alle in-flight-exemplaren stoppen

Een andere optie is om alle in-flight exemplaren te stoppen. Als u de standaard Azure Storage-provider voor Durable Functions gebruikt, kunt u alle exemplaren stoppen door de inhoud van de wachtrijen voor interne besturingselementen en werkitem-wachtrijen te wissen. U kunt de functie-app ook stoppen, deze wachtrijen verwijderen en de app opnieuw starten. De wachtrijen worden automatisch opnieuw gemaakt zodra de app opnieuw wordt opgestart. De vorige indelingsexemplaren kunnen voor onbepaalde tijd in de status 'Actief' blijven, maar ze zullen uw logboeken niet rommelen met foutberichten en uw app niet beschadigen. Deze aanpak is ideaal bij snelle ontwikkeling van prototypen, inclusief lokale ontwikkeling.

Notitie

Deze benadering vereist directe toegang tot de onderliggende opslagresources en is niet geschikt voor alle opslagproviders die worden ondersteund door Durable Functions.

Implementaties naast elkaar

De meest foutbestendige manier om ervoor te zorgen dat wijzigingen die fouten veroorzaken veilig worden geïmplementeerd, is door ze naast uw oudere versies te implementeren. U kunt dit doen met behulp van een van de volgende technieken:

  • Implementeer alle updates als volledig nieuwe functies, waarbij bestaande functies in de huidige staat blijven. Dit wordt over het algemeen niet aanbevolen vanwege de complexiteit van het recursief bijwerken van de aanroepers van de nieuwe functieversies.
  • Implementeer alle updates als een nieuwe functie-app met een ander opslagaccount.
  • Implementeer een nieuwe kopie van de functie-app met hetzelfde opslagaccount, maar met een bijgewerkte taakhubnaam . Dit resulteert in het maken van nieuwe opslagartefacten die kunnen worden gebruikt door de nieuwe versie van uw app. De oude versie van uw app blijft worden uitgevoerd met behulp van de vorige set opslagartefacten.

Implementatie naast elkaar is de aanbevolen techniek voor het implementeren van nieuwe versies van uw functie-apps.

Notitie

Deze richtlijnen voor de implementatiestrategie naast elkaar maken gebruik van Azure Storage-specifieke termen, maar zijn over het algemeen van toepassing op alle ondersteunde Durable Functions opslagproviders.

Implementatiesites

Wanneer u implementaties naast elkaar uitvoert in Azure Functions of Azure App Service, raden we u aan de nieuwe versie van de functie-app te implementeren in een nieuwe implementatiesite. Met implementatiesites kunt u meerdere kopieën van uw functie-app naast elkaar uitvoeren met slechts één ervan als de actieve productiesite . Wanneer u klaar bent om de nieuwe indelingslogica beschikbaar te maken voor uw bestaande infrastructuur, kan dit net zo eenvoudig zijn als het wisselen van de nieuwe versie in de productiesite.

Notitie

Deze strategie werkt het beste wanneer u HTTP- en webhooktriggers gebruikt voor orchestratorfuncties. Voor niet-HTTP-triggers, zoals wachtrijen of Event Hubs, moet de triggerdefinitie worden afgeleid van een app-instelling die wordt bijgewerkt als onderdeel van de wisselbewerking.

Volgende stappen