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

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

Так что именно здесь происходит? Оказывается, здесь задействовано несколько различных функций архитектуры, основанной на услугах: автоматические выключатели, ограничения скорости и повторные попытки. Давайте посмотрим на них и посмотрим, как они взаимодействуют с каждым из них.

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

Предохранители

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

Автоматический выключатель обычно реализует две разные стратегии: последовательные отказы запускают выключатель, когда встречается более фиксированного количества отказов, в качестве альтернативы может быть установлен коэффициент успешности, который прерывает те, которые опускаются ниже установленного порога. Finagle, клиент Scala, который мы используем, предлагает оба варианта и по умолчанию использует 5 последовательных отказов.

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

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

Вы спросите, если бы у нас был автоматический выключатель и он был правильно настроен, почему мы все же отказались от обслуживания бара? Объяснение можно найти, посмотрев на следующую концепцию: ограничения скорости. Каждый сервер имеет максимальную нагрузку, с которой он может справиться, и, если вы не можете эластично масштабировать серверы, вы бы предпочли быстро сбрасывать запросы при приближении к этому пределу. Все наши услуги имеют ограничения по скорости. Во время инцидента два бара были отмечены как мертвые, весь трафик направлялся к единственному выжившему. Совокупный трафик превысил лимит скорости сервера, что привело к выдаче ответов «429 Too Many Requests» в дополнение к «500 Internal Server Error».

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

Повторные попытки

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

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

Давайте в последний раз взглянем на график с самого начала. На этот раз с небольшими аннотациями.

  1. Обычный трафик (зеленый) распределяется по всем трем серверам. Иногда возвращаются 429 ответов (бирюзовый), которые мы покорно игнорировали.
  2. Серверы начинают давать сбой и возвращают 500 ответов (красный). Паузы в трафике к первому серверу указывают на сработавший автоматический выключатель.
  3. Нагрузка увеличивается из-за 500 повторных попыток ответа, что еще больше усугубляет убогость службы бара. Большинство повторных попыток в конечном итоге приводят к ответу 429.
  4. Наконец, два сервера помечаются автоматическим выключателем как неработающие, направляя весь оставшийся трафик на первый сервер. Этот сервер превышает пределы скорости для большинства запросов, что предотвращает срабатывание автоматического выключателя, поскольку 429 ответов считаются успешными.

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