Vad är Durable Functions?

Durable Functions är en utökning av Azure Functions som gör att du kan skriva tillståndsful functions i en serverlös beräkningsmiljö. Med tillägget kan du definiera tillståndsfulla arbetsflöden genom att skriva orkestreringsfunktioner och tillståndsfulla entiteter genom att skriva entitetsfunktioner med hjälp Azure Functions programmeringsmodellen. I bakgrunden hanterar tillägget tillstånd, kontrollpunkter och omstarter så att du kan fokusera på affärslogiken.

Språk som stöds

Durable Functions stöder för närvarande följande språk:

  • C#: både förkompilerade klassbibliotek och C#-skript.
  • JavaScript: stöds endast för version 2.x eller senare av Azure Functions runtime. Kräver version 1.7.0 av Durable Functions-tillägget eller en senare version.
  • Python: kräver version 2.3.1 Durable Functions tillägget eller en senare version.
  • F#: både förkompilerade klassbibliotek och F#-skript. F#-skriptet stöds endast för version 1.x av Azure Functions-körningen.
  • PowerShell: Stöds endast för version 3.x av Azure Functions runtime och PowerShell 7. Kräver version 2.x av pakettilläggen.

För att få åtkomst till de senaste funktionerna och uppdateringarna rekommenderar vi att du använder de senaste versionerna av Durable Functions-tillägget och de språkspecifika Durable Functions biblioteken. Läs mer om Durable Functions versioner.

Durable Functions har som mål att stödja alla Azure Functions-språk. I Durable Functions-problemlistan finns senaste status för arbetet med att stödja ytterligare språk.

Liksom Azure Functions finns det mallar som hjälper dig att utveckla Durable Functions med Visual Studio 2019, Visual Studio Codeoch Azure Portal.

Programmönster

Det huvudsakliga användningsfallet för Durable Functions är att förenkla komplexa, tillståndskänsliga koordinationskrav i serverlösa program. I följande avsnitt beskrivs vanliga programmönster som kan dra nytta av Durable Functions:

Mönster #1: Funktionskedja

I funktionskedjemönstret körs en sekvens med funktioner i en viss ordning. I det här mönstret tillämpas utdata för en funktion på indata för en annan funktion.

Ett diagram över funktionskedjemönstret

Du kan använda Durable Functions för att implementera funktionskedjemönstret koncist, som du ser i följande exempel.

I det här exemplet är F1 värdena , , och namnen på andra funktioner i samma F2 F3 F4 funktionsapp. Du kan implementera kontrollflöde med hjälp av normala imperativ kodningskonstruktioner. Koden körs uppifrån och ned. Koden kan omfatta befintlig språkkontrollflödessemantik, till exempel villkor och loopar. Du kan inkludera felhanteringslogik i try / catch / finally block.

[FunctionName("Chaining")]
public static async Task<object> Run(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    try
    {
        var x = await context.CallActivityAsync<object>("F1", null);
        var y = await context.CallActivityAsync<object>("F2", x);
        var z = await context.CallActivityAsync<object>("F3", y);
        return  await context.CallActivityAsync<object>("F4", z);
    }
    catch (Exception)
    {
        // Error handling or compensation goes here.
    }
}

Du kan använda context parametern för att anropa andra funktioner efter namn, skicka parametrar och returnera funktionsutdata. Varje gång koden await anropar , Durable Functions framework kontrollpunkter förloppet för den aktuella funktionsinstansen. Om processen eller den virtuella datorn återanvänds mitt under körningen återupptas funktionsinstansen från föregående await anrop. Mer information finns i nästa avsnitt, Pattern #2: Fan out/fan in.

Mönster #2: För fan out/fan in

I mönstret för för fan out/fan in kör du flera funktioner parallellt och väntar sedan på att alla funktioner ska slutföras. Ofta utförs viss aggregering på de resultat som returneras från funktionerna.

Ett diagram över mönstret för för fan out/fan

Med normala funktioner kan du för fläkta ut genom att få funktionen att skicka flera meddelanden till en kö. Det är mycket svårare att komma tillbaka till det här. För att bläddra i skriver du i en normal funktion kod för att spåra när de köutlösta funktionerna slutar och sedan lagrar funktionsutdata.

Tillägget Durable Functions hanterar det här mönstret med relativt enkel kod:

[FunctionName("FanOutFanIn")]
public static async Task Run(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    var parallelTasks = new List<Task<int>>();

    // Get a list of N work items to process in parallel.
    object[] workBatch = await context.CallActivityAsync<object[]>("F1", null);
    for (int i = 0; i < workBatch.Length; i++)
    {
        Task<int> task = context.CallActivityAsync<int>("F2", workBatch[i]);
        parallelTasks.Add(task);
    }

    await Task.WhenAll(parallelTasks);

    // Aggregate all N outputs and send the result to F3.
    int sum = parallelTasks.Sum(t => t.Result);
    await context.CallActivityAsync("F3", sum);
}

För fläkten distribueras arbetet till flera instanser av F2 funktionen. Arbetet spåras med hjälp av en dynamisk lista med uppgifter. Task.WhenAll anropas för att vänta tills alla anropade funktioner har avslutats. Sedan F2 aggregeras funktionens utdata från den dynamiska uppgiftslistan och skickas till F3 funktionen.

Den automatiska kontrollpunkter som sker vid anropet på säkerställer att en potentiell krasch eller omstart av halvvägs inte kräver att en redan slutförd aktivitet await Task.WhenAll startas om.

Anteckning

I sällsynta fall är det möjligt att en krasch kan inträffa i fönstret när en aktivitetsfunktion har slutförts men innan dess slutförande sparas i orkestreringshistoriken. Om detta inträffar körs aktivitetsfunktionen igen från början efter att processen återställs.

Mönster #3: Asynkrona HTTP-API:er

Det asynkrona HTTP API-mönstret åtgärdar problemet med att koordinera tillståndet för långvariga åtgärder med externa klienter. Ett vanligt sätt att implementera det här mönstret är att ha en HTTP-slutpunkt som utlöser den långvariga åtgärden. Omdirigera sedan klienten till en statusslutpunkt som klienten avsöker för att lära sig när åtgärden är klar.

Ett diagram över HTTP API-mönstret

Durable Functions har inbyggt stöd för det här mönstret, vilket förenklar eller till och med tar bort den kod som du behöver skriva för att interagera med långvariga funktionskörningar. Exempel på Durable Functions(C# och JavaScript)visar till exempel ett enkelt REST-kommando som du kan använda för att starta nya orkestreringsfunktionsinstanser. När en instans startar exponerar tillägget webhook-HTTP-API:er som frågar orkestreringsfunktionens status.

I följande exempel visas REST-kommandon som startar en initierare och frågar efter dess status. För tydlighetens skull utelämnas viss protokollinformation från exemplet.

> curl -X POST https://myfunc.azurewebsites.net/api/orchestrators/DoWork -H "Content-Length: 0" -i
HTTP/1.1 202 Accepted
Content-Type: application/json
Location: https://myfunc.azurewebsites.net/runtime/webhooks/durabletask/instances/b79baf67f717453ca9e86c5da21e03ec

{"id":"b79baf67f717453ca9e86c5da21e03ec", ...}

> curl https://myfunc.azurewebsites.net/runtime/webhooks/durabletask/instances/b79baf67f717453ca9e86c5da21e03ec -i
HTTP/1.1 202 Accepted
Content-Type: application/json
Location: https://myfunc.azurewebsites.net/runtime/webhooks/durabletask/instances/b79baf67f717453ca9e86c5da21e03ec

{"runtimeStatus":"Running","lastUpdatedTime":"2019-03-16T21:20:47Z", ...}

> curl https://myfunc.azurewebsites.net/runtime/webhooks/durabletask/instances/b79baf67f717453ca9e86c5da21e03ec -i
HTTP/1.1 200 OK
Content-Length: 175
Content-Type: application/json

{"runtimeStatus":"Completed","lastUpdatedTime":"2019-03-16T21:20:57Z", ...}

Eftersom Durable Functions körtid hanterar tillstånd åt dig behöver du inte implementera en egen statusspårningsmekanism.

Tillägget Durable Functions exponerar inbyggda HTTP-API:er som hanterar långvariga orkestreringar. Du kan också implementera det här mönstret själv genom att använda dina egna funktionsutlösare (till exempel HTTP, en kö eller Azure Event Hubs) och orkestreringsklientbindningen . Du kan till exempel använda ett kömeddelande för att utlösa avslutning. Du kan också använda en HTTP-utlösare som skyddas av en Azure Active Directory-autentiseringsprincip i stället för de inbyggda HTTP-API:erna som använder en genererad nyckel för autentisering.

Mer information finns i artikeln OM HTTP-funktioner, som förklarar hur du kan exponera asynkrona, långvariga processer via HTTP med hjälp Durable Functions tillägget.

Mönster #4: Övervaka

Övervakningsmönstret refererar till en flexibel, återkommande process i ett arbetsflöde. Ett exempel är avsökning tills specifika villkor uppfylls. Du kan använda en vanlig timerutlösare för att hantera ett grundläggande scenario, till exempel ett periodiskt rensningsjobb, men intervallet är statiskt och hanteringen av instanslivslängder blir komplex. Du kan använda Durable Functions för att skapa flexibla upprepningsintervall, hantera aktivitetens livslängd och skapa flera övervakningsprocesser från en enda orkestrering.

Ett exempel på övervakningsmönstret är att ångra det tidigare asynkrona HTTP API-scenariot. I stället för att exponera en slutpunkt för en extern klient för att övervaka en långvarig åtgärd använder den långvariga övervakaren en extern slutpunkt och väntar sedan på en tillståndsändring.

Ett diagram över övervakningsmönstret

Med några rader kod kan du använda Durable Functions för att skapa flera övervakare som observerar godtyckliga slutpunkter. Övervakarna kan avsluta körningen när ett villkor uppfylls, eller så kan en annan funktion använda den beständiga orkestreringsklienten för att avsluta övervakarna. Du kan ändra intervallet för en wait övervakare baserat på ett specifikt villkor (till exempel exponentiell backoff).)

Följande kod implementerar en grundläggande övervakare:

[FunctionName("MonitorJobStatus")]
public static async Task Run(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    int jobId = context.GetInput<int>();
    int pollingInterval = GetPollingInterval();
    DateTime expiryTime = GetExpiryTime();

    while (context.CurrentUtcDateTime < expiryTime)
    {
        var jobStatus = await context.CallActivityAsync<string>("GetJobStatus", jobId);
        if (jobStatus == "Completed")
        {
            // Perform an action when a condition is met.
            await context.CallActivityAsync("SendAlert", machineId);
            break;
        }

        // Orchestration sleeps until this time.
        var nextCheck = context.CurrentUtcDateTime.AddSeconds(pollingInterval);
        await context.CreateTimer(nextCheck, CancellationToken.None);
    }

    // Perform more work here, or let the orchestration end.
}

När en begäran tas emot skapas en ny orkestreringsinstans för det jobb-ID:t. Instansen avsöker en status tills ett villkor uppfylls och loopen avslutas. En beständig timer styr avsökningsintervallet. Sedan kan mer arbete utföras, eller så kan orkestrering avslutas. När nextCheck överskrider expiryTime avslutas övervakaren.

Mönster #5: Mänsklig interaktion

Många automatiserade processer omfattar någon typ av mänsklig interaktion. Det är svårt att involvera människor i en automatiserad process eftersom människor inte är lika tillgängliga och lika dynamiska som molntjänster. En automatiserad process kan möjliggöra den här interaktionen genom att använda tidsgränser och kompensationslogik.

En godkännandeprocess är ett exempel på en affärsprocess som inbegriper mänsklig interaktion. Godkännande från en chef kan krävas för en utgiftsrapport som överskrider ett visst belopp i dollar. Om chefen inte godkänner utgiftsrapporten inom 72 timmar (chefen kanske var på semester) startar en eskaleringsprocess för att få godkännande från någon annan (kanske chefens chef).

Ett diagram över det mänskliga interaktionsmönstret

Du kan implementera mönstret i det här exemplet med hjälp av en orkestreringsfunktion. Orkestratorn använder en beständig timer för att begära godkännande. Orkestreraren eskalerar om tidsgränsen inträffar. Orchestrator väntar på en extern händelse,till exempel ett meddelande som genereras av en mänsklig interaktion.

De här exemplen skapar en godkännandeprocess för att demonstrera det mänskliga interaktionsmönstret:

[FunctionName("ApprovalWorkflow")]
public static async Task Run(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    await context.CallActivityAsync("RequestApproval", null);
    using (var timeoutCts = new CancellationTokenSource())
    {
        DateTime dueTime = context.CurrentUtcDateTime.AddHours(72);
        Task durableTimeout = context.CreateTimer(dueTime, timeoutCts.Token);

        Task<bool> approvalEvent = context.WaitForExternalEvent<bool>("ApprovalEvent");
        if (approvalEvent == await Task.WhenAny(approvalEvent, durableTimeout))
        {
            timeoutCts.Cancel();
            await context.CallActivityAsync("ProcessApproval", approvalEvent.Result);
        }
        else
        {
            await context.CallActivityAsync("Escalate", null);
        }
    }
}

Om du vill skapa den hållbara timern anropar du context.CreateTimer . Meddelandet tas emot av context.WaitForExternalEvent . Sedan Task.WhenAny anropas för att avgöra om eskalera (tidsgränsen inträffar först) eller bearbeta godkännandet (godkännandet tas emot före tidsgränsen).

En extern klient kan leverera händelsemeddelandet till en väntande orkestreringsfunktion med hjälp av de inbyggda HTTP-API:erna:

curl -d "true" http://localhost:7071/runtime/webhooks/durabletask/instances/{instanceId}/raiseEvent/ApprovalEvent -H "Content-Type: application/json"

En händelse kan också höjas med hjälp av den beständiga orkestreringsklienten från en annan funktion i samma funktionsapp:

[FunctionName("RaiseEventToOrchestration")]
public static async Task Run(
    [HttpTrigger] string instanceId,
    [DurableClient] IDurableOrchestrationClient client)
{
    bool isApproved = true;
    await client.RaiseEventAsync(instanceId, "ApprovalEvent", isApproved);
}

Mönster #6: Aggregator (tillståndsful entities)

Det sjätte mönstret handlar om att aggregera händelsedata under en tidsperiod till en enda adresserbar entitet. I det här mönstret kan de data som aggregeras komma från flera källor, levereras i batchar eller vara spridda över långa tidsperioder. Aggregatorn kan behöva vidta åtgärder för händelsedata när de tas emot, och externa klienter kan behöva köra frågor mot aggregerade data.

Aggregatordiagram

Det svåra med att implementera det här mönstret med normala, tillståndslösa funktioner är att samtidighetskontroll blir en enorm utmaning. Du behöver inte bara bekymra dig om flera trådar som ändrar samma data samtidigt, utan du måste också oroa dig för att säkerställa att aggregatorn endast körs på en enda virtuell dator i taget.

Du kan använda varaktiga entiteter för att enkelt implementera det här mönstret som en enda funktion.

[FunctionName("Counter")]
public static void Counter([EntityTrigger] IDurableEntityContext ctx)
{
    int currentValue = ctx.GetState<int>();
    switch (ctx.OperationName.ToLowerInvariant())
    {
        case "add":
            int amount = ctx.GetInput<int>();
            ctx.SetState(currentValue + amount);
            break;
        case "reset":
            ctx.SetState(0);
            break;
        case "get":
            ctx.Return(currentValue);
            break;
    }
}

Varaktiga entiteter kan också modelleras som klasser i .NET. Den här modellen kan vara användbar om listan över åtgärder är fast och blir stor. Följande exempel är en motsvarande implementering av entiteten Counter med hjälp av .NET-klasser och metoder.

public class Counter
{
    [JsonProperty("value")]
    public int CurrentValue { get; set; }

    public void Add(int amount) => this.CurrentValue += amount;

    public void Reset() => this.CurrentValue = 0;

    public int Get() => this.CurrentValue;

    [FunctionName(nameof(Counter))]
    public static Task Run([EntityTrigger] IDurableEntityContext ctx)
        => ctx.DispatchAsync<Counter>();
}

Klienter kan lägga till åtgärder i en entitetsfunktion i entitetsklientens bindning (kallas även "signalering").

[FunctionName("EventHubTriggerCSharp")]
public static async Task Run(
    [EventHubTrigger("device-sensor-events")] EventData eventData,
    [DurableClient] IDurableEntityClient entityClient)
{
    var metricType = (string)eventData.Properties["metric"];
    var delta = BitConverter.ToInt32(eventData.Body, eventData.Body.Offset);

    // The "Counter/{metricType}" entity is created on-demand.
    var entityId = new EntityId("Counter", metricType);
    await entityClient.SignalEntityAsync(entityId, "add", delta);
}

Anteckning

Dynamiskt genererade proxys är också tillgängliga i .NET för att signalera entiteter på ett typsäkert sätt. Förutom signalering kan klienter också fråga efter tillståndet för en entitetsfunktion med hjälp av typsäkra metoder orkestreringsklientbindningen.

Entitetsfunktioner är tillgängliga Durable Functions 2.0 och högre för C#, JavaScript och Python.

Tekniken

I bakgrunden bygger Durable Functions-tillägget på Durable Task Framework, ett bibliotek med öppen källkod på GitHub som används för att skapa arbetsflöden i kod. Precis Azure Functions är den serverlösa utvecklingen av Azure WebJobs är Durable Functions den serverlösa utvecklingen av Durable Task Framework. Microsoft och andra organisationer använder Durable Task Framework i stor utsträckning för att automatisera verksamhetskritiska processer. Det passar perfekt för den serverlösa Azure Functions miljön.

Kodbegränsningar

För att tillhandahålla tillförlitliga och långvariga körningsgarantier har orkestreringsfunktioner en uppsättning kodningsregler som måste följas. Mer information finns i artikeln om begränsningar i Orchestrator-funktionskod.

Fakturering

Durable Functions debiteras samma som Azure Functions. Mer information finns i prissättning för Azure Functions. När du kör Orchestrator-funktioner i Azure Functions-förbrukningsplanenfinns det vissa faktureringsbeteenden att känna till. Mer information om dessa beteenden finns i artikeln Durable Functions fakturering.

Kom igång snabbt

Du kan komma igång med Durable Functions på mindre än 10 minuter genom att slutföra någon av dessa språkspecifika snabbstartsguider:

I de här snabbstarterna skapar och testar du en beständig "hello world"-funktion lokalt. Du publicerar sedan funktionskoden till Azure. Den funktion som du skapar orkestrerar och kedjar samman anrop till andra funktioner.

Läs mer

Följande video visar fördelarna med Durable Functions:

En mer detaljerad diskussion om Durable Functions och underliggande teknik finns i följande video (den fokuserar på .NET, men begreppen gäller även för andra språk som stöds):

Eftersom Durable Functions är ett avancerat tillägg för Azure Functions är det inte lämpligt för alla program. En jämförelse med andra Azure-orkestreringstekniker finns i avsnittet om att jämföra Azure Functions med Azure Logic Apps.

Nästa steg