Есть пресловутый Polly, который реализует несколько паттернов. Среди прочих — CircuitBreaker.
Но вот как лучше делать повторы на практике? Можно ли придумать некое универсальное настраиваемое решение? Что используете вы?
Некоторые мысли в форме потока сознания:
Скрытый текст
Давайте так — некая операция, которая не прошла. Тут сразу разделить на устраняемые и не устраняемые без перекомпиляции ошибки:
1. Устраняемые: сетевые (отвалилась база данных, отвалился внешний сервис), системные (как то файл заблокирован для записи, может потом разблокируется, т.к. админ просто открыл его в блокноте и потом закроет или нехватка места на диске — админ почистит).
2. Не устраняемые: ошибка в коде, как то не учли что логин может быть не только email-ом, но и телефоном — и пытаетесь телефон преобразовать к email — можно делать это бесконечное количество раз и результат будет всегда одним и тем же — долбежка не имеет смысла.
Т.е. сразу нужно как-то разделять исключения на два типа. Иногда это не так просто как кажется, иногда нет возможности строгой классификации
Далее. Пусть мы разделили худо-бедно ошибки и можем повторять. Но как часто повторять? Бесконечно? Может таки не стоит — может всему есть предел? Ок, этот предел можно конфигурировать. Но нужна какая-то хитрая формула.
Но вот, допустим, некий сервис внешний был не доступен сутки. Представьте какого значения достигло ЧФ и как долго ждать повтора Вроде можно ограничить — не более чем час. Или же какой-то рычаг оставить — повторить все. К примеру, когда вы узнали что была проблема с базой и она решилась.
Здравствуйте, Shmj, Вы писали:
S>Здравствуйте, sqrt, Вы писали:
S>>Что такое Polly и CircuitBreaker?
S>https://makolyte.com/csharp-circuit-breaker-with-polly/
S>>> Но вот как лучше делать повторы на практике? S>>Универсальных решений не бывает. Это анти-паттерн.
S>Что мешает сделать универсальное решение? Как вы предлагаете решать?
Здравствуйте, Shmj, Вы писали:
S>Но вот, допустим, некий сервис внешний был не доступен сутки. Представьте какого значения достигло ЧФ и как долго ждать повтора Вроде можно ограничить — не более чем час. Или же какой-то рычаг оставить — повторить все. К примеру, когда вы узнали что была проблема с базой и она решилась.
В отрыве от предметной области разговор бессмысленный. Если у тебя HFT, то ретрай даже в секунду никому не нужен. Если интерактивное приложение (некритичное) — то без привлечения внимания пользователя можно десяток-другой секунд потупить. Если бэкграунд сервис, опять же некритичный, то можно часами ждать. Система типа dead hand должна пытаться вечно.
Здравствуйте, ltc, Вы писали:
ltc>Если интерактивное приложение (некритичное) — то без привлечения внимания пользователя можно десяток-другой секунд потупить. Если бэкграунд сервис, опять же некритичный, то можно часами ждать. Система типа dead hand должна пытаться вечно.
Немного проанализируйте и вы поймете — все эти случаи отличаются только одним параметром — время попыток повтора. Думайте что мешает сделать универсальное решение, где повторы применимы.
Здравствуйте, Shmj, Вы писали:
S>Здравствуйте, ltc, Вы писали:
ltc>>Если интерактивное приложение (некритичное) — то без привлечения внимания пользователя можно десяток-другой секунд потупить. Если бэкграунд сервис, опять же некритичный, то можно часами ждать. Система типа dead hand должна пытаться вечно.
S>Немного проанализируйте и вы поймете — все эти случаи отличаются только одним параметром — время попыток повтора. Думайте что мешает сделать универсальное решение, где повторы применимы.
Термин взят из электротехники, где есть предохранитель с повторным включением, т.к. на линиях бывают случайные самопроходящие замыкания. Так и здесь: существуют случайные самопроходящие(сервис подвис на время) так и быстро устранимые сбои. Вот из среднего времени самопрохождения сбоя(максимального, минимального) и нужно исходить.
Здравствуйте, vsb, Вы писали:
vsb>Недавно реализовывал. Первоначальная задержка 5-7 минут. Максимальная 20-28 часов. Увеличивается на 0-40% каждый раз.
Это вы описали параметры конфигурации. Понимаете ли, что параметры можно менять от проекта к проекту — а само решение можно везде использовать одно и то же.
У меня сейчас схожая проблема проблема: есть библиотечный код, который пытается найти файл по текстовому запросу от юзер кода, в случае если ничего подходящего не нашлось я сообщаю об этом юзер коду и ожидаю что он передаст мне новую строку для поиска файла, который опять же может не найтись. Вот сколько раз спрашивать юзер код? Так можно до бесконечности висеть на этом месте.
Я даю юзер коду 10 попыток, после чего делаю некие действия по умолчанию и всё.
Здравствуйте, Shmj, Вы писали:
vsb>>Недавно реализовывал. Первоначальная задержка 5-7 минут. Максимальная 20-28 часов. Увеличивается на 0-40% каждый раз.
S>Это вы описали параметры конфигурации. Понимаете ли, что параметры можно менять от проекта к проекту — а само решение можно везде использовать одно и то же.
В спринге это есть. Я правда не пользовался сам, но вообще — есть. В Golang тоже, недавно к AWS SDK читал, там идет параметр Retry как интерфейс, я так понимаю, как раз чтобы настраивать его можно было.
Здравствуйте, Shmj, Вы писали:
S>Но вот как лучше делать повторы на практике? Можно ли придумать некое универсальное настраиваемое решение? Что используете вы?
Наверное самое универсальное и наиболее используемая модель это делать несколько повторов с экспонтенциальным ростом времени задержки между попытками. Если хотите еще больше "универсальности", то к времени задержки можно добавить случайную величину в пределах 1/4 этого времени. Так делают чтобы сделать нагрузку на ресурс более равномерной после провала в его доступности когда есть много клиентов, которые в противном случае могут параллельно пытаться делать повторы в одни и те же моменты времени создавая этим ненужный аврал.
Здравствуйте, Aquilaware, Вы писали:
A>Наверное самое универсальное и наиболее используемая модель это делать несколько повторов с экспонтенциальным ростом времени задержки между попытками. Если хотите еще больше "универсальности", то к времени задержки можно добавить случайную величину в пределах 1/4 этого времени. Так делают чтобы сделать нагрузку на ресурс более равномерной после провала в его доступности когда есть много клиентов, которые в противном случае могут параллельно пытаться делать повторы в одни и те же моменты времени создавая этим ненужный аврал.
Вот мне, кстати, всегда было непонятно, почему нужно именно экспоненциальное время задержки. Почему не арктангенс или кусочно-линейное? Ну, типа попробовали ежеминутно-через 5 минут — через полчаса — через час — и долбим раз в час.
Тогда у нас матожидание бездарно потерянного времени после починки системы ограничено получасом. А не неизвестным заранее значением — когда в итоге после починки сервера нужно ещё и идти перезапускать клиента, который "следующая попытка подключения будет произведена через 32 дня".
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Тогда у нас матожидание бездарно потерянного времени после починки системы ограничено получасом. А не неизвестным заранее значением — когда в итоге после починки сервера нужно ещё и идти перезапускать клиента, который "следующая попытка подключения будет произведена через 32 дня".
Супер! Столкнулся с такой проблемой на практике. Внешний сервис упал на 24 часа. Редко, но бывает.
Однако же — долбить раз в час на протяжении 1 года каждый час = тоже глупо. По-моему должен быть какой то лимит. Ну ок, бывает что нечто упадет на сутки. Ну на трое суток в худшем случае. Но зачем долбить после 3 суток или на крайняк 5 дней?
По сути тут дело все в конфигурации. Нужно 3 параметра:
1. На короткой дистанции. Может просто сетевая ошибка рядовая. Тут можно по тем же числам Фибоначчи или тем же экспоненциальным ростом.
2. Предел, после которого значение переходит в константу. К примеру, 1 час. Раз в час это уже достаточно редко и уже не будет так уж нагружать сервер.
3. Все-таки предел, когда даже долбежку раз в час нужно признать бессмысленной. К примеру, спустя 3 суток.
S>Немного проанализируйте и вы поймете — все эти случаи отличаются только одним параметром — время попыток повтора. Думайте что мешает сделать универсальное решение, где повторы применимы.
универсализм — как правило несет избыточность, зачем мне нужно решение с хиулиардом параметров если в моем случае я использую только 1
Здравствуйте, Shmj, Вы писали:
S>Однако же — долбить раз в час на протяжении 1 года каждый час = тоже глупо.
Почему? Нужно какое-то математическое обоснование термина "глупо". С моей точки зрения, лишнее обращение к серверу плохо только создаваемой нагрузкой. Если мы долбим ежечасно в течение года, то это всего-то 8760 обращений.
Сколько обращений к этому сервису мы делаем обычно? Несколько раз в минуту? Ну ок, при неисправности мы снижаем нагрузку на 2 порядка. Можно и на 3 — ну, то есть должно быть какое-то соотношение между "обычной" и "аварийной" интенсивностью.
S>По-моему должен быть какой то лимит. Ну ок, бывает что нечто упадет на сутки. Ну на трое суток в худшем случае. Но зачем долбить после 3 суток или на крайняк 5 дней?
Потому что ремонт может занять и 3 суток, и 5 дней.
Но опять же, всё зависит от контекста. Если для нас это жизненно важный сервис, то вместе с ним лежим и мы. Допустим, он сломался навсегда — владельцы разорились, не смогли восстановить. Или вообще, мы понимаем, что нет смысла полагаться на сервис, SLA которого настолько плоха. За какое время мы переключимся на альтернативу? Если за неделю, то не имеет никакого смысла долбить больше недели — имеет смысл ввести регламент "сервис, не отвечающий более суток, подлежит замене". С таким регламентом через 8 дней от начала сбоя у нас будет новая версия кода, в которой обращений к этому сервису вовсе нет.
А долбление раз в час нужно только для того, чтобы если этот сервис через пару дней всё же поднялся, то мы продолжим работу на нём, пока инженеры пилят замену.
S>По сути тут дело все в конфигурации. Нужно 3 параметра:
S>1. На короткой дистанции. Может просто сетевая ошибка рядовая. Тут можно по тем же числам Фибоначчи или тем же экспоненциальным ростом. S>2. Предел, после которого значение переходит в константу. К примеру, 1 час. Раз в час это уже достаточно редко и уже не будет так уж нагружать сервер. S>3. Все-таки предел, когда даже долбежку раз в час нужно признать бессмысленной. К примеру, спустя 3 суток.
Да, согласен, похоже на правду.
Но, повторюсь, я сам правильного ответа не знаю. Наверняка есть какие-то учебники по SRE, где всё это расписано и дано в упражнениях.
Возможно, правильный профиль backoff зависит от распределения времени восстановления после сбоя с учётом распределения вероятностей различных типов сбоев.
И вот эта вот экспоненциальная формула тщательно выведена и обоснована для некоторого класса таких распределений
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>>Однако же — долбить раз в час на протяжении 1 года каждый час = тоже глупо. S>Почему? Нужно какое-то математическое обоснование термина "глупо". С моей точки зрения, лишнее обращение к серверу плохо только создаваемой нагрузкой. Если мы долбим ежечасно в течение года, то это всего-то 8760 обращений. S>Сколько обращений к этому сервису мы делаем обычно? Несколько раз в минуту? Ну ок, при неисправности мы снижаем нагрузку на 2 порядка. Можно и на 3 — ну, то есть должно быть какое-то соотношение между "обычной" и "аварийной" интенсивностью.
Тут, скорее, не мат. обоснование а практическая целесообразность, здравый смысл и лицензионное соглашение.
К примеру, если речь о сервисе пополнения счета. Приняли от вас оплату и пробуем провести через шлюз оператора как его партнеры. Не прошло. Ну ок, платеж в статусе — на обработке. Думаю и ежу понятно, что в таком статусе — ну день еще может повисеть, ну 3 дня с учетом выходных — а вот год ждать пока тебе телефон пополнят — никто не будет..
S>>По-моему должен быть какой то лимит. Ну ок, бывает что нечто упадет на сутки. Ну на трое суток в худшем случае. Но зачем долбить после 3 суток или на крайняк 5 дней? S>Потому что ремонт может занять и 3 суток, и 5 дней.
Если ремонт 5 дней — то нужно отказываться от услуг такого поставщика. И быстренько за 1-2 дня переключиться на другого.
S>А долбление раз в час нужно только для того, чтобы если этот сервис через пару дней всё же поднялся, то мы продолжим работу на нём, пока инженеры пилят замену.
Здравствуйте, Shmj, Вы писали:
S>Но вот как лучше делать повторы на практике? Можно ли придумать некое универсальное настраиваемое решение? Что используете вы?
Такие решения давно придуманы. Полиси ретраев (количество и периодичность) в нормальных библиотеках выделены в отдельный классы (или функции). В библиотеке есть готовые классы — для постоянного времени между ретраями, для экспоненциального, и т.п. При желании — можно легко написать свой класс для кастомной логики. Open-closed Principe в действии
Мы используется экспоненциальный, до некоторого периода. Потом сбой, или постоянные редкие ретраи. Плюс джиттер
Здравствуйте, Sinclair, Вы писали: S>Вот мне, кстати, всегда было непонятно, почему нужно именно экспоненциальное время задержки. Почему не арктангенс или кусочно-линейное? Ну, типа попробовали ежеминутно-через 5 минут — через полчаса — через час — и долбим раз в час.
Конкретные сценарии и ограничение сверху зависят от системы. А цель — избежать того, что называется "dogpile" — различия в нагрузках на различных этапах (например, при старте сервера и в процессе нормальной работы). (Я сейчас по Release It, second edition, pp. 78-80 привожу).
Допустим, сервис А вызывает сервис Б. В какой-то момент сервис Б не выдержал нагрузки и ушел в дикие задержки. Некоторые экземпляры упали, некоторые нет. Часа через три проснулись разработчики и операторы сервиса Б и попробовали его починить. Только есть проблема — ваш сервис А к этому моменту дает четырехкратную нагрузку на сервис Б! Четырехктратная — это обычная нагрузка плюс повторы операций за три часа, которые сервис лежал. При этом при старте сервис может требовать больше ресурсов — кэши не заполнены, код не проджитен. Он уже при нормальной нагрузке упал, а тут — четырехкратная (и возрастающая, так как текущую нагрузку он облуживать еще не может).
Экспонента позволяет избежать такого "умножения" нагрузки. При этом активно используется и рандомизация — чтобы пришедшая за короткое время волна тоже размазывалась (в диапазоне, допускаемом "экспонентой"). Без рандомизации волна будет повторяться и может приводить к повторным отказам. А с рандомизацией она будет увеличиваться в длине но уменьшаться в высоте (при условии, что остальная нагрузка не пиковая).
Если у вас запросы раз в минуту — можно делать любое время задержки. А вот при сотнях-тысячах запросов в минуту все эти тонкости становятся уже важны. Они позволяют ограничить масштабы проблемы и в дальнейшем быстрее вернуться в рабочий режим. Если интересует тема надежности, рекомендую вышеупомянутую Release It — легко читается, содержит как примеры реальных отказов систем, так и практические советы о том, как избежать проблем.
Здравствуйте, Shmj, Вы писали:
S>Но вот как лучше делать повторы на практике? Можно ли придумать некое универсальное настраиваемое решение? Что используете вы?
А зачем универсальное? Неясно зачем для этого вообще некий супер-фреймворк с трёхэтажной конфигурацией? Функциональность тривиальная — дать задержку, повторить. Три строчки кода. Не нужен для этого фреймворк.
Самое сложное в этой задаче — изучить специфику сервиса — как он ведёт себя при падении, как быстро чинится и т.п. — на этом основании придумать собственно придумать функцию, выдающую задержки, чтобы система восстанавливалась как можно быстрее и требовала минимум ручного вмешательства. А это — никак не алгоритмизуется в общем случае.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Ну если хочется очень заморочиться, то можно собрать статистику, придумать таргет функцию и построить оптимальную логику ретрая под таргет функцию.
Если этот процесс сделать автоматическим, то получишь адаптивный механизм.
Только, имхо, для 99.99% задач это нафиг не нужно.