Прекращение работы службы - это наиболее распространенный способ действий при обнаружении непредвиденной ошибки. Ключевое слово здесь - неожиданно.

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

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

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

Поскольку подобные сбои часто бывают кратковременными, обычно достаточно просто попытаться выполнить HTTP-запрос еще раз. Если сбой действительно временный, проблема с сетью исчезнет сама собой, и вторая попытка (или третья и т. Д.) Вызова API часто будет работать.

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

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

Базовый пример: повторить попытку после ошибки HTTP

В следующем примере выполнение HTTP API будет повторяться, если будет поднято HTTPError. Он будет повторять попытки максимум три раза с задержкой 100 миллисекунд между первой и второй попытками и задержкой 200 миллисекунд между второй и третьей попытками.

В примере используется библиотека Retry, которая поставляется со стеком Eventide, но не обязательно использовать эту библиотеку. В экосистеме Ruby доступно несколько реализаций повторных попыток. Также можно использовать собственное retry ключевое слово Ruby при условии, что оно подходит для конкретных обстоятельств.

Будьте осторожны, чтобы избежать бесконечного цикла повторных попыток при реализации логики повторных попыток с использованием необработанных строительных блоков Ruby begin / rescue / retry. Из бесконечного цикла повторных попыток будет сложно выйти безопасно, и он может вынудить оператора убить процесс полным перебором, вместо того, чтобы позволить ему завершиться изящно.

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

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

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

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

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

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

Повторные попытки на уровне потребителя

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

Тем не менее, можно реализовать обобщенную повторную попытку в error_raised методе потребителя.

На самом деле непрактично использовать библиотеку типа Retry при повторных попытках у потребителя, поскольку это приведет к трем попыткам обработки сообщения, а не к двум.

При реализации обобщенной обработки чего-то вроде MessageStore::ExpectedVersion::Error следует использовать более примитивный механизм, как показано выше. Как обычно при реализации метода error_raised, очень важно, чтобы ошибка, переданная методу, была повторно вызвана, чтобы гарантировать, что ошибка может завершить процесс как исключительное условие.

Мониторинг процессов на системном уровне как механизм повтора

Мониторинг процессов на системном уровне, такой как systemctl, в systemd, предоставляет еще более примитивные средства повторной попытки.

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

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

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

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

Все дело в перезагрузке

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

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

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