Date: 2020-05-31

За несколько недель до 14 февраля системе Dodo IS немного поплохело под нагрузкой. Одной из причин стало то, что в backend’ах мобильного приложения и сайта не совсем корректно работали политики поверх HttpClient’а (Retry, Circuit Breaker, Timeout). В этой статье я хочу поделиться с вами потенциальными проблемами, которые могут возникнуть при неправильном использовании таких политик.

Политики HttpClient’а и надёжность запросов

Для начала краткая вводная: о каких «политиках поверх HttpClient’а» мы говорим, и зачем они нужны?

Допустим, сервис А запрашивает какие-то данные у сервиса B путём обычного http-запроса. К сожалению, сеть — штука ненадёжная, а сервера могут выходить из строя. Мы не можем гарантировать, что наш запрос успешно дойдёт и будет обработан на стороне сервиса B.

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

  1. Retry policy — для ситуации, когда в ответ на наш запрос вернулась ошибка, существует политика повторных запросов. Идея очевидная: если что-то пошло не так, то давайте попробуем ещё раз?
  2. На случай, если мы ждём ответ от сервиса B, а его всё нет, придумана политика таймаутов.
  3. Circuit Breaker — ещё одна интересная политика, которая позволяет нам останавливать все запросы к какому-то сервису, если мы точно знаем, что он недоступен (подробнее см. дальше).

Такие политики работают как обёртка над стандартным HttpClient’ом. Каждая из политик перехватывает запрос, проверяет ответ от сервера и выполняет какие-то операции.

Спасибо Polly за наше счастливое детство

В мире .NET эта проблема в некотором смысле решена. Есть прекрасная библиотека Polly, которая предоставляет уже готовые политики. Достаточно выбрать те, которые вам нужны, обернуть свой HttpClient, и дело в шляпе. Но, как мы знаем, дьявол кроется в деталях.

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

Как ошибиться в 3 строках кода 4 раза

В чём же тут проблема? Берём клиент, обвешиваем нужными политиками, которые декорируют исходный HttpClient, и всё работает. Ниже приведён пример, как сейчас собирается HttpClient с помощью IHttpClientBuilder’а и какие политики к нему применяются.

clientBuilder    .AddRetryPolicy(settings.RetrySettings)    .AddCircuitBreakerPolicy(settings.CircuitBreakerSettings)    .AddTimeoutPolicy(settings.TimeoutPerTry);

Здесь ровно три строчки кода с применением политик. Ниже мы рассмотрим, как можно ошибиться в каждой из них, вдобавок ещё и порядок напутать. Итого 4 ошибки.

Retry policy

Начнём с повторных запросов. Тут вроде бы всё достаточно просто: если код ответа от сервера находится в списке кодов, которые нужно «ретраить» (разрешите мне ввести в нашу терминологию такое классное русское слово), то мы выполняем запрос ещё раз. В самой библиотеке Polly есть описание синтаксиса и работы такой политики.

В чём же тут можно ошибиться? На самом деле, когда мы имеем дело с распределёнными системами, то ошибиться можно абсолютно во всем. Рассмотрим несколько моментов, которые могут потенциально вызвать проблему.