с Самуэлем Рагхунатхом

В Grubhub мы хотим, чтобы вы получали еду. Нам все равно, упадет ли линия электропередачи в центр обработки данных AWS, рабочий-строитель случайно отключит всю водопроводную воду в офисе или все в США только что закончили смотреть документальный фильм Бейонсе о Коачелле и теперь хотят заказать лимонад - через все это , мы хотим, чтобы вы приносили еду.

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

Краткая история нашей системы надзора

Раньше, когда Seamless и Grubhub были двумя отдельными компаниями, мы запускали все из одного центра обработки данных (ЦОД). Когда наши компании объединились, нам нужно было спланировать будущий рост, поэтому мы перешли в облако и создали структуру обслуживания.

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

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

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

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

Итак, как мы контролируем этот конвейер выполнения в течение определенного периода времени?

Как мы определяем «успех»

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

Но для того, чтобы сделать то, что нужно Grubhub, супервизор должен был избегать всего, что могло бы сделать его слишком хрупким:

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

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

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

Слушай! Появляется дикий наблюдатель!

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

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

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

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

Вот основная логика обработки записей временного сегмента:

Контроль за центрами обработки данных

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

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

Лучшие и худшие практики супервизии

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

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

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

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

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

Как это может вам помочь

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

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