Как масштабируются вещи: введение в разработку реального масштабируемого программного обеспечения
Цель этого поста — раскрыть некоторые основы проектирования программных систем, уделив особое внимание аспектам проектирования .
На примере из реальной жизни он отвечает на вопрос, который многие инженеры задают себе: как мне вообще приступить к проектированию системы?
Я обнаружил, что слишком много онлайн-ресурсов посвящены инструментам и подходу «запачкать руки».
Хотя овладение инструментами важно, мы чаще, чем хотелось бы признавать, пропускаем основную часть головоломки; высокоуровневое мышление о том, как все работает, еще до того, как мы попытаемся закодировать их на наших так называемых языках программирования.
По сути, «искусство программирования — это искусство организации сложности» (Фарли, Дэвид. Современная программная инженерия).
Организационная сложность должна возникнуть до того, как вы начнете набирать какой-либо код (или, может быть, даже думать о компьютерных системах вообще!).
Таким образом, мы погрузимся в реальный пример того, как мы можем использовать концепции, которые гарантируют, что конкретный экземпляр нашей системы сможет масштабироваться и определять, что представляют собой эти концепции.
Пристегнитесь!
Позвольте мне дать вам немного контекста о Creator Now .
В основе нашего бизнеса лежит геймификация информации о каналах авторов на YouTube, чтобы предоставить им наилучшие возможности для обучения и развития на пути творчества.
Это означает, что для каждого нового зарегистрированного пользователя нам нужно в значительной степени полагаться на API YouTube для извлечения информации о жизни их создателя.
Мы хотим, чтобы эта информация обновлялась как можно чаще. В конце концов, нет смысла поддерживать продукт, в котором основная часть информации, определяющая пользовательский опыт, отстает от реальности «реального мира».
Это наша ключевая задача:
Как мы можем масштабироваться, если нам приходится полагаться на сторонний API с ограничениями, которые мы не можем контролировать?
Во-первых, давайте разберемся, как работает API YouTube и в чем на самом деле заключается проблема, используя аналогию из реального мира.
Представьте, что YouTube API — это большой офис, в котором хранятся данные обо всех каналах и обо всем, что связано с YouTube.
Каждый раз, когда кто-то в Интернете хочет проверить информацию о конкретном канале, они идут туда. Короче говоря, им всем нужно послать кого-нибудь в это здание, чтобы запросить эту информацию.
Поток довольно прост: кто-то входит в парадную дверь. Они предъявляют свои полномочия привратнику. Как только им разрешают войти, их обслуживает счастливый сотрудник YouTube:
- Здравствуйте; какая информация вам нужна?
- Не могли бы вы проверить последнюю информацию на канале CatsAreAmazing ?
- Конечно, позвольте мне проверить наши файлы! <извлекает данные для канала и возвращает отчет на одну страницу>
- Спасибо, добрый господин!
Но YouTube столкнулся с огромным количеством людей, которые просили что-то.
Поскольку все постоянно пытаются получить много информации, они решили ввести некоторые правила ( это не отражает реальных правил API YouTube ):
- Вы можете запросить информацию не более чем о 50 каналах/видео при каждом посещении офиса.
- Вы можете заходить в офис только 10 раз в минуту, 10 000 раз в день

Хорошо, так что мы должны быть умными об этом.
Давайте быстро посчитаем, какое максимальное количество информации о канале мы можем запросить в минуту (и в день).
Если мы можем заходить в офис 10 раз в минуту и каждый раз запрашивать 50 каналов: 10 x 50 = 50 каналов в минуту. Делая аналогичные вычисления, мы понимаем, что можем запрашивать 10 000 x 50 = 50 000 каналов в день.
Если вы расширите это немного дальше, вы поймете, что мы можем использовать всю нашу квоту за 16 часов и 40 минут — то есть, это время потребуется, если мы запросим 50 каналов в минуту, чтобы взорвать нашу квоту в 50 000 каналов в день.
Итак, золотой вопрос здесь:
Как мы можем наиболее эффективно обновлять наше приложение с помощью последней информации о пользователях (даже если у нас более 50 тысяч пользователей)?
Во-первых, давайте установим для себя некоторые основные правила, которые позволят нам сэкономить несколько поездок в офис YouTube и убедиться, что мы следуем их правилам. Давайте разобьем ответственность за эту основную задачу на более мелкие задачи, которые выполняются разными отделами нашей компании:
- Давайте назовем наш отдел, который отвечает за взаимодействие с офисом YouTube, Scraper .
- Нам не нужно запрашивать новую информацию о канале, если мы уже получили информацию для него за последние 24 часа (мы согласны с 24-часовой задержкой для обновления каналов).
- Это означает, что нам нужно каким-то образом запомнить последнюю информацию о канале и время, когда мы ее получили — назовем это нашим кабинетом .
- Мы, вероятно, хотим, чтобы в офис могли ходить более одного человека — поскольку на YouTube установлено ограничение в 50 человек в минуту, мы можем оптимизировать наш процесс, наняв 50 человек для выполнения этой работы (назовем их бегунами ) .
- Нам нужен какой-то контроль над тем, сколько визитов мы совершаем в их офис в данную минуту и в данный день (может быть, мы можем записать это куда-нибудь? Назовем это «Журналом бегунов» ) .
- Это также означает, что каждый раз, когда наши бегуны хотят выйти и получить новую информацию о канале, нам нужно перепроверить, не рискуют ли они потерять поездку из-за того, что мы экстраполируем нашу минутную/дневную квоту. Этот централизованный центр управления будет называться отделом бегунов .

Итак, что такого особенного в этом потоке, помимо того факта, что он дает нам начальное решение (большинства) наших проблем?
Он обеспечивает четкое разделение задач :
- Scraper заботится только о получении информации для канала YouTube (независимо от того, как )
- Кабинет заботится только о хранении/ получении информации для канала
- Отдел бегунов заботится только об организации пробегов в офис YouTube.
- Журнал бегунов заботится только о хранении/получении информации о пробежках.
- Назначенный бегун заботится только о самом беге.
Это также так называемая концепция модуляризации , связанная с Loose Coupling .
Давайте немного расширим тему слабой связи и того, как это делает нашу систему более масштабируемой. Чтобы понять это, нам нужно понять, почему важны интерфейсы (контракты) между модулями.
Возьмем, к примеру, взаимодействие Скребка и Кабинета .
Им обоим нужно договориться о том, как они будут разговаривать друг с другом: будут ли Кабмин получать запросы по телефону? СМС? — Также нужно согласовать, как Кабмин будет возвращать информацию в Парсер: это папка с файлами? Вложение по электронной почте?
Важно только то, что им нужно соглашение, интерфейс, контракт.
Почему?
Как только эта линия установлена, не имеет значения, насколько сложна внутренняя структура Кабинета : для всех забот Скребка это может быть операция с одним человеком или пятиэтажная строительная армия.
Пока Кабинет надежно выполняет свой контракт, все в порядке.
Это позволяет нам и нашей команде разработчиков независимо работать над каждым из этих модулей, принимая решения, которые лучше всего подходят для его внутренней производительности.
Мы можем распараллелить работу каждого из них, потому что, в конце концов, важно только то, что они умеют разговаривать друг с другом.
Эта концепция применяется даже в нашей повседневной жизни: каждый раз, когда вы заказываете что-то в Интернете, вас обычно волнует, кто является перевозчиком? Знаете ли вы, как внутренне работает калькулятор, чтобы вернуть вам результаты? Тебе все равно?
Какая красота, а?
Что делать, если у нас больше запросов, чем мы можем обработать?
Блин, это все еще не решает одну из наших основных проблем! Мы создадим отставание, с которым отдел бегунов может не справиться вовремя!

Кажется, нам нужна какая-то система, чтобы убедиться, что у нас есть отставание от этих операций и обрабатывать их наиболее эффективным способом, который мы можем придумать, не оставляя запросы на неопределенный срок!
Давайте придумаем некоторые правила масштабирования количества обращений в отдел бегунов .
Вот некоторые вещи ( правила ), которые мы можем придумать:
- Первым пришел, первым обслужен: мы обрабатываем запросы как очередь. Это гарантирует, что в какой-то момент запрос на получение информации для определенного канала будет обработан. Мы просто не можем гарантировать, когда.
- Приоритет каналов, для которых у нас нет данных (новые каналы в нашем приложении). Это может быть умной стратегией: канал, по которому у нас вообще нет данных, означает, что пользователь, вероятно, не может использовать приложение. т.е. наличие устаревших данных лучше, чем отсутствие данных вообще!
- Убедитесь, что у нас нет повторяющихся запросов в нашем бэклоге: если для определенного канала уже есть отложенная задача, мы можем просто отбросить дальнейшие запросы к ней.
Давайте рассмотрим пример, чтобы увидеть, имеет ли все это смысл.
Мы начали наш день с чистого листа — никаких ожидающих запросов в бэклоге.
Внезапно у нас наплыв 7000 запросов: приложению отчаянно нужна информация о 7к каналах! Учитывая изложенную выше логику, мы:
- Удалите все повторяющиеся запросы каналов, которые могут быть в этом пакете.
- Проверьте, для каких каналов у нас нет прошлой информации; переместите эти каналы в начало очереди
- Сохраните очередь в нашем журнале бегунов
Опять же, вот снова красота модульности: это внутреннее изменение в том, как работает отдел бегунов .
Scraper по-прежнему будет работать точно так же: он будет продолжать запрашивать информацию о каналах ( как избалованный ребенок!). Задачей отдела бегунов является хранение и обработка этой очереди внутри компании.
Помните: все остальные модули заботятся о контрактах и интерфейсах!
Фу!
Хорошо, приятель, это было много — но, надеюсь, это имело для вас такой же смысл, как и для меня, когда я писал это.
Теперь у вас должно быть очень хорошее понимание того, почему важны модуляризация, слабая связанность и абстракции.
Теперь вы также знаете, как мы можем, еще до того, как начать печатать код, подумать о разработке системы, которая может масштабироваться независимо.
В следующий раз, когда вы начнете проектировать систему, постарайтесь ответить на фундаментальные вопросы о том, как вы можете спроектировать такие независимые, слабо связанные модули еще до того, как начнете печатать код.
В противном случае вы можете в конечном итоге начать разрабатывать устаревший код завтрашнего дня, который никто не понимает и/или не хочет использовать!