Управление версиями в устойчивых функциях (Функции Azure)

Функции добавляются, удаляются и изменяются на протяжении времени существования приложения. Устойчивые функции позволяют связывать функции ранее недоступными способами, и это влияет на управление версиями.

Как управлять критическими изменениями

Есть несколько критических изменений, о которых следует знать. В этой статье рассматриваются самые распространенные из них. Общим для них является то, что на новую и имеющуюся функции оркестрации влияют изменения кода функции.

Изменение сигнатур функций действий или сущностей

Изменение сигнатуры относится к изменению имени, входных или выходных данных функции. Если для функции действия или сущности применено такое изменение, это может привести к нарушению работы функции оркестратора, которая зависит от нее. Это особенно относится к типобезопасным языкам. Обновление функции оркестратора для применения этого изменения может привести к нарушению работы имеющихся активных экземпляров.

В качестве примера предположим, что имеется следующая функция оркестратора.

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

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

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

Это изменение отлично работает для всех новых экземпляров функции оркестратора, но может нарушить работу всех экземпляров, работающих в режиме выполнения. Например, рассмотрим случай, когда экземпляр оркестрации вызывает функцию Foo, получает логическое значение, а затем достигает контрольной точки. Если на этой точке развернуто изменение сигнатуры, экземпляр с контрольной точкой завершится ошибкой сразу после возобновления работы и воспроизведения вызова Foo. Этот сбой происходит потому, что результат в таблице журнала является логическим значением, но новый код пытается десериализовать его в строковое значение, что приводит к непредвиденному поведению или даже исключению среды выполнения для типобезопасных языков.

Это лишь один из примеров того, как изменение сигнатуры может нарушить работу имеющихся экземпляров. Как правило, изменение способа вызова функции для оркестратора может привести к проблемам.

Изменение логики оркестратора

Другой класс проблем с управлением версиями связан с таким изменением кода функции оркестратора, которое может изменить путь выполнения для активных экземпляров.

Рассмотрим следующую функцию оркестратора:

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

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

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

Это изменение добавит новый вызов функции в SendNotification между Foo и Bar. Изменения сигнатуры отсутствуют. Проблема возникает, когда имеющийся экземпляр возобновляет работу после вызова Bar. Во время воспроизведения, если при исходном вызове функции Foo возвращается значение true, то воспроизведение функции оркестратора приводит к вызову SendNotification, который находится вне его журнала выполнения. Среда выполнения обнаруживает это несоответствие и отображает ошибку недетерменированной оркестрации, поскольку она обнаружила вызов SendNotification вместо Bar. Проблема такого же рода может возникнуть при добавлении вызовов API в другие устойчивые операции, например создание устойчивых таймеров, ожидание внешних событий, вызов подчиненных оркестраций и т. д.

Стратегии устранения рисков

Ниже приведены некоторые стратегии для устранения проблем с управлением версиями:

  • Отсутствие действий (не рекомендуется)
  • Остановка всех активных экземпляров
  • Выполнение параллельного развертывания

Ничего не предпринимать

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

  • Может возникать сбой оркестрации с ошибкой недетерминированной оркестрации.
  • Оркестрации могут зависать, сообщая состояние Running.
  • Если функция была удалена, любая вызывающая ее функция может завершиться ошибкой.
  • Если удалена функция, запланированная к выполнению, приложение может испытывать низкоуровневые сбои во время выполнения в подсистеме платформы устойчивых задач, что может привести к существенному снижению производительности.

Из-за этих потенциальных сбоев пассивная стратегия не рекомендуется.

Остановка всех активных экземпляров

Другим вариантом является остановка всех активных экземпляров. Если используется поставщик службы хранилища Azure по умолчанию для Устойчивых функций, остановить все экземпляры можно путем очистки содержимого внутренних очередей control-queue и workitem-queue. Можно также остановить приложение-функцию, удалить эти очереди и снова запустить приложение. После перезапуска приложения очереди будут созданы снова автоматически. Предыдущие экземпляры оркестрации могут оставаться в состоянии выполнения неограниченно долго, но они не будут перегружать журналы сообщениями об ошибках или нарушать работу приложения. Этот подход идеально подходит для быстрой разработки прототипов, в том числе локальной.

Примечание

Он требует прямого доступа к базовым ресурсам хранилища и может подходить не для всех поставщиков хранилищ, поддерживаемых Устойчивыми функциями.

Выполнение параллельного развертывания

Параллельное развертывание с более старыми версиями — наиболее отказоустойчивый способ обеспечить безопасное развертывание критических изменений. Это можно сделать с помощью любого из следующих способов:

  • Развертывайте все обновления как полностью новые функции, оставляя существующие функции без изменений. Обычно делать это не рекомендуется из-за сложности, связанной с рекурсивным обновлением вызывающих объектов новых версий функций.
  • Развертывание всех обновлений в качестве нового приложения-функции с помощью другой учетной записи хранения.
  • Разверните новую копию приложения-функции в той же учетной записи хранения, но с обновленным именем центра задач. Это приведет к созданию новых артефактов хранилища, которые могут использоваться новой версией приложения. Старая версия приложения продолжит выполняться с использованием предыдущего набора артефактов хранилища.

Параллельное развертывание является рекомендуемым методом развертывания новых версий приложений-функций.

Примечание

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

Слоты развертывания

При параллельном развертывании в Функциях Azure или Службе приложений Azure рекомендуется развернуть новую версию приложения-функции в новом слоте развертывания. Слоты развертывания позволяют параллельно запускать несколько копий приложения-функции, при этом только один слот может быть активным рабочим слотом. Предоставить новую логику оркестрации для имеющейся инфраструктуры может быть так же просто, как заменить новую версию в рабочем слоте.

Примечание

Эта стратегия оптимально подходит при использовании триггеров HTTP и веб-перехватчика для функций оркестратора. Для триггеров, отличных от HTTP, таких как очереди или Центры событий, определение триггера должно основываться на параметре приложения, который обновляется в рамках операции замены.

Дальнейшие действия