Zpracování chyb a opakování v orchestracích

Implementujete orchestrace Durable Functions v kódu, a proto použijete integrované funkce pro zpracování chyb ve vašem jazyce. Zpracování chyb a kompenzace nevyžadují nové koncepty, ale několik chování orchestrace stojí za to vědět.

Poznámka:

Verze 4 programovacího modelu Node.js pro Azure Functions je obecně dostupná. Model v4 je navržený tak, aby poskytoval flexibilnější a intuitivnější prostředí pro vývojáře v JavaScriptu a TypeScriptu. Další informace o rozdílech mezi v3 a v4 najdete v průvodci migrací.

V následujících fragmentech kódu JavaScript (PM4) označuje programovací model v4, nové prostředí.

Aplikace, které používají cloudové služby, musí zvládat selhání, a opakování na straně klienta je důležitou součástí návrhu. Sady SDK durable task zahrnují podporu zpracování chyb, opakování a časových limitů, které vám pomůžou vytvářet robustní pracovní postupy.

Zpracování chyb ve funkcích aktivit a dílčích orchestracích

V Durable Functions jsou neošetřené výjimky vyvolané v rámci funkcí aktivit nebo dílčí orchestrace zařazovány zpět do funkce orchestrátoru pomocí standardizovaných typů výjimek.

Následující funkce orchestrátoru převádí finanční prostředky mezi dvěma účty:

Izolovaný model pracovního procesu

V izolovaném prostředí Durable Functions C# se neošetřené výjimky zobrazují jako TaskFailedException.

Zpráva o výjimce obvykle identifikuje, které funkce aktivit nebo dílčí orchestrace způsobily selhání. Pokud chcete získat přístup k podrobnějším informacím o chybě, zkontrolujte vlastnost FailureDetails .

[FunctionName("TransferFunds")]
public static async Task Run(
    [OrchestrationTrigger] TaskOrchestrationContext context, TransferOperation transferDetails)
{
    await context.CallActivityAsync("DebitAccount",
        new
        {
            Account = transferDetails.SourceAccount,
            Amount = transferDetails.Amount
        });

    try
    {
        await context.CallActivityAsync("CreditAccount",
            new
            {
                Account = transferDetails.DestinationAccount,
                Amount = transferDetails.Amount
            });
    }
    catch (TaskFailedException)
    {
        // Refund the source account.
        // Another try/catch could be used here based on the needs of the application.
        await context.CallActivityAsync("CreditAccount",
            new
            {
                Account = transferDetails.SourceAccount,
                Amount = transferDetails.Amount
            });
    }
}

Poznámka:

  • Zpráva o výjimce obvykle identifikuje, které funkce aktivit nebo dílčí orchestrace způsobily selhání. Pokud chcete získat přístup k podrobnějším informacím o chybách, zkontrolujte FailureDetails vlastnost.
  • Ve výchozím nastavení FailureDetails zahrnuje typ chyby, chybovou zprávu, sledování zásobníku a všechny vnořené vnitřní výjimky (každý reprezentovaný jako rekurzivní FailureDetails objekt). Pokud chcete do výstupu selhání zahrnout další vlastnosti výjimky, přečtěte si téma Vložení vlastních vlastností výjimky pro FailureDetails (.NET Isolated).

Důležité

Poznámka k migraci (při přechodu z interního procesu na izolovaný systém):FunctionFailedException.InnerException v modelu interního procesu obsahuje původní objekt výjimky vyvolaný aktivitou, který můžete přetypovat a zkontrolovat přímo. V izolovaném pracovním modelu TaskFailedExceptionneobsahuje původní výjimku jako InnerException. Místo toho jsou podrobnosti o chybě k dispozici pouze prostřednictvím FailureDetails vlastnosti, která poskytuje vlastnosti založené na řetězci (ErrorType, ErrorMessage, StackTrace). K původnímu objektu výjimky nelze přetypovat ani k němu přistupovat přímo. Slouží FailureDetails.IsCausedBy<T>() ke kontrole původního typu výjimky.


Model v probíhajícím procesu

V procesu Durable Functions jazyka C# jsou neošetřené výjimky vyvolány jako FunctionFailedException.

Zpráva o výjimce obvykle obsahuje funkci aktivity nebo dílčí orchestraci, která selhala. Podrobnosti zkontrolujte v InnerException.

[FunctionName("TransferFunds")]
public static async Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
    var transferDetails = context.GetInput<TransferOperation>();

    await context.CallActivityAsync("DebitAccount",
        new
        {
            Account = transferDetails.SourceAccount,
            Amount = transferDetails.Amount
        });

    try
    {
        await context.CallActivityAsync("CreditAccount",
            new
            {
                Account = transferDetails.DestinationAccount,
                Amount = transferDetails.Amount
            });
    }
    catch (FunctionFailedException)
    {
        // Refund the source account.
        // Another try/catch could be used here based on the needs of the application.
        await context.CallActivityAsync("CreditAccount",
            new
            {
                Account = transferDetails.SourceAccount,
                Amount = transferDetails.Amount
            });
    }
}

Poznámka:

Předchozí příklady jazyka C# používají Durable Functions 2.x. Pro Durable Functions 1.x místo DurableOrchestrationContext použijte IDurableOrchestrationContext. Rozdíly ve verzích najdete v článku Durable Functions.

Pokud první volání funkce CreditAccount selže, funkce orchestrátoru kompenzuje tím, že poukáže prostředky zpět do zdrojového účtu.

V sadách SDK trvalých úloh se neošetřené výjimky vyvolané v rámci aktivit nebo dílčích orchestrací zařaďují zpět do orchestrátoru pomocí typu TaskFailedException. Vlastnost výjimky FailureDetails poskytuje podrobné informace o selhání.

using Microsoft.DurableTask;

[DurableTask(nameof(TransferFundsOrchestration))]
public class TransferFundsOrchestration : TaskOrchestrator<TransferOperation, string>
{
    public override async Task<string> RunAsync(
        TaskOrchestrationContext context, TransferOperation transfer)
    {
        await context.CallActivityAsync(
            nameof(DebitAccountActivity),
            new AccountOperation { Account = transfer.SourceAccount, Amount = transfer.Amount });

        try
        {
            await context.CallActivityAsync(
                nameof(CreditAccountActivity),
                new AccountOperation { Account = transfer.DestinationAccount, Amount = transfer.Amount });
        }
        catch (TaskFailedException ex)
        {
            // Log the failure details
            var details = ex.FailureDetails;

            // Compensate by refunding the source account
            await context.CallActivityAsync(
                nameof(CreditAccountActivity),
                new AccountOperation { Account = transfer.SourceAccount, Amount = transfer.Amount });

            return $"Transfer failed: {details.ErrorMessage}. Compensation completed.";
        }

        return "Transfer completed successfully";
    }
}

Pokud aktivita CreditAccount selže, orchestrátor zachytí výjimku a kompenzuje tím, že připisuje prostředky zpět na původní účet.

Zpracování chyb při volání více aktivit (spuštění/sběr)

Pokud používáte Task.WhenAll ke paralelnímu spouštění více volání aktivit (model fan-out/fan-in), a jedna nebo více aktivit selže, await je vyvolána pouze první výjimka. Pokud chcete získat přístup ke všem selháním, zkontrolujte vlastnost Exception na objektu Task, který je vrácen funkcí Task.WhenAll.

Izolovaný model pracovního procesu
var tasks = new[]
{
    context.CallActivityAsync("Activity1", input1),
    context.CallActivityAsync("Activity2", input2),
    context.CallActivityAsync("Activity3", input3),
};

var allTask = Task.WhenAll(tasks);
try
{
    await allTask;
}
catch (TaskFailedException)
{
    // 'await' rethrows only the first exception. To inspect all failures,
    // check allTask.Exception, which is an AggregateException.
    if (allTask.Exception != null)
    {
        foreach (var inner in allTask.Exception.InnerExceptions)
        {
            if (inner is TaskFailedException taskFailed)
            {
                // Use taskFailed.FailureDetails to inspect error details
                var errorType = taskFailed.FailureDetails.ErrorType;
                var errorMessage = taskFailed.FailureDetails.ErrorMessage;
            }
        }
    }
}

Model v probíhajícím procesu
var tasks = new[]
{
    context.CallActivityAsync("Activity1", input1),
    context.CallActivityAsync("Activity2", input2),
    context.CallActivityAsync("Activity3", input3),
};

var allTask = Task.WhenAll(tasks);
try
{
    await allTask;
}
catch (FunctionFailedException)
{
    // 'await' rethrows only the first exception. To inspect all failures,
    // check allTask.Exception, which is an AggregateException.
    if (allTask.Exception != null)
    {
        foreach (var inner in allTask.Exception.InnerExceptions)
        {
            if (inner is FunctionFailedException funcFailed)
            {
                // Use funcFailed.InnerException to access the original exception
            }
        }
    }
}

Zpracování chyb ve funkcích entit

Zpracování výjimek ve funkcích entit závisí na modelu hostování Durable Functions:

Izolovaný model pracovního procesu

V izolovaném prostředí Durable Functions C# runtime obalí výjimky funkce entity do EntityOperationFailedException. Chcete-li získat původní podrobnosti o výjimce, zkontrolujte vlastnost FailureDetails.

[Function(nameof(MyOrchestrator))]
public static async Task<List<string>> MyOrchestrator(
   [Microsoft.Azure.Functions.Worker.OrchestrationTrigger] TaskOrchestrationContext context)
{
    var entityId = new Microsoft.DurableTask.Entities.EntityInstanceId(nameof(Counter), "myCounter");
    try
    {
        await context.Entities.CallEntityAsync(entityId, "Add", 1);
    }
    catch (EntityOperationFailedException ex)
    {
        // Add your error handling
    }

    return new List<string>();
}

Model v probíhajícím procesu

V Durable Functions s jazykem C# jako in-process vracejí funkce entit orchestrátoru jejich původní typy výjimek.

[FunctionName("Function1")]
public static async Task<string> RunOrchestrator(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{
    try
    {
        var entityId = new EntityId(nameof(Counter), "myCounter");
        await context.CallEntityAsync(entityId, "Add", 1);
    }
    catch (Exception ex)
    {
        // The exception type is InvalidOperationException with the message "this is an entity exception".
    }
    return string.Empty;
}

[FunctionName("Counter")]
public static void Counter([EntityTrigger] IDurableEntityContext ctx)
{
    switch (ctx.OperationName.ToLowerInvariant())
    {
        case "add":
            throw new InvalidOperationException("this is an entity exception");
        case "get":
            ctx.Return(ctx.GetState<int>());
            break;
    }
}

Automatické opakování při selhání

Při volání funkcí aktivity nebo dílčí orchestrace zadejte politiku automatického opakování. Následující příklad volá funkci až třikrát a čeká pět sekund mezi opakovanými pokusy:

Izolovaný model pracovního procesu
[FunctionName("TimerOrchestratorWithRetry")]
public static async Task Run([OrchestrationTrigger] TaskOrchestrationContext context)
{
    var options = TaskOptions.FromRetryPolicy(new RetryPolicy(
        maxNumberOfAttempts: 3,
        firstRetryInterval: TimeSpan.FromSeconds(5)));

    await context.CallActivityAsync("FlakyFunction", options: options);

    // ...
}

Model v probíhajícím procesu
[FunctionName("TimerOrchestratorWithRetry")]
public static async Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
    var retryOptions = new RetryOptions(
        firstRetryInterval: TimeSpan.FromSeconds(5),
        maxNumberOfAttempts: 3);

    await context.CallActivityWithRetryAsync("FlakyFunction", retryOptions, null);

    // ...
}

Poznámka:

Předchozí příklady jazyka C# jsou pro Durable Functions 2.x. Pro Durable Functions 1.x je nutné použít DurableOrchestrationContext místo IDurableOrchestrationContext. Další informace o rozdílech mezi verzemi najdete v článku o verzích Durable Functions .

Volání funkce aktivity v předchozím příkladu používá parametr ke konfiguraci zásady automatického opakování. Přizpůsobte zásady těmito možnostmi:

  • Maximální počet pokusů: Maximální počet pokusů. Pokud je nastavená hodnota 1, nedojde k žádným opakovaným pokusům.
  • Interval prvního opakovaného pokusu: Doba čekání před prvním opakovaným pokusem.
  • Koeficient zpětného odložení: Koeficient použitý k určení míry zvýšení zpětného odložení. Výchozí hodnota je 1.
  • Maximální interval opakování: Maximální doba čekání mezi opakovanými pokusy.
  • Časový limit opakování: Maximální doba strávená opakovaným pokusem. Ve výchozím nastavení budou opakování pokračovat po neomezenou dobu.

Sady SDK trvalých úloh zahrnují alternativní metody plánování, které opakují neúspěšné aktivity na základě zadaných zásad. Tyto metody jsou užitečné pro aktivity, které čtou data z webových služeb nebo provádějí idempotentní zápisy do databáze.

using Microsoft.DurableTask;

[DurableTask(nameof(OrchestratorWithRetry))]
public class OrchestratorWithRetry : TaskOrchestrator<string, string>
{
    public override async Task<string> RunAsync(
        TaskOrchestrationContext context, string input)
    {
        // Configure retry policy
        var retryPolicy = new RetryPolicy(
            maxNumberOfAttempts: 3,
            firstRetryInterval: TimeSpan.FromSeconds(5),
            backoffCoefficient: 2.0,
            maxRetryInterval: TimeSpan.FromMinutes(1),
            retryTimeout: TimeSpan.FromMinutes(5));

        var options = TaskOptions.FromRetryPolicy(retryPolicy);

        // Call activity with automatic retry
        string result = await context.CallActivityAsync<string>(
            nameof(UnreliableActivity), input, options);

        return result;
    }
}

Možnosti zásad opakování:

  • Maximální počet pokusů: Maximální počet opakovaných pokusů. Pokud je nastavená hodnota 1, nedojde k žádným opakovaným pokusům.
  • Interval prvního opakovaného pokusu: Doba čekání před prvním opakovaným pokusem.
  • Koeficient zpětného odložení: Koeficient použitý k určení míry zvýšení zpětného odložení. Výchozí hodnota je 1.
  • Maximální interval opakování: Maximální doba čekání mezi opakovanými pokusy.
  • Časový limit opakování: Maximální doba strávená opakováním.

Vlastní obslužné rutiny opakování

V .NET a Java implementujte obslužné rutiny opakování v kódu, pokud nejsou zásady deklarativního opakování dostatečně výrazné. V jiných jazycích implementujte logiku opakování pomocí smyček, zpracování výjimek a časovačů pro zpoždění mezi opakováními.

Izolovaný model pracovního procesu
TaskOptions retryOptions = TaskOptions.FromRetryHandler(retryContext =>
{
    // Don't retry anything that derives from ApplicationException
    if (retryContext.LastFailure.IsCausedBy<ApplicationException>())
    {
        return false;
    }

    // Quit after N attempts
    return retryContext.LastAttemptNumber < 3;
});

try
{
    await ctx.CallActivityAsync("FlakeyActivity", options: retryOptions);
}
catch (TaskFailedException)
{
    // Case when the retry handler returns false...
}

Model v probíhajícím procesu
RetryOptions retryOptions = new RetryOptions(
    firstRetryInterval: TimeSpan.FromSeconds(5),
    maxNumberOfAttempts: int.MaxValue)
{
    Handle = exception =>
    {
        // Return true to handle and retry, or false to throw.
        if (exception is TaskFailedException failure)
        {
            // Exceptions from task activities are always this type. Inspect the
            // inner exception for more details.
        }

        return false;
    }
};

await ctx.CallActivityWithRetryAsync("FlakeyActivity", retryOptions, null);

Vlastní obslužné rutiny opakování

V .NET a Java implementujte obslužné rutiny opakování v kódu pro řízení logiky opakování. Tento přístup je užitečný v případě, že deklarativní opakovací politiky nejsou dostatečně výrazné.

using Microsoft.DurableTask;

[DurableTask(nameof(OrchestratorWithCustomRetry))]
public class OrchestratorWithCustomRetry : TaskOrchestrator<string, string>
{
    public override async Task<string> RunAsync(
        TaskOrchestrationContext context, string input)
    {
        // Custom retry handler with conditional logic
        TaskOptions retryOptions = TaskOptions.FromRetryHandler(retryContext =>
        {
            // Don't retry if it's a validation error
            if (retryContext.LastFailure.IsCausedBy<ArgumentException>())
            {
                return false;
            }

            // Retry up to 5 times for transient errors
            return retryContext.LastAttemptNumber < 5;
        });

        try
        {
            return await context.CallActivityAsync<string>(
                nameof(UnreliableActivity), input, retryOptions);
        }
        catch (TaskFailedException)
        {
            // All retries exhausted
            return "Operation failed after all retries";
        }
    }
}

Časové limity funkcí

Pokud volání funkce trvá příliš dlouho, vyprší jeho časový limit ve funkci orchestrátoru. Vytvořte trvalý časovač pomocí selektoru any úloh, jak je znázorněno v následujícím příkladu:

Izolovaný model pracovního procesu
[Function("TimerOrchestrator")]
public static async Task<bool> Run([OrchestrationTrigger] TaskOrchestrationContext context)
{
    TimeSpan timeout = TimeSpan.FromSeconds(30);
    DateTime deadline = context.CurrentUtcDateTime.Add(timeout);

    using (var cts = new CancellationTokenSource())
    {
        Task activityTask = context.CallActivityAsync("FlakyFunction");
        Task timeoutTask = context.CreateTimer(deadline, cts.Token);

        Task winner = await Task.WhenAny(activityTask, timeoutTask);
        if (winner == activityTask)
        {
            // success case
            cts.Cancel();
            return true;
        }
        else
        {
            // timeout case
            return false;
        }
    }
}

Model v probíhajícím procesu
[FunctionName("TimerOrchestrator")]
public static async Task<bool> Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
    TimeSpan timeout = TimeSpan.FromSeconds(30);
    DateTime deadline = context.CurrentUtcDateTime.Add(timeout);

    using (var cts = new CancellationTokenSource())
    {
        Task activityTask = context.CallActivityAsync("FlakyFunction");
        Task timeoutTask = context.CreateTimer(deadline, cts.Token);

        Task winner = await Task.WhenAny(activityTask, timeoutTask);
        if (winner == activityTask)
        {
            // success case
            cts.Cancel();
            return true;
        }
        else
        {
            // timeout case
            return false;
        }
    }
}

Poznámka:

Předchozí příklady jazyka C# jsou pro Durable Functions 2.x. Pro Durable Functions 1.x je nutné použít DurableOrchestrationContext místo IDurableOrchestrationContext. Další informace o rozdílech mezi verzemi najdete v článku o verzích Durable Functions .

Poznámka:

Tento mechanismus neukončí provádění funkcí aktivity, které už probíhá. Umožňuje funkci orchestrátoru ignorovat výsledek a pokračovat dál. Další informace najdete v tématu Časovače.

Časové limity aktivit

Pokud volání aktivity trvá příliš dlouho, můžete na něj přestat čekat. Vytvořte odolný časovač a srovnejte ho s úkolovou aktivitou.

using Microsoft.DurableTask;
using System;
using System.Threading;
using System.Threading.Tasks;

[DurableTask(nameof(OrchestratorWithTimeout))]
public class OrchestratorWithTimeout : TaskOrchestrator<string, bool>
{
    public override async Task<bool> RunAsync(
        TaskOrchestrationContext context, string input)
    {
        TimeSpan timeout = TimeSpan.FromSeconds(30);
        DateTime deadline = context.CurrentUtcDateTime.Add(timeout);

        using var cts = new CancellationTokenSource();
        Task activityTask = context.CallActivityAsync(nameof(SlowActivity), input);
        Task timeoutTask = context.CreateTimer(deadline, cts.Token);

        Task winner = await Task.WhenAny(activityTask, timeoutTask);
        if (winner == activityTask)
        {
            // Activity completed in time - cancel the timer
            cts.Cancel();
            return true;
        }
        else
        {
            // Timeout occurred
            return false;
        }
    }
}

Poznámka:

Tento mechanismus neukončí provádění aktivit, které už probíhá. Umožňuje orchestrátoru ignorovat výsledek a pokračovat dál. Další informace najdete v dokumentaci k časovačům .

Neošetřené výjimky

Pokud funkce orchestrátoru selže s neošetřenou výjimkou, modul runtime zaznamená podrobnosti o výjimce a instance se dokončí se stavem Failed .

Přidat vlastní vlastnosti výjimky pro FailureDetails (.NET Isolated)

V pracovních postupech Durable Task, které používají .NET izolovaný model, jsou selhání úloh serializována do objektu FailureDetails. Ve výchozím nastavení objekt obsahuje tato pole:

  • ErrorType– Název typu výjimky
  • Message— Zpráva o výjimce
  • StackTrace– Serializované trasování zásobníku
  • InnerFailure– Vnořený FailureDetails objekt pro vnitřní výjimky

Začínáme s Microsoft.Azure.Functions.Worker.Extensions.DurableTask v1.9.0, můžete toto chování rozšířit implementací IExceptionPropertiesProvider (definovaného v balíčku Microsoft.DurableTask.Worker počínaje v1.16.1). Tento zprostředkovatel definuje, které typy výjimek a vlastnosti se mají zahrnout do slovníku FailureDetails.Properties .

Poznámka:

  • Tato funkce je dostupná pouze v izolovaném prostředí .NET . Podpora pro Java ještě není dostupná.
  • Ujistěte se, že používáte Microsoft.Azure.Functions.Worker.Extensions.DurableTask verze 1.9.0 nebo novější.
  • Ujistěte se, že používáte Microsoft.DurableTask.Worker verze 1.16.1 nebo novější.

Implementace zprostředkovatele vlastností výjimek

Implementujte vlastní IExceptionPropertiesProvider objekt pro extrakci a vrácení vybraných vlastností pro výjimky, o které vás zajímá. Vrácený slovník je serializován do Properties pole FailureDetails , kdy je vyvolán odpovídající typ výjimky.

using Microsoft.DurableTask.Worker;

public class CustomExceptionPropertiesProvider : IExceptionPropertiesProvider
{
    public IDictionary<string, object?>? GetExceptionProperties(Exception exception)
    {
        return exception switch
        {
            ArgumentOutOfRangeException e => new Dictionary<string, object?>
            {
                ["ParamName"] = e.ParamName,
                ["ActualValue"] = e.ActualValue
            },
            InvalidOperationException e => new Dictionary<string, object?>
            {
                ["CustomHint"] = "Invalid operation occurred",
                ["TimestampUtc"] = DateTime.UtcNow
            },
            _ => null // Other exception types not handled
        };
    }
}

Registrace poskytovatele

V Program.cs zaregistrujte vlastní IExceptionPropertiesProvider v hostiteli izolačního procesu .NET:

using Microsoft.DurableTask.Worker;
using Microsoft.Extensions.DependencyInjection;

var host = new HostBuilder()
    .ConfigureFunctionsWorkerDefaults(builder =>
    {
        // Register custom exception properties provider
        builder.Services.AddSingleton<IExceptionPropertiesProvider, CustomExceptionPropertiesProvider>();
    })
    .Build();

host.Run();

Po registraci poskytovatele budou všechny výjimky, které odpovídají zpracovávanému typu, automaticky zahrnovat nakonfigurované vlastnosti v jeho FailureDetails.

Ukázkový výstup FailureDetails

Pokud dojde k výjimce, která odpovídá konfiguraci vašeho poskytovatele, orchestrace obdrží serializovaný FailureDetails objekt takto:

{
  "errorType": "TaskFailedException",
  "message": "Activity failed with an exception.",
  "stackTrace": "...",
  "innerFailure": {
    "errorType": "ArgumentOutOfRangeException",
    "message": "Specified argument was out of range.",
    "properties": {
      "ParamName": "count",
      "ActualValue": 42
    }
  }
}

Neošetřené výjimky

Pokud orchestrátor selže kvůli neošetřené výjimce, modul runtime zaznamená podrobnosti o výjimce a instance se dokončí se stavem Failed . TaskFailedException má vlastnost FailureDetails, která zahrnuje typ chyby, zprávu a trasování zásobníku.

Další kroky