Откат изменений базы данных в долговременной функции

Допустим, у меня есть следующая оркестровка:

[FunctionName("Orchestration")]
public static async Task Orchestration_Start([OrchestrationTrigger]  DurableOrchestrationContext ctx)
{
    await ctx.CallActivityAsync("Foo");
    await ctx.CallActivityAsync("Bar");
    await Task.WhenAll(ctx.CallActivityAsync("Baz"), ctx.CallActivityAsync("Baz"));
}

Во всех моих действиях используется база данных SQL Azure, и в случае сбоя любого из вызовов я хочу отменить все изменения, сделанные предыдущими действиями, например, если второй вызов Baz выдает исключение, я хочу отменить все, что сделано Foo, Bar и если первый Baz завершена, я хочу отменить его модификации тоже.

В приложении без функций я мог бы просто обернуть все тело оркестровки в using scope = new TransactionScope() блок.

Будет ли это работать для потенциально распределенной оркестровки, и если нет, то есть ли аналогичный механизм в структуре функций Azure? Или я должен написать реализацию отката для каждого из действий и зафиксировать изменения в базе данных после выполнения каждого из них?

1 ответ

Durable Functions реализуют механизм возможной согласованности. Это совершенно иное понятие, чем другие виды согласованности (например, сильная), поскольку оно гарантирует, что транзакция будет в конечном итоге завершена. Что это значит?

Используя TransactionScope Вы можете убедиться, что если что-то пойдет не так в транзакции, откат будет выполнен автоматически. В Durable Function это не так - у вас нет автоматической функции, которая дает вам такую ​​функциональность - фактически, если второе действие из вашего примера завершится неудачно, вы получите несогласованные данные, хранящиеся в базе данных.

Чтобы реализовать транзакцию в таком сценарии, вы должны попытаться / поймать возможную проблему и выполнить логику, которая позволит вам устранить ошибку:

[FunctionName("Orchestration")]
public static async Task Orchestration_Start([OrchestrationTrigger]  DurableOrchestrationContext ctx)
{
    try 
    {
        await ctx.CallActivityAsync("Foo");
        await ctx.CallActivityAsync("Bar");
        await Task.WhenAll(ctx.CallActivityAsync("Baz"), ctx.CallActivityAsync("Baz"));
    }
    catch(Exception)
    {
        // Do something...
    }  
}

Существует также возможность реализовать политику повторов, чтобы избежать временных ошибок:

public static async Task Run(DurableOrchestrationContext context)
{
    var retryOptions = new RetryOptions(
        firstRetryInterval: TimeSpan.FromSeconds(5),
        maxNumberOfAttempts: 3);

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

    // ...
}

Однако важно понять, как среда выполнения Durable Functions действительно управляет ситуацией, когда что-то идет не так. Предположим, что следующий код не работает:

[FunctionName("Orchestration")]
public static async Task Orchestration_Start([OrchestrationTrigger]  DurableOrchestrationContext ctx)
{
    await ctx.CallActivityAsync("Foo");
    await ctx.CallActivityAsync("Bar"); // THROWS!
    await Task.WhenAll(ctx.CallActivityAsync("Baz"), ctx.CallActivityAsync("Baz"));
}

Если вы воспроизведете всю оркестровку, первое действие (с пропущенным "Foo") не будет выполнено еще раз - его состояние будет сохранено в хранилище, поэтому результат будет сразу же доступен. Среда выполнения выполняет контрольную точку после каждого действия, поэтому состояние сохраняется и оно знает, где оно завершилось ранее.

Теперь, чтобы правильно обработать ситуацию, вы должны реализовать следующий алгоритм:

  • выполнить откат вручную при обнаружении исключения
  • если это не удается, отправьте сообщение, например, в очередь, которая затем обрабатывается вручную кем-то, кто понимает, как работает процесс

Хотя изначально это может выглядеть как большой недостаток, на самом деле это идеальное решение - ошибки действительно случаются, поэтому всегда рекомендуется избегать переходных (с помощью повторных попыток), но если откат не удался, это ясно указывает на что-то не так в вашей системе.

Выбор за вами - будь у вас сильная согласованность и вам приходится иметь дело с проблемами с масштабируемостью, или вы используете более свободную модель, которая обеспечивает лучшую масштабируемость, но с которой труднее работать.

Другие вопросы по тегам