Поделиться через


Проблемы управления версиями и подходы в устойчивых функциях (Функции 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.
  • Если функция удалена, любая функция, пытающаяся ее вызвать, может завершиться с ошибкой.
  • Если функция удаляется после запланированного выполнения, приложение может столкнуться с низкоуровневыми сбоями среды выполнения в подсистеме устойчивых задач, что может привести к серьезному снижению производительности.

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

Версии оркестрации

Замечание

Версионирование систем оркестрации в настоящее время находится в стадии публичного предварительного просмотра.

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

При оркестрации версионирования:

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

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

Подробные рекомендации по настройке и реализации см. в разделе "Версионирование в Durable Functions".

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

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

Замечание

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

Параллельное развертывание

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

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

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

Замечание

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

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

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

Замечание

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

Дальнейшие шаги