Transcription
Итак, разработка программного обеспечения меняется очень быстро, особенно в последние несколько месяцев, и я думаю, мы находимся в том моменте, когда нам нужно замедлиться и понять, что происходит. У нас есть все эти машины, которые пишут код для нас. Мы разговариваем с ними, и они пишут код, и я сам пользуюсь ими каждый день. Я думаю, это замечательно. Есть определенный прирост производительности, но иногда мы также видим своего рода сообщения и сбои, эти ошибки повсюду на огромных платформах, форумы происходят все чаще и чаще. Поэтому я хочу снять видео, чтобы привнести немного добра и знаний в архитектуру программного обеспечения. Я думаю, сейчас важнее, чем когда-либо, понимать, что мы строим, а не как мы это строим. У меня много видео на моем YouTube-канале, и я всегда, еще до появления ИИ, показывал вам теорию, плюсы и минусы каждого принятого мной решения. И это действительно глубоко в разработке программного обеспечения, и я хотел дать вам введение для тех, кто, возможно, не так хорошо разбирается в технологиях, в архитектуре программного обеспечения. Итак, в этом видео мы построим, мы сначала попытаемся определить, что такое архитектура программного обеспечения, а затем мы построим клон Google Drive на уровне идеи, шаг за шагом. Мы начнем с очень малого, только с сервера, а затем перейдем к архитектуре микросервисов. Так что каждое решение по пути я постараюсь изложить и показать вам причину, стоящую за ним. Что касается проектирования программного обеспечения, я всегда ссылаюсь на Мартина Фаулера, и я хочу показать вам фразу из этой статьи, и вы можете найти ее в описании, как всегда, но я хочу сначала попытаться определить архитектуру и почему она полезна, а затем то, что мы будем делать в остальной части видео, это то, что мы сами спроектируем клон Google Drive. Так что мы сможем загружать файлы, скачивать файлы, и мы начнем с очень малого, всего с одного сервера, данные на сервере, а затем мы постепенно перейдем к монолитной архитектуре. Мы поговорим об ограничении скорости, кэшировании, горизонтальном масштабировании, вертикальном масштабировании, о многом другом. И цель состоит в том, чтобы показать вам решения, постепенную эволюцию и множество способов, которыми вы можете думать о проектировании программного обеспечения. Дело не только в коде, дело в мышлении системами. А затем мы также можем думать о коде в системах. Но да, без дальнейших церемоний, позвольте мне просто, если вы хотите получить статью, я оставлю ее в описании. Но позвольте мне просто немного выделить это. Так что хорошая архитектура важна, иначе в будущем станет медленнее и дороже добавлять новые возможности. Это действительно тот опыт, который большинство из нас имели при создании производственного программного обеспечения, верно? А именно, если мы не построим хорошую архитектуру, бизнес пострадает. И именно поэтому архитектура важна. Она заключается в поддержке бизнеса, чтобы доходы могли продолжаться, бизнес мог работать бесперебойно, и чтобы мы не получали такого рода сообщения нашим клиентам. Конечно, я не виню этих парней. Это компании, которые знают, что они делают. Итак, для начала давайте разберемся, как проектировать программное обеспечение. По сути, это будет своего рода интенсивный курс, и мы, как я уже сказал, будем проводить постепенную эволюцию. Конечно, если нет лучшего, чем предыдущий. Я, конечно, не хочу излишне усложнять и хочу научить вас целенаправленному процессу принятия решений. Архитектура может быть лучшей для вас на текущем этапе компании, в которой вы работаете, а может и нет. Так что именно здесь вступает в игру решение, и вам нужно понимать плюсы и минусы. И об этом написаны целые книги, как эта. Это одна из моих любимых. Вы, вероятно, знаете о ней. Так что, конечно, это тяжелая тема для обсуждения. Я в основном останусь на уровне идеи. Но давайте начнем. Давайте начнем очень просто. Итак, у меня здесь будет сервер. Вот так мог начаться ваша первая программа. У вас есть просто сервер, а затем у вас есть пользователи. Итак, позвольте мне просто, и что происходит, это то, что ваши пользователи будут отправлять запросы на ваш сервер. Так что очень просто. Это будет наш Google Drive. Очень просто. Так что все данные находятся внутри сервера. Так что это все один экземпляр, и это работает, это работает хорошо. Но я начну задавать некоторые вопросы, а именно, что произойдет, если сервер, например, выйдет из строя, и что произойдет, когда вы захотите масштабироваться. Итак, давайте начнем с первого, потому что, если вы видите, наши данные связаны с сервером. Так что данные на самом деле находятся внутри сервера. Так что мы могли бы представить это на Go, это было бы что-то вроде этого. Это очень простая структура Golang, где у нас есть наши данные внутри сервера. Так что файлы — это карта от идентификатора к расположению файла, и, конечно, если сервер выйдет из строя, данные также будут потеряны. По сути, это очень простое решение, и вы, конечно, уже знаете, как его решить, вероятно, просто добавить базу данных. Но идея здесь не просто в добавлении базы данных, а в том, что вы будете отделять сервер, который будет без состояния, от базы данных. Вот. Так что, по сути, теперь у нас есть наш сервер и наша база данных, и всякий раз, когда мы делаем запросы, мы будем получать данные из базы данных, чтобы наши пользователи не теряли данные всякий раз, когда они делают запрос, а наш сервер выходит из строя. Так что теперь у нас есть постоянство данных, и наш первый вопрос, наша первая проблема фактически решена. Так что очень просто, мы доберемся до этого, это станет интереснее, я обещаю вам, но также важно понять, почему вы вообще хотите добавить базу данных. Так что также важно понять эти мелкие детали и что происходит, когда вы хотите масштабироваться. Это наш второй вопрос, который заключается в том, что если вы фактически масштабируете этот экземпляр здесь с данными, что произойдет, это то, что у вас будет два сервера, например. Так что мы будем горизонтально масштабироваться. Мы доберемся до этого. Но если вы хотите иметь несколько экземпляров, по сути, у нас будут дублированные данные. И, конечно, это нежелательно. Это будет работать, потому что данные все еще будут там. И запрос поступает на второй сервер, данные все еще будут там, верно? Так что, если вы делаете запрос на этот, но проблема возникает, когда у вас есть синхронизация данных. Так что, если пользователь пишет на эту машину, данные будут записаны здесь, но другая машина не будет знать. Так что это еще одна проблема, которую нам нужно решить, и она решается наличием отдельного экземпляра прямо здесь. Это означает, что мы можем фактически масштабировать наш сервер, и пользователь, какой бы экземпляр он ни использовал, это не имеет значения, потому что у нас есть одна единая реляционная база данных. Так что база данных, которая будет содержать таблицы пользователей, таблицы файлов. Так что все будет храниться здесь, и чему мы можем научиться из этого, так это то, что это классическое разделение обязанностей в разработке программного обеспечения, где сервер отвечает за обслуживание пользователя, а база данных отвечает за хранение данных. Это означает, что если сервер выходит из строя или удаляется, база данных не беспокоится об этом, потому что она не знает о сервере. Сервер знает о базе данных. Но если сервер или база данных выходят из строя, конечно, это больше, это отличается, потому что если нет базы данных, нет способа обслужить пользователя. Но в некоторых случаях запросы не должны идти в базу данных. Так что здесь есть некоторое разделение. Это своего рода ключевой вывод из этого первого урока. Итак, вот концепция: данные, связанные с машиной, означают отсутствие масштабирования и отсутствие отказоустойчивости. Давайте пойдем немного дальше. Итак, вот наша упрощенная архитектура. Теперь давайте представим следующее. Наше приложение работает хорошо. У нас много пользователей. Оно становится популярным в магазине приложений, но мы получаем жалобы на то, что наше приложение медленно реагирует. Так что, по сути, сервер не обрабатывает все эти запросы должным образом. Конечно, давайте фактически масштабируем его. Так что мы масштабируемся до второго экземпляра. Теперь, что происходит здесь, и мы уже видели это раньше, что мы можем независимо масштабировать наш сервер, что хорошо, но есть множество проблем, когда мы добавляем более одного сервера. Мы начинаем входить в распределенную архитектуру как таковую, потому что у нас есть несколько серверов, и в этом случае им не нужно общаться друг с другом, но нам нужен какой-то способ посередине, чтобы знать, кто будет сервером. Так что представьте, что большинство пользователей фактически обращаются к этому, а у этого сервера много свободных ресурсов для использования. Пользователи получат ухудшенный опыт, потому что этот сервер не будет использоваться. И нам также нужно иметь часть посередине, которая будет решать, какой сервер будет обслуживать пользователя. Так что это обычно называется балансировщиком нагрузки. У нас здесь есть? Да, есть. Отлично. Так что это обычно называется балансировщиком нагрузки, и, по сути, давайте поместим нашу архитектуру сюда. Позвольте мне фактически переместить наши серверы и дублировать их. Теперь у нас есть еще одна часть посередине, которая фактически отвечает за простое перенаправление на исправную машину. Так что всякий раз, когда вы слышите балансировщик нагрузки, просто эта часть посередине, которая будет перенаправлять на исправную машину, или в этом случае она может использовать случайный выбор или просто перенаправлять по алгоритму циклического перебора. Так что это алгоритм, который будет на балансировщике нагрузки, что если пользователь А приходит, он будет перенаправлен на сервер один. Если приходит другой пользователь, мы перенаправим на другой, так что своего рода последовательно чередуя между серверами, чтобы они оба получали одинаковый трафик. Но действительно ли трафик одинаковый? Если пользователь А фактически загружает файл, скажем, он собирается сделать, давайте начнем проектировать наш API, если он начинает загружать файл, в то время как пользователь Б просто получает файл. Он просто потребляет файл, верно? Он получает файлы 1, 2, 3. Является ли это на самом деле одинаковым трафиком? Является ли это на самом деле одинаковым объемом запросов? Конечно, нет. Это будет более дорогостоящим с точки зрения хранения. Это может быть более дорогостоящим с точки зрения ЦП. Так что здесь разные переживания. Конечно, циклический перебор уже достаточно хорош и решает нашу проблему. Но наш балансировщик нагрузки также может быть умнее и использовать проверки работоспособности, чтобы понять, как работают наши серверы. И если этот сервер работает намного лучше, чем другой, потому что он получает файл, который загружается, то балансировщик нагрузки, эта часть здесь, точно знает, куда отправить. Так что просто чтобы показать вам, что существует множество балансировщиков нагрузки, использующих разные методы. И интересная вещь о балансировщиках нагрузки заключается в том, что они могут делать гораздо больше. Например, мы еще не переходим к архитектуре микросервисов. Но если у нас есть сервер, и это своего рода решает проблему, которую я только что сказал, а именно, у нас будет сервис под названием файлы, который будет просто обрабатывать файлы. Позвольте мне фактически поместить это немного ниже, чтобы у нас было больше места. Если мы знаем, что этот пользователь собирается загрузить файл, и это замедлит наши серверы, мы можем фактически просто перенаправить его на определенный или специализированный сервис, который просто знает, как выполнять загрузку файлов, в то время как другие обрабатывают трафик нормально и исправны. Так что балансировщики нагрузки могут фактически выполнять маршрутизацию по именам путей. Так что, если ваш API начинается с post и предназначен для загрузки, вы можете фактически просто перенаправить на этот сервер. Чтобы не тратить больше времени на балансировщики нагрузки, позвольте мне просто показать вам, что есть еще три способа сделать это. Есть взвешенный циклический перебор. Есть сеансы привязки. Я думаю, это довольно интересно. Я фактически работал над чем-то подобным совсем недавно. И, по сути, причина, по которой вы хотите иметь сеансы привязки, и что происходит, это то, что если пользователь А приходит к балансировщику нагрузки, он будет перенаправлен на сервер, в следующий раз балансировщик нагрузки будет стараться изо всех сил перенаправить его на тот же сервер. Почему мы хотим это делать в основном потому, что если на сервере есть какое-то состояние для этого пользователя, он выполняет какую-то работу, может быть интересно перенаправить его на тот же сервер. И снова у нас есть соединение. Так что, по сути, типа, если на этом сервере меньше соединений, давайте фактически использовать его, потому что он более доступен. Чтобы проиллюстрировать, о чем я говорю, я думаю, имеет смысл показать вам некоторый код и веб-сервер, фактически работающий в реальном мире, чтобы это имело смысл. Я думаю, это вам станет понятно. Итак, у меня здесь три сервиса Golang. Так что просто три сервера, которые точно такие же. Так что у них точно такой же код. Это довольно просто. Просто простой HTTP-сервер. Он имеет некоторую обработку сеансов, которую я покажу вам для файлов cookie и сеансов привязки. Просто некоторая HTML-страница, которую я сгенерировал, а затем это просто очень простой сервер, который получает файлы cookie и создает сеансы. Интересная часть здесь, в Nginx, потому что здесь у нас есть два блока upstream. Это будет две группы серверов. Это, по сути, означает, что наш прокси-сервер Nginx сможет балансировать нагрузку в случае, если URL содержит RR. Это, по сути, означает циклический перебор или сеанс привязки. Так что просто чтобы показать, как это будет работать на практике. Так что позвольте мне фактически выполнить docker compose up. Я думаю, сервер не работал. И вот оно. Так что, если я просто открою браузер по адресу /rr, и если я обновлю страницу, вы увидите, что каждый раз я буду получать один, два и три. Так что вы можете видеть, что циклический перебор делает свою работу. Он обращается к серверу один, сервер два и сервер три. Вот что он делает. Однако, если я применю сеансовый файл cookie, я только что включил здесь расположение файла. Так что вы можете видеть, что это / sticky. Позвольте мне фактически обновить. Теперь мы получаем сервер три. Я обновлю еще раз, когда мы всегда получаем сервер три. Так что вы можете видеть, как это работает. Вы можете видеть здесь количество обращений. Это идентификация сеансового файла cookie. Так что это может быть ваш токен аутентификации, например. И каждый раз балансировщик нагрузки перенаправляет меня на тот же сервер. Так что это балансировка нагрузки с привязкой к сеансу. Теперь, чтобы снова показать вам, я только что очистил файлы cookie. Позвольте мне фактически обновить и посмотрим, что произойдет. Так что я получил сервер один. Я обновлю еще раз. Я получил сервер три. И, как вы можете видеть, он почему-то очень любит сервер три. Это именно то, как работает сеанс привязки. Итак, теперь, когда вы знаете, как координировать трафик между нашими серверами, мы можем фактически начать масштабироваться как сумасшедшие, потому что мы знаем, что у нас есть новый компонент, новый строительный блок, это парень, балансировщик нагрузки, и он может воскрешать трафик. Так что у нас есть сила балансировщика нагрузки, разблокированная, и это означает, что у нас может быть столько серверов, сколько мы хотим, и столько, сколько мы можем за них заплатить, но это означает, что я хочу провести различие между двумя типами масштабирования, которые мы можем сделать. Мы можем делать горизонтальное масштабирование, что мы и делали. Так что добавление большего количества машин в ваш кластер, в ваш бизнес, и это имеет смысл, потому что если у вас больше машин, у вас больше ресурсов, больше мощности, потому что каждая машина независима от другой. Так что мы можем выполнять некоторые вычисления здесь. В то время как этот свободен для обслуживания другого пользователя или даже другого кластера пользователей. Есть другой способ, которым вы можете это сделать, и чаще всего вы будете использовать оба. Так что нет одного способа сделать это. Это немного похоже на бросание денег в проблему. Вы можете услышать это выражение, которое заключается в увеличении сервера. Так что у нас есть один сервер, но это мощная машина. У нее больше ОЗУ, больше ЦП, больше хранилища. Так что вы увеличиваете вычислительную мощность вашей машины. Так что имеет смысл иметь оба решения более мощных машин, но и горизонтально их масштабировать. И это обычно называется вертикальным масштабированием. А это горизонтальное. Итак, это просто это описание, которое я хотел убедиться, что вы понимаете. И это, по сути, позволяет нам сделать балансировщик нагрузки немного умнее, потому что чаще всего вы знаете, что наличие большего количества машин означает больше потраченных денег. Более мощные машины означают больше потраченных денег. Так что мы хотим иметь как можно меньше машин. Так что минимальное количество идеально. И автоскейлеры обычно позволяют вам иметь или балансировщики нагрузки позволяют вам автоматически масштабироваться. Так что у вас может быть минимум два, например. Но если у вас много трафика, вы можете увеличить до десяти. Вот как это обычно делается. Так что с автоматическим масштабированием Kubernetes делает это. Я знаю, что Google Cloud тоже делает это либо с серверами, либо даже с бессерверными решениями, что, по сути, означает, что вам не нужно беспокоиться о сервере, о его подготовке. Поставщик делает это за вас. Просто поместите образ Docker или контейнер Docker, и сервер позаботится об этом. Но это уже другая история. По сути, это означает, что у вас может быть балансировщик нагрузки, который фактически знает, сколько трафика слишком много, и тогда он увеличит или подготовит экземпляры для вас. Так что довольно интересно. Теперь, скажем, наше приложение прибыльно. Оно работает. Наши клиенты довольны. Мы очень хорошо масштабируемся. Мы зарабатываем много денег, но мы начинаем нанимать больше людей. Так что у нас больше членов команды, больше инженеров, и у нас также есть некоторые конечные точки, которые медленно работают, и мы получаем об этом отчеты. Так что же здесь происходит? И мы как бы открываем двери в целый новый мир, опасный мир, это микросервисы. И мы начали этот путь с нашим файловым сервисом, который, по сути, заключается в том, что некоторые клиенты упомянули, что загрузки стали медленнее. Так что всякий раз, когда мы загружали файлы, они становились медленными, потому что большинство серверов обрабатывали все. Теперь, что мы делаем с микросервисами, это то, что мы решим эту боль, имея несколько специализированных сервисов, которые знают, как делать одну вещь, и одну вещь очень хорошо. Так что у нас будет, например, если мы начнем думать о нашем бизнесе, а микросервисы — это целый новый мир тем, и я хочу упомянуть, что у меня есть 20-часовой курс только по микросервисам, мы используем Golang, конечно, это первая ссылка в описании, если вы заинтересованы, первые три часа полностью бесплатны, так что вы можете посмотреть их, есть много теории, и очень важно понимать, что микросервисы — это сложная тема, чаще всего вы можете захотеть использовать микросервисы, когда вы работаете над большим бизнесом, и когда я упомянул, что мы нанимаем в нашем гипотетическом случае, это потому, что очень интересно иметь, например, команду, которая работает только над файлами, другую команду, которая работает только над уведомлениями, и поскольку мы говорим об услугах, давайте фактически попробуем подумать о нашей предметной области, нашем клоне Google Drive, и давайте попробуем создать сервисы. Так что у нас есть наш файловый сервис, который, по сути, обрабатывает загрузки, получает уведомления, которые обрабатывают, например, push-уведомления для веб- и настольных компьютеров, и у нас может быть, например, сервис аутентификации, который просто обрабатывает аутентификацию, и, например, реальное время. Так что реальное время может быть чем угодно, что вам может понадобиться в реальном времени, например, вы загружаете файл на свой компьютер, который синхронизируется с облаком, а облако также синхронизируется с вашими другими личными компьютерами, так что просто некоторое разделение здесь, с которым мы можем начать работать. Так что это четыре предметные области, и четыре команды фактически могут работать над этими микросервисами, и это своего рода поднимает вопрос: как балансировщик нагрузки, и я думаю, вы тоже должны знать ответ к настоящему времени, потому что я касался этого ранее, как балансировщик нагрузки, например, этот пользователь будет аутентифицироваться, так что давайте фактически сделаем, например, POST /api/login, так что очевидно, что мы обратимся к сервису аутентификации, но как балансировщик нагрузки знает, как это обрабатывать? Если вы думаете о потоке, пользователь вызывает нашу конечную точку. Так что наш сервис, и балансировщик нагрузки будет перенаправлять на сервис аутентификации, потому что он знает, что это для сервиса аутентификации. Некоторые балансировщики нагрузки могут делать это, некоторые не могут. Так что также важно понимать, что, возможно, балансировщик нагрузки — это не тот, кто справится с этой задачей. Не только это, если мы добавим аутентификацию, это означает, что мы добавляем аутентификацию. Где находится аутентификация? Если мы сделаем этот запрос, можем ли мы фактически напрямую вызвать сервис уведомлений? Можем ли мы фактически обойти балансировщик нагрузки с аутентификацией к файловому сервису? Где находится аутентификация? Так что я только что реструктурировал все, потому что, конечно, балансировщик нагрузки не сможет справиться с этим, потому что давайте добавим новую часть сюда, которая будет шлюзом. Так что это будет называться API Gateway. По сути, API Gateway будет получать запрос. Он будет анализировать его и перенаправлять в нужный сервис. Не только это, он также может агрегировать ответы. Например, чтобы получить вашу аутентификацию, нам нужно обратиться к сервису аутентификации, но нам также может понадобиться обратиться, например, к другому сервису, такому как сервис профиля, где мы фактически получаем профиль пользователя. Так что просто гипотетический случай, и, по сути, то, что делает этот шлюз, это то, что он будет координировать это распределение, и он вернет вам ответ пользователю, агрегированный со всеми этими запросами. Не только это, это решит вторую проблему, которую я упомянул, а именно, мы не хотим, чтобы пользователи напрямую общались с сервисами. Так что мы сделаем всю эту частную сеть. Так что это то, что вы обычно видите как VPC, и, по сути, то, что у нас есть здесь, это то, что шлюз будет единственной точкой входа для связи с нашей системой. Все эти серверы не будут открывать свои порты наружу. Так что их IP-адрес будет просто внутри этой виртуальной сети, и единственный способ для клиентов общаться с нашим бизнесом — это через шлюз. Так что шлюз будет обрабатывать аутентификацию, маршрутизацию и все такое. Я оставил здесь балансировщик нагрузки, и я удалю его на следующих диаграммах, потому что мы также можем захотеть масштабировать шлюз. Так что, например, если шлюз теперь становится единой точкой отказа, также может быть интересно иметь балансировщик нагрузки перед шлюзом, а также внутри нашего кластера. Так что мы перешли к тому, чтобы знать, каким сервисам нужно больше ресурсов, и тогда нам также нужно масштабировать наш шлюз, потому что наш шлюз может фактически масштабироваться вертикально, верно, или в этом случае горизонтально, но я пока оставлю это, но на следующих я всегда буду опускать балансировщик нагрузки, но вы можете представить, что здесь может быть балансировщик. Итак, теперь, когда у нас есть наша распределенная архитектура, и нам нужна аутентификация, и имеет смысл, что, конечно, у нас всегда была аутентификация, но давайте фактически углубимся в нее. Давайте фактически разберемся, как мы можем фактически сделать это в этой распределенной архитектуре. Это почти то же самое, если это не если это просто монолитный сервис, просто один экземпляр сервера, как у нас был, по сути, здесь, если мы, например, думаем о том, как будет работать конечная точка файлов. Так что мы будем загружать некоторые файлы. Пользователь отправит эту полезную нагрузку, верно? Что произойдет, это то, что шлюз проверит, имеет ли пользователь токен JWT. Так что своего рода токен. Я говорю об этом на канале и во всех моих курсах, но, по сути, просто токен, подпись, означающая, что вы аутентифицированы. Он содержит срок действия. Так что шлюз проверит, хотите ли вы перейти к сервису аутентификации или нет. Но это потребует одного сетевого вызова. Что вы также можете захотеть сделать, это просто добавить здесь уровень безопасности на шлюзе или даже перед шлюзом для проверки этого. Так что я не буду вдаваться в подробности, если это сервис, если это шлюз. Каждый сервис отличается. Каждая архитектура отличается. Вы можете фактически иметь это или просто шлюз, обрабатывающий аутентификацию или, по крайней мере, базовую часть аутентификации, просто проверяя, действителен ли токен. Так что пользователь теперь отправляет этот заголовок авторизации с токеном. Вот как это обычно делается с файлами cookie. Так что в нашей архитектуре нет сетевых вызовов аутентификации, потому что мы просто проверим, имеет ли этот запрос действительный токен. Если да, мы просто поверим, что он не истек. Мы проверим это, а затем перенаправим к файлам. Так что прямо на границе мы можем фактически проверить и выдать код состояния 401, если это неавторизовано. Но у нас также есть сервис аутентификации. Так что же он делает? Как мы фактически получаем токен? Здесь мы переходим к генерации токена, и где-то на каждом веб-сайте будет страница входа. И вот как это работает. Вы отправляете свои учетные данные. Так что это может быть электронная почта и пароль или просто электронная почта с токеном единого входа. Но, по сути, пользователь запросит токен с действительными учетными данными. Так что пользователь может, возможно, уже существовать в вашей архитектуре, в вашей базе данных, и то, что произойдет, это то, что шлюз просто перенаправит к аутентификации, а аутентификация будет использовать своего рода закрытый ключ для подписи вашего токена, убедившись, что он действителен, а затем он фактически перенаправит. На уровне аутентификации на уровне шлюза, извините, вы можете фактически проверить, существует ли пользователь, сначала вызвав, например, наш сервис. В этом случае у нас есть этот миниатюрный, я создал разные сервисы здесь, так что не беспокойтесь об этом, но, например, сервис пользователей может фактически просто отвечать за создание пользователей или проверку пользователей, а затем только тогда, если пользователь существует, мы фактически вызовем уровень аутентификации. Так что они могут быть вместе. Они могут не быть вместе. Это зависит от вас. Но просто чтобы показать вам, что своего рода шлюз будет этим парнем посередине этих решений. Теперь просто еще одна вещь об аутентификации в целом, это аутентификация. Так что это, по сути, означает, есть ли у вас доступ к приложению, а затем есть еще одна вещь, которая является авторизацией, которая, по сути, означает, есть ли у вас разрешения на загрузку файлов. Так что авторизация отличается от аутентификации и полностью связана. Вы можете даже поместить ее в тот же сервис, но вы можете запускать авторизацию во всех сервисах. Они могут иметь свой собственный уровень авторизации, по сути, как разрешения. Итак, теперь, когда у нас есть наша архитектура микросервисов, давайте фактически разберемся, как мы можем загружать файлы, потому что здесь есть новый компонент в головоломке, это объектное хранилище. Так что до сих пор мы фактически игнорировали, как загружать файлы, что является нашим бизнесом. Но я также хотел поместить все части на стол, чтобы вы поняли, как это работает, потому что это действительно начинает становиться сложным, когда несколько сервисов начинают общаться друг с другом, что мы и начнем делать сейчас. Так что, просто вернувшись немного, как работает загрузка и обслуживание файлов, и это своего рода повторяющаяся проблема, которую вы можете встретить в своей карьере, и вам, возможно, придется реализовать что-то подобное, я делал это много раз. Это интересная проблема, и всегда есть этот шаблон: вы собираетесь загрузить файл, верно, мы уже говорили об этом, и дело в том, что очень важно, этот файл, вы, мы не будем фактически выполнять прямую загрузку на ваш сервер, так что давайте представим здесь наш файловый сервис, очень простой, и нашу базу данных, это здесь не произойдет, вы не будете загружать файл напрямую в свою базу данных, в свою реляционную базу данных, потому что вы можете загружать 20-гигабайтный фильм, 200-мегабайтный фильм, а реляционные базы данных не предназначены для этого. Не только это, вы не должны передавать все эти данные на свой сервер, потому что он, возможно, не позволит этого, и мы не должны, потому что это позволит вам предотвратить некоторые злонамеренные атаки со стороны пользователей. Так что вы не должны загружать напрямую на свой сервер. И как вы на самом деле загружаете файлы? Так что обычно делается то, что вы собираетесь, и да, у вас также может быть, например, тайм-аут, потому что это будет много данных для сервера, и обычно происходит то, что у вас будет объектное хранилище. Представьте, например, S3-ведро, Google Cloud Bucket. Так что объектное хранилище, которое просто предназначено для хранения файлов, статических файлов, которые редко или вообще не меняются. Способ работы этого заключается в том, что вы обычно отправляете запрос на свой API, так что на ваш шлюз. Так что с метаданными, так что просто имя файла, размер файла, а также как блоб. Так что, например, ваш фактический файл, изображение. Но что происходит, это то, что вы просто скажете: я хочу воспроизвести этот файл. Шлюз обратится к файловому сервису. Он обратится к реляционной базе данных. Он скажет: эй, пользователь хочет сохранить файл. Так что давайте сгенерируем некоторые метаданные, которые будут выглядеть примерно так. Так что, например, это будет таблица файлов. Так что мы сгенерируем новый идентификатор для этого файла. У нас будет имя файла. Мы начнем с размера. Вы также можете захотеть сохранить, например, тип. Это в случае PNG что-то вроде этого, но не сам файл. Так что это важная часть, потому что то, что произойдет, это то, что хорошо, у нас есть метаданные файла, сохраненные, потому что мы теперь можем проверить, существует ли файл. Что мы должны сделать, это обратиться к объектному хранилищу, например, Google Cloud или чему-то подобному, и сказать: эй, дайте мне ссылку, по которой мои пользователи могут напрямую загружать в ведро. Так что, по сути, это вернет вам ссылку, которая будет аутентифицирована или нет. Так что важно, чтобы вы разрешили очень короткое окно загрузки. Так что у этого будет срок действия, и он может быть аутентифицирован для загрузки или нет. Я думаю, нормально оставить его неаутентифицированным, но пока у него короткое окно загрузки, это нормально, и он также будет ограничивать некоторые размеры. Так что мы можем сказать, например, только 50 мегабайт загрузок через эту ссылку, и с этим решением пользователь теперь будет, так что мы перенаправим с метаданными, мы скажем: эй, это 200, это нормально, и мы также перенаправим эту ссылку прямо сюда, и пользователь или фронтенд автоматически начнет загружать файл или передавать файл в это ведро. Так что у вас будет файл в ведре, напрямую загруженный туда. И если вы думаете об этом решении, оно на самом деле довольно интересное, потому что мы полностью обходим нашу архитектуру. Так что мы фактически идем напрямую в ведро, и наши серверы полностью не затронуты. Так что, если я вернусь к началу видео, мы говорили, что наш файловый сервис был перегружен запросами на загрузку, и это делало наши серверы очень медленными. Мы только что решили это с помощью объектного хранилища. Так что, вероятно, мне следовало представить вам эту часть раньше. Однако я также хочу показать вам, что это на самом деле архитектура с несколькими сервисами. Позвольте мне показать вам проблему. Представьте теперь, что мы фактически загрузили файл. Так что файл загружен. Но когда пользователь загружает файл, верно? Когда он попадает в ведро, ведро отправит событие в нашу внутреннюю систему, говоря: "Эй, файл загружен. Выполните некоторые действия." Некоторые действия могут включать генерацию миниатюры, вызов сервиса реального времени для фактического обновления всех ваших личных компьютеров с этим файлом. Это также может быть, я не знаю, сервис уведомлений для уведомления, например, push-уведомлений на ваш мобильный телефон о том, что файл был загружен. Так что это своего рода межсервисное взаимодействие, которое у вас будет. И для этого нет смысла фактически вызывать или, по крайней мере, объектное хранилище, чтобы знать обо всех ваших сервисах. Это нереалистично. Так это не работает. Ему нужно знать только об одном сервисе, о вашем API. Как это работает? Нам, конечно, нужна новая часть. И это обычно известно как брокер или как очередь, Kafka, RabbitMQ, что бы вы ни хотели, подсистема публикации-подписки. Это будет, по сути, брокер, кто-то посередине, кто будет получать сообщения, а затем он просто позаботится о доставке этих сообщений вашим сервисам. Надеюсь, это не было сложно. Я думаю, было немного, но позвольте мне разбить проблему и показать вам, и объяснить, почему вам нужен брокер. Так что это своего рода начало курса по микросервисам, но это идея. Так что у нас есть наше объектное хранилище, может быть, другой сервис. Он будет рассылать несколько или только одно событие этим подписчикам. Хорошо. Так что миниатюра будет генерировать миниатюру из видео, а реальное время также хочет обновлять другие машины в вашей сети с этим новым файлом. Хорошо. Так что поток следующий. Клиент обращается к API Gateway, который имеет токен. Аутентификация проверяет. Все хорошо. Сервис метаданных создает строку. Он возвращает URL для загрузки. Клиент автоматически загрузит это в объектное хранилище напрямую. Так что мы знаем, что не следует загружать напрямую в нашу архитектуру. А затем клиент загружает байты в ведро. Все хорошо. До сих пор мы видели все. А затем сервис миниатюр будет потреблять событие, генерировать предварительный просмотр и записывать обратно. Так вот где начинается проблема. Что произойдет, если, давайте даже немного больше разберем проблему. Что произойдет, если мы просто сделаем этот синхронный вызов сервису миниатюр после создания видео. Так что видео создано, загружено или изображение, и нам нужно создать миниатюру, какой-то побочный эффект, и если объект напрямую вызовет наш сервис, что произойдет, если, например, наш сервис миниатюр был недоступен, что произойдет, если наш сервис был медленным, и запрос истек. Так что, например, если мы получили 404 или 503 Service Unavailable. По сути, что произойдет, это то, что наше видео, которое мы только что загрузили, будет без миниатюры, и мы не будем знать почему. Так что, конечно, платформы, такие как YouTube, Google Drive, имеют решения для этого. Они имеют надежность и долговечность сообщений, событий, и, конечно, это прямое взаимодействие не является вариантом "иди или умри", и именно поэтому мы не хотим иметь этот синхронный вызов. Мы хотим иметь кого-то посередине, кто только получает сообщения, эти события, и он очень надежен и способен доставлять их сервисам. Так что вторая часть — да, это, по сути, то, что я сказал, что делает сервис, и что, если мы хотим также рассылать или отправлять несколько копий одного и того же события другим сервисам. Так что, по сути, то, что у нас есть прямо здесь. Так что, по сути, потребительский сервис должен будет уведомить всех этих парней, что, конечно, не имеет смысла, это не масштабируется. Лучшим решением, конечно, будет иметь этого парня посередине, который является нашим брокером. Так что вместо того, чтобы объектное хранилище напрямую общалось с сервисом, оно вызовет этого брокера, и этот брокер будет, по сути, высокодоступным. Так что он позаботится о том, чтобы он был очень хорош в поддержании работоспособности. Он не будет разбивать сообщения. Даже если он выйдет из строя, они будут долговечными. Они могут храниться в отдельной базе данных. У него есть функция повторной отправки. Так что, если сообщение не доставлено, например, файловому сервису, мы знаем, что он подтвердит это, и он повторно отправит его снова через определенный период времени. Если сообщение не доставлено, мы поместим его в кладбище сообщений, обычно называемое очередью мертвых писем. И это будет способ, которым мы должны настроить оповещения в нашей системе для Slack, для Discord, зная, и тогда это будет способ для нас узнать: эй, сообщение X не было доставлено. Так что давайте посмотрим, что происходит. Так что я перехожу ко многим темам, но это действительно природа распределенных систем микросервисов, и действительно основная тема, которую я хочу вам показать, это то, что это снова, как и начало сервиса сервера и базы данных. Это снова разделение обязанностей, но в большем масштабе. Сервис миниатюр отвечает только за создание миниатюр, реальное время для уведомлений в реальном времени, файлы — то же самое, и аутентификация — то же самое. Итак, одна из последних тем, потому что я могу продолжать этот видеоролик бесконечно и масштабировать проблему. Я хочу закончить только кэшированием, CDN и ограничением скорости, потому что наше приложение очень сильно масштабируется. Мы получаем, например, 10 000 активных пользователей в месяц. Это много, верно? Это также означает, что бизнес идет хорошо. Но есть еще ряд проблем, с которыми мы сталкиваемся. Так что мы можем масштабироваться бесконечно. Мы можем просто бросать деньги в проблему. Мы можем вертикально или горизонтально масштабироваться. Наша архитектура выглядит хорошо и распределена. Но если 500 пользователей, которые теперь начинают попадать в область оптимизации, и я хочу оставить это напоследок, потому что вы не должны преждевременно оптимизировать. Это целая проблема излишнего усложнения сама по себе. Так что я хочу оставить это напоследок, и именно здесь мы начинаем экономить ресурсы, начинаем пытаться срезать эти миллисекунды запросов, кэширование попадает сюда. Так что давайте представим, что это классическая проблема. 500 пользователей пытаются открыть один и тот же файл каждый день, например, на главном экране вашего приложения. Если они открывают один и тот же файл каждый раз, нам придется всегда тратить вычислительные ресурсы, по сути, от шлюза к файлам, к реляционной базе данных, к объектному хранилищу и обратному потоку. Так что весь процесс, который мы видели: получение файла, шлюз, файлы, реляционная база данных для метаданных. Мы получаем метаданные, идем к объектному хранилищу, и объектное хранилище возвращает их шлюзу, а шлюз возвращает файл, обогащенный всеми данными. Хорошо, так что это своего рода весь поток, который мы хотим оптимизировать, и есть несколько способов оптимизировать здесь. Мы можем сначала оптимизировать само изображение и актив, изображение — это отдельный зверь, потому что это обычно как 1 ГБ, 200 мегабайт изображений, а метаданные — это другое. Так что давайте начнем с метаданных, верно? Так что кэширование, по сути, это будет слой, который мы добавим сюда. Так что это будет, например, Redis. Если вы знакомы с Redis, Redis — это хранилище ключ-значение. Это способ кэшировать значения в оперативной памяти. Так что, например, причина, по которой вы хотите кэшировать в оперативной памяти вашего компьютера, заключается в том, что это быстрый доступ. Так что получить данные из оперативной памяти очень быстро, чем идти на диск. Так что это своего рода компьютерная наука, но именно поэтому кэш хорош, потому что мы используем оперативную память. Однако, как вы видели недавно с ИИ и всем остальным, оперативная память очень дорогая. Это редкий ресурс, и поэтому мы хотим использовать его только для мелких вещей, которые невелики. Так что вы не помещаете файлы в оперативную память. Это первое, что вы можете соблазниться сделать, эй, давайте просто поместим наш весь файл в оперативную память. Обычно так не делают. Это очень дорого. И я говорю обычно, потому что есть бизнесы, которые делают это, потому что это действительно предоставляет им лучший сервис. Я думаю, что Netflix делает что-то подобное. Например, если новый фильм или сериал собирается быть очень популярным, они могут просто поместить его в оперативную память только для этого первоначального всплеска новых пользователей. Я отвлекаюсь, но, по сути, мы начнем с кэширования метаданных наших файлов. Так что просто имя файла, свойства, потому что это никогда не меняется, если редко. Хорошо. И это, по сути, кэширование байтов файла в кэше. Так что в Redis, например, обычно неправильно. Я говорю обычно, потому что иногда это может быть правильно. 200-мегабайтное видео в Redis — это дорогая память, и Redis не предназначен для потоковой передачи больших блобов. Так как же нам на самом деле передавать кэшированные видео? Хорошо. Так как же нам на самом деле кэшировать наши видео, наши изображения, наши активы? Вот где появляется еще один компонент головоломки, это CDN. Так что CDN будет здесь, на границе, за пределами нашего кластера. По сути, CDN — это своего рода собственный кэш, но он очень хорошо используется для активов, больших вещей, но прежде чем я фактически расскажу о CDN, давайте разберемся, как работает кэш, как это работает, как это будет своего рода алгоритмом, что произойдет, это то, что всякий раз, когда поступает запрос, фактически, позвольте мне показать вам, так что мы получим это изображение, мы вызовем шлюз, и шлюз вызовет файловый сервис, и, например, файловый сервис может обратиться напрямую к кэшу. И если файл находится в кэше, теплый и готов к обслуживанию, мы можем фактически просто вернуть этот файл из кэша вместо того, чтобы идти напрямую в реляционную базу данных, в объектное хранилище, а затем перенаправлять его. Мы можем фактически или нам нужно фактически обратиться к объектному хранилищу в этом случае, потому что нам нужно получить файл. Но просто чтобы получить URL файла по идентификатору файла, нам нужно обратиться к реляционной базе данных. Так что здесь есть еще один цикл. Вот где кэш пригодится, потому что мы напрямую обратимся к кэшу, и кэш обойдет базу данных, потому что метаданные файла находятся в кэше. Так что своего рода алгоритм: существует ли ключ, например, файл 1 2 3, в кэше. Если это правда, мы просто вернем его, верно? Так что мы просто отправим его. Но если это первый раз, когда это изображение запрашивается, и его нет в кэше, что мы сделаем, это то, что мы, конечно, обратимся к базе данных. Так что получим его из базы данных. И как только мы его получим, мы можем фактически сохранить его в кэше, чтобы следующий запрос был кэширован. Это будет быстрее. И, наконец, мы, конечно, вернемся к пользователю. Так что это шаблон, который вы часто видите, когда реализуете механизмы кэширования в своем бэкенде. Так что это хороший шаблон, с которым стоит ознакомиться. Теперь, возвращаясь к CDN, это немного похоже на кэш, и поэтому я добавил сюда некоторые заметки, чтобы облегчить это. Так что давайте представим, что у CDN есть сотни небольших серверов или небольших точек присутствия по всему миру. Так что это хорошая вещь о поставщике CDN, что у них больше присутствия по всему миру, потому что это
означает, что пользователи при создании запроса вместо обращения к вашему серверу обращаются напрямую к CDN. В этом сила CDN, потому что она полностью избегает всех этих вычислений, которые мы проделали, чтобы просто получить изображение. Хорошо. Итак, первый пользователь, например, в Лиссабоне, пытается получить файл один-два-три, и он извлекает его из базы данных. Он делает все, что мы делали раньше. Но затем CDN кэширует это изображение, так что в следующий раз, когда кто-то, и в данном случае даже он сам или кто-либо в Лиссабоне, получит это изображение из, э, C. Таким образом, следующие 499 пользователей, например, получат это изображение намного быстрее, например, за 20 миллисекунд вместо, например, 1 секунды, потому что этот сервер, эта CDN намного ближе к пользователям. Например, если у вас есть другие пользователи в США, задержка до Лиссабона составляет около 300 миллисекунд. Так что приятно иметь изображения, кэшированные рядом с вашими пользователями. Итак, именно здесь на помощь приходит CDN, и для нашего бизнеса это на самом деле очень важный, э, строительный блок. И чтобы завершить весь этот урок, позвольте мне просто перейти к ограничению скорости, потому что это тоже важно. И всякий раз, когда вы начинаете масштабироваться, очень важно иметь ограничение скорости, иначе злонамеренные пользователи могут попытаться исчерпать ресурсы вашей инфраструктуры, и это приведет к масштабированию, э, и растрате денег, ресурсов, повлияет на опыт других ваших пользователей просто потому, что они хотят быть злонамеренными. Как мы с этим работаем? Потому что у нас уже есть все строительные блоки. У нас есть наш кэш, и ограничение скорости будет работать с кэшем. Произойдет следующее: у нас будет какой-то уровень, даже на самом шлюзе или даже у облачного провайдера, он может фактически предоставить вам это бесплатно, но, по сути, у нас будет, э, где-то ограничитель скорости или какой-то технологический блок, который, вы можете представить, может быть на шлюзе, может быть другой сервис, который, по сути, будет просматривать запрос, поступающий от вашего пользователя, идентифицировать его по IP-адресу или любой другой идентифицирующей информации, а затем он запишет в кэш что-то вроде этого, и поскольку кэш является хранилищем пар ключ-значение, он работает быстро, потому что он находится в оперативной памяти. Вы можете сказать, например, пользователь, э, один-два-три сделал, я не знаю, около пяти запросов за последнюю, э, минуту, и мы заблокируем его, если он достигнет 10 запросов, он получит код состояния 429 в HTTP, так что он будет ограничен по скорости. Это очень упрощенно, у меня на самом деле есть видео об ограничении скорости на моем YouTube-канале, если вы хотите посмотреть его, мы на самом деле рассматриваем несколько алгоритмов, потому что есть разные способы ограничения скорости. Но идея в том, что у нас будет уровень, который будет подсчитывать запросы от каждого пользователя, и мы, конечно, будем, э, пытаться группировать их в, э, некоторое общее количество. Теперь вы, возможно, знакомы с этой идеей по-другому. Например, если вы используете Quadcode SHPTT, у них есть своего рода идея лимитов. Вы можете просто использовать определенное количество токенов в день. Это очень похоже. Это немного отличается, потому что это своего рода валюта, но это своего рода та же идея, что вы ограничены по скорости для определенных ресурсов, потому что их дорого вычислять. И снова, кэш также довольно хорошо справляется с этим, с предварительным вычислением дорогостоящих вычислений и их хранением здесь в памяти, потому что, например, получение изображения не так уж и дорого. Но другое дорогостоящее вычисление может быть компиляция кода. вы можете захотеть сохранить это в объектном хранилище и получить это из кэша, например. Это еще один пример, который вы можете увидеть. Таким образом, на протяжении всей вашей карьеры все эти шаблоны станут, по сути, вашими друзьями. Вы будете видеть их, вы можете увидеть, какой путь мы прошли. И я действительно хотел показать вам в этом видео, что вы можете перейти от очень простой архитектуры к чему-то сложному и по пути понять плюсы, плюсы, минусы каждого решения, которое мы принимаем в нашей системе. И снова, архитектура — это не только инфраструктура. Как вы видели с кэшированием, мы говорили об алгоритмах, продуктовых решениях, какой тип базы данных мы будем использовать. Так что это понимание проблемы, предметной области, а затем построение программного решения, которое решит ее наилучшим образом и которое будет масштабироваться в будущем. Так что, надеюсь, из этого видео вы что-то получили, вам понравилось. Дайте мне знать в комментариях, и давайте продолжим обсуждение там. Спасибо за просмотр.