Реализация шаблона размыкателя цепи

Совет

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

.NET Microservices Architecture for Containerized .NET Applications eBook cover thumbnail.

Как отмечалось ранее, следует реализовать обработку сбоев, на восстановление после которых может требоваться неопределенное количество времени, например, при попытке подключиться к удаленной службе или ресурсу. Обработка подобных сбоев может повысить стабильность работы и отказоустойчивость приложения.

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

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

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

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

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

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

Реализация шаблона размыкателя цепи с помощью IHttpClientFactory и Polly

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

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

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

// Program.cs
var retryPolicy = GetRetryPolicy();
var circuitBreakerPolicy = GetCircuitBreakerPolicy();

builder.Services.AddHttpClient<IBasketService, BasketService>()
        .SetHandlerLifetime(TimeSpan.FromMinutes(5))  // Sample: default lifetime is 2 minutes
        .AddHttpMessageHandler<HttpClientAuthorizationDelegatingHandler>()
        .AddPolicyHandler(retryPolicy)
        .AddPolicyHandler(circuitBreakerPolicy);

Метод AddPolicyHandler() добавляет политики для объектов HttpClient, которые вы будете использовать. В этом случае он добавляет политику Polly для размыкателя цепи.

Для реализации более модульного подхода политику размыкателя цепи можно определить в отдельном методе GetCircuitBreakerPolicy(), как в следующем примере.

// also in Program.cs
static IAsyncPolicy<HttpResponseMessage> GetCircuitBreakerPolicy()
{
    return HttpPolicyExtensions
        .HandleTransientHttpError()
        .CircuitBreakerAsync(5, TimeSpan.FromSeconds(30));
}

В приведенном выше примере кода политика размыкателя цепи настроена таким образом, что прерывает или размыкает цепь после пяти последовательных попыток при повторной отправке HTTP-запросов. Если это произойдет, цепь разомкнется на 30 секунд: в этот период вызовы не будут размещаться, а будут немедленно завершаться размыкателем цепи. Политика автоматически интерпретирует соответствующие исключения и коды состояния HTTP как ошибки.

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

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

С точки зрения использования при использовании HttpClient вам не нужно добавлять ничего нового, так как код совпадает с использованием HttpClient , IHttpClientFactoryкак показано в предыдущих разделах.

Тестирование повторных HTTP-запросов и размыкателей цепи в eShopOnContainers

При запуске решения eShopOnContainers в узле Docker должно запускаться несколько контейнеров. Некоторые контейнеры, например контейнер SQL Server, запускаются и инициализируются медленнее, чем другие. Это особенно проявляется при первом развертывании приложения eShopOnContainers в Docker, так как при этом должны настраиваться образы и база данных. Из-за более медленного запуска некоторых контейнеров остальные службы могут изначально вызывать исключения HTTP, даже если вы настроили зависимости между контейнерами на уровне docker-compose, как было описано в предыдущих разделах. Зависимости docker-compose между контейнерами существуют на уровне процессов. Процесс точки входа в контейнер может быть запущен, но сервер SQL Server может быть не готов принимать запросы. В результате может происходить непрерывный ряд ошибок, и в приложении может возникнуть исключение при попытке использовать данный контейнер.

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

eShopOnContainers решает эти проблемы при запуске всех контейнеров с помощью шаблона повторных попыток, описанного ранее.

Тестирование размыкателя цепи в eShopOnContainers

Есть несколько способов разомкнуть цепь и протестировать ее в eShopOnContainers.

Один из вариантов — уменьшить разрешенное число повторных попыток до 1 в политике размыкателя цепи и повторно развернуть все решение в Docker. При одной повторной попытке высока вероятность того, что HTTP-запрос завершится сбоем во время развертывания, размыкатель цепи откроется и произойдет ошибка.

Другой вариант — использовать пользовательское ПО промежуточного слоя, реализованное в микрослужбе Basket. При его включении оно перехватывает все HTTP-запросы и возвращает код состояния 500. Чтобы включить ПО промежуточного слоя, можно выполнить запрос GET к сбойному универсальному коду ресурса (URI), например, следующему:

  • GET http://localhost:5103/failing
    Этот запрос возвращает текущее состояние ПО промежуточного слоя. Если оно включено, запрос возвращает код состояния 500. Если оно отключено, ответа нет.

  • GET http://localhost:5103/failing?enable
    Этот запрос включает ПО промежуточного слоя.

  • GET http://localhost:5103/failing?disable
    Этот запрос отключает ПО промежуточного слоя.

Например, после запуска приложения вы можете включить ПО промежуточного слоя, выполнив запрос к указанному ниже коду URI в любом браузере. Обратите внимание на то, что микрослужба размещения заказов использует порт 5103.

http://localhost:5103/failing?enable

После этого можно проверить состояние, используя код URI http://localhost:5103/failing, как показано на рис. 8-5.

Screenshot of checking the status of failing middleware simulation.

Рис. 8-5. Проверка состояния "нерабочего" ПО промежуточного слоя ASP.NET (в этом случае отключено)

На этом этапе микрослужба Basket возвращает код состояния 500 при каждом вызове.

Когда ПО промежуточного слоя будет запущено, вы можете попытаться сделать заказ в веб-приложении MVC. Так как запрос завершается сбоем, цепь размыкается.

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

public class CartController : Controller
{
    //…
    public async Task<IActionResult> Index()
    {
        try
        {
            var user = _appUserParser.Parse(HttpContext.User);
            //Http requests using the Typed Client (Service Agent)
            var vm = await _basketSvc.GetBasket(user);
            return View(vm);
        }
        catch (BrokenCircuitException)
        {
            // Catches error when Basket.api is in circuit-opened mode
            HandleBrokenCircuitException();
        }
        return View();
    }

    private void HandleBrokenCircuitException()
    {
        TempData["BasketInoperativeMsg"] = "Basket Service is inoperative, please try later on. (Business message due to Circuit-Breaker)";
    }
}

Ниже представлен их список. Политика повторных попыток несколько раз пытается выполнить HTTP-запрос и получает ошибки HTTP. Когда число повторных попыток достигает максимального значения, заданного для политики размыкателя цепи (в этом случае 5), приложение вызывает исключение BrokenCircuitException. В результате выводится понятное сообщение для пользователя, как показано на рис. 8-6.

Screenshot of the MVC web app with basket service inoperative error.

Рис. 8-6. Размыкатель цепи возвращает ошибку в пользовательский интерфейс

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

Наконец, еще одной возможностью для CircuitBreakerPolicy является использование методов Isolate (размыкает цепь и сохраняет ее в таком состоянии) и Reset (снова замыкает цепь). Таким образом, можно создать служебную конечную точку HTTP, которая напрямую вызывает методы Isolate и Reset политики. Такую конечную точку HTTP с надлежащей защитой можно также использовать в рабочей среде для временной изоляции подчиненной системы, например, если ее необходимо обновить. Кроме того, с ее помощью можно размыкать цепь вручную для защиты подчиненной системы, если есть подозрения на ее неисправность.

Дополнительные ресурсы