Статья опубликована на моем сайте.

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

Сервис

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

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

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

Автоматический выключатель

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

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

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

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

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

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

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

Выполнение

Я собрал голую реализацию. Для простоты в нем отсутствует надлежащая обработка ошибок и др. поэтому, пожалуйста, не копируйте/вставляйте это в продакшн.

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

Здесь особо не на что смотреть. Скрипт подключится к серверу AMQP, создаст очередь и привяжет ее к обмену для получения сообщений. Когда сообщение получено, вызывается функция process. Эта функция вызывает внешний шлюз. Если вызов успешен, он подтверждает получение сообщения. Если это не удается, он отклоняет его, что возвращает его обратно в очередь, и он будет обработан снова.

Теперь мы добавим сюда автоматический выключатель.

Во-первых, нам нужно подготовить две функции. Один для (повторной) подписки на очередь и один для временной отмены подписки (паузы). Функция pause использует потребительский тег, который мы запоминаем в процессе подписки. Он также планирует повторную подписку по истечении периода BREAKER_PAUSE timeout.

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

И это почти все. Конечно, в реальной реализации у нас, по крайней мере, была бы некоторая регистрация и мониторинг этого события, но в этой простой форме это можно использовать в качестве примера того, как реализовать прерыватель цепи в этом сценарии обслуживания в стиле рабочего.

Автоматический выключатель в онлайн-сервисах

В этой службе рабочего типа можно приостановить обработку входящих сообщений. А как насчет онлайн-сервисов, где нельзя делать паузы и нужно отвечать на входящие запросы? Как обычно, ответ: «Это зависит». У вас есть выбор из нескольких сценариев в зависимости от вашего варианта использования.

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

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

В зависимости от вашей бизнес-логики вы также можете делать определенные бизнес-выборы. Возьмем пример услуги, которая отвечает на вопрос, есть ли товар на складе или нет. Если 99% ваших товаров обычно есть на складе, можно ответить «есть в наличии», даже если вы не уверены, потому что соединение с вашей системой управления складом отключено. Было бы неплохо зарегистрировать эти предполагаемые ответы и составить отчет об этих случаях, чтобы ваши сотрудники могли проверить его позже.

Приложение 1: Полный сценарий

Исходный код доступен на Github.



Приложение 2. Дополнительная литература