Как масштабируются вещи: введение в разработку реального масштабируемого программного обеспечения

Dec 03 2022
Цель этого поста — раскрыть некоторые основы проектирования программных систем, уделив особое внимание аспектам проектирования. На примере из реальной жизни он отвечает на вопрос, который многие инженеры задают себе: как мне вообще приступить к проектированию системы? Я обнаружил, что слишком много онлайн-ресурсов посвящены инструментам и подходу «запачкать руки».

Цель этого поста — раскрыть некоторые основы проектирования программных систем, уделив особое внимание аспектам проектирования .

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

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

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

По сути, «искусство программирования — это искусство организации сложности» (Фарли, Дэвид. Современная программная инженерия).

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

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

Пристегнитесь!

Позвольте мне дать вам немного контекста о Creator Now .

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

Это означает, что для каждого нового зарегистрированного пользователя нам нужно в значительной степени полагаться на API YouTube для извлечения информации о жизни их создателя.

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

Это наша ключевая задача:

Как мы можем масштабироваться, если нам приходится полагаться на сторонний API с ограничениями, которые мы не можем контролировать?

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

Представьте, что YouTube API — это большой офис, в котором хранятся данные обо всех каналах и обо всем, что связано с YouTube.

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

Поток довольно прост: кто-то входит в парадную дверь. Они предъявляют свои полномочия привратнику. Как только им разрешают войти, их обслуживает счастливый сотрудник YouTube:

  • Здравствуйте; какая информация вам нужна?
  • Не могли бы вы проверить последнюю информацию на канале CatsAreAmazing ?
  • Конечно, позвольте мне проверить наши файлы! <извлекает данные для канала и возвращает отчет на одну страницу>
  • Спасибо, добрый господин!
  • Я надеюсь, что офисы YouTube новее, чем это!

Но YouTube столкнулся с огромным количеством людей, которые просили что-то.

Поскольку все постоянно пытаются получить много информации, они решили ввести некоторые правила ( это не отражает реальных правил API YouTube ):

  1. Вы можете запросить информацию не более чем о 50 каналах/видео при каждом посещении офиса.
  2. Вы можете заходить в офис только 10 раз в минуту, 10 000 раз в день
«Неееет! Мне очень хотелось получить всю информацию обо всех каналах мира!»

Хорошо, так что мы должны быть умными об этом.

Давайте быстро посчитаем, какое максимальное количество информации о канале мы можем запросить в минуту (и в день).

Если мы можем заходить в офис 10 раз в минуту и ​​каждый раз запрашивать 50 каналов: 10 x 50 = 50 каналов в минуту. Делая аналогичные вычисления, мы понимаем, что можем запрашивать 10 000 x 50 = 50 000 каналов в день.

Если вы расширите это немного дальше, вы поймете, что мы можем использовать всю нашу квоту за 16 часов и 40 минут — то есть, это время потребуется, если мы запросим 50 каналов в минуту, чтобы взорвать нашу квоту в 50 000 каналов в день.

Итак, золотой вопрос здесь:

Как мы можем наиболее эффективно обновлять наше приложение с помощью последней информации о пользователях (даже если у нас более 50 тысяч пользователей)?

Во-первых, давайте установим для себя некоторые основные правила, которые позволят нам сэкономить несколько поездок в офис YouTube и убедиться, что мы следуем их правилам. Давайте разобьем ответственность за эту основную задачу на более мелкие задачи, которые выполняются разными отделами нашей компании:

  1. Давайте назовем наш отдел, который отвечает за взаимодействие с офисом YouTube, Scraper .
  2. Нам не нужно запрашивать новую информацию о канале, если мы уже получили информацию для него за последние 24 часа (мы согласны с 24-часовой задержкой для обновления каналов).
  3. Это означает, что нам нужно каким-то образом запомнить последнюю информацию о канале и время, когда мы ее получили — назовем это нашим кабинетом .
  4. Мы, вероятно, хотим, чтобы в офис могли ходить более одного человека — поскольку на YouTube установлено ограничение в 50 человек в минуту, мы можем оптимизировать наш процесс, наняв 50 человек для выполнения этой работы (назовем их бегунами ) .
  5. Нам нужен какой-то контроль над тем, сколько визитов мы совершаем в их офис в данную минуту и ​​в данный день (может быть, мы можем записать это куда-нибудь? Назовем это «Журналом бегунов» ) .
  6. Это также означает, что каждый раз, когда наши бегуны хотят выйти и получить новую информацию о канале, нам нужно перепроверить, не рискуют ли они потерять поездку из-за того, что мы экстраполируем нашу минутную/дневную квоту. Этот централизованный центр управления будет называться отделом бегунов .
Первоначальный проект нашего рабочего процесса

Итак, что такого особенного в этом потоке, помимо того факта, что он дает нам начальное решение (большинства) наших проблем?

Он обеспечивает четкое разделение задач :

  • Scraper заботится только о получении информации для канала YouTube (независимо от того, как )
  • Кабинет заботится только о хранении/ получении информации для канала
  • Отдел бегунов заботится только об организации пробегов в офис YouTube.
  • Журнал бегунов заботится только о хранении/получении информации о пробежках.
  • Назначенный бегун заботится только о самом беге.

Это также так называемая концепция модуляризации , связанная с Loose Coupling .

Давайте немного расширим тему слабой связи и того, как это делает нашу систему более масштабируемой. Чтобы понять это, нам нужно понять, почему важны интерфейсы (контракты) между модулями.

Возьмем, к примеру, взаимодействие Скребка и Кабинета .

Им обоим нужно договориться о том, как они будут разговаривать друг с другом: будут ли Кабмин получать запросы по телефону? СМС? — Также нужно согласовать, как Кабмин будет возвращать информацию в Парсер: это папка с файлами? Вложение по электронной почте?

Важно только то, что им нужно соглашение, интерфейс, контракт.

Почему?

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

Пока Кабинет надежно выполняет свой контракт, все в порядке.

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

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

Эта концепция применяется даже в нашей повседневной жизни: каждый раз, когда вы заказываете что-то в Интернете, вас обычно волнует, кто является перевозчиком? Знаете ли вы, как внутренне работает калькулятор, чтобы вернуть вам результаты? Тебе все равно?

Какая красота, а?

Что делать, если у нас больше запросов, чем мы можем обработать?

Блин, это все еще не решает одну из наших основных проблем! Мы создадим отставание, с которым отдел бегунов может не справиться вовремя!

Ха, о. Это не выглядит весело.

Кажется, нам нужна какая-то система, чтобы убедиться, что у нас есть отставание от этих операций и обрабатывать их наиболее эффективным способом, который мы можем придумать, не оставляя запросы на неопределенный срок!

Давайте придумаем некоторые правила масштабирования количества обращений в отдел бегунов .

Вот некоторые вещи ( правила ), которые мы можем придумать:

  1. Первым пришел, первым обслужен: мы обрабатываем запросы как очередь. Это гарантирует, что в какой-то момент запрос на получение информации для определенного канала будет обработан. Мы просто не можем гарантировать, когда.
  2. Приоритет каналов, для которых у нас нет данных (новые каналы в нашем приложении). Это может быть умной стратегией: канал, по которому у нас вообще нет данных, означает, что пользователь, вероятно, не может использовать приложение. т.е. наличие устаревших данных лучше, чем отсутствие данных вообще!
  3. Убедитесь, что у нас нет повторяющихся запросов в нашем бэклоге: если для определенного канала уже есть отложенная задача, мы можем просто отбросить дальнейшие запросы к ней.

Давайте рассмотрим пример, чтобы увидеть, имеет ли все это смысл.

Мы начали наш день с чистого листа — никаких ожидающих запросов в бэклоге.

Внезапно у нас наплыв 7000 запросов: приложению отчаянно нужна информация о 7к каналах! Учитывая изложенную выше логику, мы:

  1. Удалите все повторяющиеся запросы каналов, которые могут быть в этом пакете.
  2. Проверьте, для каких каналов у нас нет прошлой информации; переместите эти каналы в начало очереди
  3. Сохраните очередь в нашем журнале бегунов

Опять же, вот снова красота модульности: это внутреннее изменение в том, как работает отдел бегунов .

Scraper по-прежнему будет работать точно так же: он будет продолжать запрашивать информацию о каналах ( как избалованный ребенок!). Задачей отдела бегунов является хранение и обработка этой очереди внутри компании.

Помните: все остальные модули заботятся о контрактах и ​​интерфейсах!

Фу!

Хорошо, приятель, это было много — но, надеюсь, это имело для вас такой же смысл, как и для меня, когда я писал это.

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

Теперь вы также знаете, как мы можем, еще до того, как начать печатать код, подумать о разработке системы, которая может масштабироваться независимо.

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

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