Pull to refresh

Comments 36

Да, в идеальном мире NRE не должно быть, но мы — живые люди, и мы ошибаемся. Для себя мы пока решили оставить ретраи для кода 500, но вопрос всё ещё дискуссионный.
Очень философски
При работе с распределенными системами нужно быть немножко философом :)
При работе с распределенными системами и разработке браузеров)

норм, vk api опрашивать — самое оно, его как мейл купил, так там через день 500 стало падать

Отличная статья, это нужно прочитать каждому, кто собирается делать автоматические повторы запросов. Но возникает вопрос — а зачем это все нужно? Есть очень простая политика — сервис вернул ошибку, ретранслируй ее пользователю, не пытайся повторять запросы. Не вижу смысла использовать что-то ещё в сервисе заказа пиццы или интернет магазине. Какие проблемы пользователя или бизнеса вся эта машинерия решает?

Есть очень простая политика — сервис вернул ошибку, ретранслируй ее пользователю, не пытайся повторять запросы.

А что пользователь будет с этой ошибкой делать?


Какие проблемы пользователя или бизнеса вся эта машинерия решает?

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

Я комментарий свой написал когда поймал себя на мысли, что тяжело придумать такие трансиентные ошибки, которые бы а) возникали достаточно часто, чтобы это слало проблемой и б) решались сами собой в пределах 10 секунд.


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

Простейший пример — сеть моргнула.

Сеть моргнула это пожалуй единственный сценарий, который очевиден. Но он же и самый редкий в контексте сервиса, который крутится в дата центре, с надежной сетью. Даже если она падает, то случается это так редко, что тратить месяцы усилий на корректный retry кажется излишним.

Ниже вот подсказывают кейс с пулом сервисов и 429. Это понятный сценарий и очень даже жизненный. Я о таких сценариях, в контексте Dodo и спрашиваю.

Самый простой пример — сервис, который мы планируем ретраить, крутится на n серверах. В какой-от момент один из серверов начинает плохо себя чувствовать (память кончилась, в рестарт ушел — в облаке часто что-то случается). В такой ситуации запрос, пришедший на умирающий сервер, после ретрая с большой вероятностью (nginx выводит плохие сервера из апстима) попадет на здоровую машину и пользователь не заметит проблемы.
У нас такое случается постоянно, по разным причинам.

Так может это у клиента сеть моргнула, а не в дата-центре.

Мы же говорим о системе которая живет в облаке, скорее всего в одном дата центре.

Почему бы?


При этом обсуждаются синхронные сценарии

Почему бы?


Ну то есть вы взяли какие-то допущения, я понимаю, но почему вы считаете, что эти допущения верны?


Скажем, у меня вот мобильное приложение говорит с бэкендом (тут, в принципе, уже все это применимо, но на мобилке, все-таки, маловероятен .net, так что пойдем дальше), и запускает там некую условно долгоиграющую (15-30-60 секунд) задачу. Это, понятное дело, уже асинхрония (в моем случае — через поллинг, но не важно), поэтому второе ваше допущение неверно. А бэкенд говорит с сервисом, предоставляемым другой компанией, и они в разных облаках, если не на разных континентах (что уже передает привет всем сетевым проблемам), и в документации на этот сервис явно написано: бывает вот такая ошибка, в этом случае сделайте ретрай через время, указанное в заголовке.


Если у меня есть политики повторов в бэкенде, все будет работать более-менее само. Если у меня бэкенд будет просто пробрасывать ошибку пользователю, пользватель будет сам нажимать кнопку "повторить" (если мы ее ему дали) и расстраиваться. А зачем?

Я говорю о сценарии с которого все началось в статье — «Одной из причин стало то, что в backend’ах мобильного приложения и сайта не совсем корректно работали политики поверх HttpClient’а (Retry, Circuit Breaker, Timeout).»

Ну так у меня ровно это и есть: бэкенд мобильного приложения. Он же бэкенд сайта.

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

Ну и, в дополнение, существует еще ряд несистемных транзиентных ошибок, которые могут быть заложены бизнес-логикой приложения. Например, eventually consistency или optimistic concurrency в принципе используют такой дизайн, что ошибки (а с ними и ретраи) будут неизбежны.

Сколько можно делать retry? Где-то несколько секунд, ну секунд 20 максимум, после этого пользователю запрос уже не интересен. Из ошибок, которые сами быстро исправляются я могу придумать только рестарт сервиса. Какие конкретно ошибки в вашей системе вы обходите с помощью повторов запроса?

Несколько секунд, а тем более 20, это довольно значительное время даже по меркам http-запросов.
Я привёл выше ошибки с eventually consistency, когда информация, нужная для запроса, еще не успела записаться в БД. Ошибки блокировки записей в БД.
И не забывайте, что речь идёт не только о взаимодействии пользователя с системой, а о распределенной системе, где сервисы взаимодействуют между собой в том числе через http-запросы. Если Вы думаете, что в интернет-магазине весь процесс заканчивается, после того, как пользователь нажал на кнопку «Заказать», то Вы сильно ошибаетесь.
Тут везде ответ: it depends.

Про кейсы.
Ну например, мы послали запрос в пулл серверов и получили ответ 429. Что это означает? Это означает, что конкретный инстанс сейчас перегружен, ему тяжело. Это не значит, что соседний инстанс не сможет обработать запрос.
Таймаут мы можем получить опять же по причине утилизации коннекшенов.
Кейс с рестартом сервиса тоже валидный, так как по разным причинам такое может случиться.

Про количество и время.
Опять же, вопрос что с чем взаимодействует. Например, у нас есть платежный шлюз и есть эквайреры, у которых заложено очень большое время на ответ. И ты вынужден, во-первых, долго ждать, во-вторых, очень хочется прийти в консистентное состояние в результате взаимодействия.
Экваеры и платежи это мне близко — я пишу торговых роботов, там тоже retry на каждом шагу, потому что биржи и всякие API находятся далеко и имеют тенденцию отваливаться слишком часто. И кажется это как раз сценарии, на которые polly заточена и где все эти retry и CB нужны и оправданы.

За пример с пулом серверов спасибо. Пожалуй пока это единственный сценарий, когда retry оправдан в рамках обработки пользовательского запроса, а не какой-то background job.

Кстати, а почему 429, а не 503?


429 означает же, что конкретный клиент сделал слишком много запросов, а перегрузка чаще возникает когда клиентов много.

У них немножко разная природа. 503 тоже считается транзиентной ошибкой и тоже ретраится.

429 это же клиентский код, это значит, что сервер может его использовать пока он еще в состоянии обрабатывать запросы, но ему уже сложно. Например, когда вы используете bulkhead на стороне сервера, и намеренно обрубаете запросы, которые не влезают в очередь.
А 503 – это уже как бы последняя стадия. Не смогла, так не смогла.
Далеко не все сервисы могут нормально понимать что им плохеет из-за большого кол-ва запросов и отсылать 429. Чтобы сервис так работал, надо еще постараться. А в многих случаях из загруженного сервиса валятся 503 или еще чего хуже. И что там будет валиться сложно предугадать и можно попробовать на перформанс тестах отловить, но все очень вариативно.
Потому что «синхронность» запроса она очень условная и внутри запрос разойдется на кучу потоков, могуть создаться деад-локи, может заблочится доступ к файлам, может какой-нибудь сервис начать отбивать ошибку перегрузки, а может тупо отваливаться по таймауту. Очередь может забиться и перестать принимать сообщений и т.д.
Все правда. В частности, мы специально работали над тем, чтобы некоторые наши сервисы умели отдавать 429.
Маленькое добавление:
Из своих вариантов: 404 (балансировщик выкинул все ноды сервиса, нет ни одной доступной), таймаут соединения, 502 (в балансировщике сервис есть — по факту не в состоянии ответить ), etc
Такие политики работают как обёртка над стандартным HttpClient’ом.

Скорее "плагин", а не обертка. Под оберткой обычно имеют ввиду агрегирование одного класса другим

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

Есть сервис, который выполняет какие-то операции и вызывает другой сервис. Этот сервис тоже выполняет операции и вызывает сервис. Наконец, последний сервис просто что-то делает. У каждой операции в пайплайне есть свой таймаут, например 10 секунд. Если собственные операции каждого сервиса будут работать по 4 секунды, то на гейтвее может случиться ошибка таймаута, хотя каждый сервис отработал за корректное время. Вы как-то решаете подобные проблемы?
Вы правы, хорошее замечание, такая проблема существует. И, пожалуй, неправильно выбирать какой-то константный таймаут для любого сервиса вне зависимости от архитектуры вашего решения. Сейчас мы больше опираемся на здравый смысл с выбором таймаутов в разных сервисах. Какого-то эффективного механизма или формулы правильной настройки таймаутов я пока не знаю.
Ну например в гайдлайнах некоторых советуют использовать паттерн Deadline для распределенных таймаутов: www.datawire.io/guide/traffic/deadlines-distributed-timeouts-microservices

Но вообще несмотря на то, что Polly — это прекрасная библиотека, вам наверное стоит пересмотреть идею использовать ее для CircuitBreaker'а. Раз у вас SOA, то вы наверное используете горизонтальное масштабирование, и вряд ли вы сможете шарить состояние CB между двумя инстансами одного сервиса. Возможно, целый ряд ваших проблем с распространением ошибки решат Hystrix или Istio, но CB-то уж точно стоит туда отправить.
Спасибо за ссылку на гайдлайн.

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

такой вопрос - где взять IHttpClientBuilder или IHttpClientFactory вне asp.net?

нужно выполнить запрос из произвольной DLL.

Ну или - как прикрутить эти Policy к HttpClient без этих инструментов.

Там же где и обычно — в пакете Microsoft.Extensions.Http

Sign up to leave a comment.