Я новичок в устойчивых функциях Azure и хочу проверить свое понимание того, что значит быть детерминированным.
Поток примерно такой:
Мой текущий подход соответствует строкам кода ниже. Однако, чем больше я об этом думаю, тем больше мне кажется, что мой нынешний подход не является детерминированным. Записи, помеченные в таблице A первым действием, могут больше не помечаться, если вы запустите действие через некоторое время (например, запись больше не соответствует критериям для пометки). Это означало бы, что список, возвращаемый вторым действием, также может отличаться, если данные в таблице A изменились.
Будет ли достаточно изменить мое первое действие, чтобы оно возвращало идентификаторы отмеченных записей в таблице A и использовать этот список в качестве входных данных для второго действия? Для меня это похоже на этот пример:
Что я действительно не понимаю во всем этом, если вы повторно запустите пример в документах Microsoft, E2_GetFileList потенциально может вернуть разные файлы, потому что новые файлы могут быть добавлены или существующие удалены. Так как же это детерминировано?
public class DeterministicOrchestrator
{
[Function("DeterministicOrchestratorApi")]
public async Task<HttpResponseData> RunApi(
[HttpTrigger] HttpRequestData request,
[DurableClient] DurableTaskClient durableTaskClient)
{
var referenceDate = new DateOnly(2023, 4, 3);
var orchestrationInstanceId = await durableTaskClient
.ScheduleNewOrchestrationInstanceAsync("DeterministicOrchestrator", referenceDate)
.ConfigureAwait(false);
return durableTaskClient.CreateCheckStatusResponse(request, orchestrationInstanceId);
}
[Function("DeterministicOrchestrator")]
public async Task Run(
[OrchestrationTrigger] TaskOrchestrationContext taskOrchestrationContext,
DateOnly referenceDate)
{
var wrappedDateOnly = new WrappedDateOnly { DateOnly = referenceDate };
await taskOrchestrationContext
.CallActivityAsync("FlagDatabaseEntries", wrappedDateOnly)
.ConfigureAwait(true);
var events = await taskOrchestrationContext
.CallActivityAsync<string[]>("CreateEvents", wrappedDateOnly)
.ConfigureAwait(true);
// Fan-out/fan-in
var eventTasks = events
.Select(x => taskOrchestrationContext.CallActivityAsync("ProcessEvent", input: x))
.ToList();
await Task.WhenAll(eventTasks).ConfigureAwait(true);
await taskOrchestrationContext
.CallActivityAsync("Export", wrappedDateOnly)
.ConfigureAwait(true);
}
[Function("FlagDatabaseEntries")]
public Task FlagDatabaseEntries([ActivityTrigger] WrappedDateOnly referenceDate)
{
// Flags entries in database table A to be processed using given referenceDate.
return Task.CompletedTask;
}
[Function("CreateEvents")]
public Task<string[]> CreateEvents([ActivityTrigger] DateOnly referenceDate)
{
// Creates events based on the entries flagged in the database by previous activity.
return Task.FromResult(Array.Empty<string>());
}
[Function("ProcessEvent")]
public Task ProcessEvent([ActivityTrigger] string eventToProcess)
{
// Process event and some of the events result in data being added to database table B.
return Task.CompletedTask;
}
[Function("Export")]
public Task Export([ActivityTrigger] DateOnly referenceDate)
{
// Export data from the database populated by processing the events.
return Task.CompletedTask;
}
}
public class WrappedDateOnly
{
public DateOnly DateOnly { get; set; }
}





Таким образом, «детерминированное» требование в случае оркестраторов устойчивых функций существует только потому, что платформа устойчивых задач выполняет код несколько раз по мере поступления результатов от действий. Таким образом, при одних и тех же входных данных оркестратора + одних и тех же выходных данных (+ события и т. д.) код оркестратора всегда должен выполнять одни и те же шаги. В этом смысле ваш оркестратор выглядит детерминированным.
Тем не менее, проблема, о которой вы говорите, требует рассмотрения. Я думаю, то, что вы предлагаете, имеет смысл. Возврат идентификаторов в процесс - это распространенный шаблон, который я использовал.
Одна вещь, которая, возможно, нуждается в разъяснении, это то, что действия не запускаются снова во время воспроизведения. После того как действие вернет результат, он будет прочитан из хранилища таблиц вместо повторного вызова действия.
Небольшое замечание: вам не нужно ConfigureAwait() ни на одном из вызовов.
Ну, единственное, что вам нужно учитывать, это то, что вы сказали, что отмеченные строки могут быть другими в этот момент. Еще одна вещь, о которой следует подумать, — это если вы хотите использовать определенный идентификатор экземпляра оркестратора. Я заметил, что вы указываете дату в качестве входных данных для оркестратора, что, вероятно, также влияет на данные, к которым вы прикасаетесь. Вы можете сделать эту дату идентификатором экземпляра, и это предотвратит запуск другого экземпляра с той же датой, что и ввод. Не уверен, что это полезно в вашем конкретном случае, так как я недостаточно знаю об этом :)
Я не знал, что могу контролировать идентификатор экземпляра, но вы правы, я хочу предотвратить одновременный запуск нескольких экземпляров с одной и той же датой :)
Дополнительную информацию о шаблоне singleton можно найти здесь: learn.microsoft.com/en-us/azure/azure-functions/durable/…
Но вы бы сказали, что это нормально, даже если я не позволю активности 1 возвращать идентификаторы? Что мне нужно только учитывать в логике действия 2, что оно может выполняться несколько раз? После завершения действия 2 сгенерированные события сохраняются оркестратором, и, таким образом, с этого момента все в порядке. Замечательно! Благодарю за разъяснение.