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

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

В Grubhub мы предпочитаем создавать специальные версии наших сервисов, чтобы облегчить тестирование. Эти версии являются симуляциями, и в этом посте будут рассмотрены сценарии, в которых мы используем их для выполнения нагрузочных тестов и тестов на отказ с использованием имитированных ответов. Хотя в этом посте мы сосредоточимся на едином подходе, «сим-сервисы» (как мы их называем) - лишь часть нашей общей истории тестирования. Другие тестовые примеры могут не получить преимущества от моделирования, но в этом посте основное внимание будет уделено тому, где Grubhub считает моделирование полезным.

Как работают сим-сервисы?

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

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

{“versions”: {“SERVICE-NAME”:”SIM-*”}}

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

На рисунке 1a мы создали простой SpringBoot контроллер. Этот контроллер вернет объект Foo, содержащий поле String и целочисленное поле. Одна конечная точка не принимает никаких входных данных и возвращает версию Foo с постоянными данными. Другая конечная точка принимает параметр запроса String, который затем устанавливается как поле String возвращаемого объекта Foo.

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

Как и было обещано, это довольно простой код, но перед тем, как закончить нашу симуляцию, нам нужно добавить еще одну вещь. Нам нужно позвонить в библиотеку, которую мы называем SimulationHelper. Эта библиотека обеспечивает возможность увеличения времени отклика, тайм-аутов или серьезных отказов посредством изменения конфигурации. На рисунке 1b вы можете видеть, что мы изменили FooControllerSim для вызова SimulationHelper.simulate перед возвратом полезной нагрузки.

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

  1. 30 минут без вмешательств (контроль)
  2. 30 минут со 100% ошибками запросов в середине 10 минут (служба не работает)
  3. 30 минут, при этом 100% запросов выполняются очень долго, в среднем за десять минут (ухудшение качества обслуживания)

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

Контроль

Вот результат нашего 30-минутного контрольного прогона:

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

Сервис недоступен

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

simulation.failureChance: 100

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

Зависимость с медленным временем отклика

Лично я считаю этот сценарий наиболее интересным. Когда в зависимости устраняются серьезные ошибки, ее влияние на систему является двоичным и, как правило, легко предсказать, но медленное время отклика может быть гораздо сложнее. Для этого запуска мы изменим несколько других настроек; один, чтобы увеличить количество таймаутов до 100% от всех запросов, а другой, чтобы тайм-ауты занимали 30 секунд.

simulation.timeoutChance: 100

simulation.timeoutMs: 30000

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

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

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

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

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

Хотите узнать больше о возможностях нашей команды? Посетите страницу вакансий в Grubhub.