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-шлюзом. По сути, API-шлюз будет получать запрос. Он будет анализировать его и перенаправлять в нужный сервис. Не только это, он также может агрегировать ответы. Например, чтобы получить вашу аутентификацию, нам нужно перейти в сервис аутентификации, но нам также может понадобиться перейти, например, в другой сервис, такой как сервис профиля, где мы фактически получаем профиль пользователя. Так что просто гипотетический случай, и, по сути, то, что делает этот шлюз, это то, что он будет координировать это распределение, и он вернет вам ответ пользователю, агрегированный со всеми этими запросами. Не только это, это решит вторую проблему, которую я упомянул, заключается в том, что мы не хотим, чтобы пользователи напрямую общались с сервисами. Так что мы сделаем всю эту частную сеть. Так что это то, что вы обычно видите как VPC, и, по сути, то, что у нас есть здесь, это то, что шлюз будет единственной точкой входа для связи с нашей системой. Все эти серверы не будут раскрывать свои порты наружу. Так что их IP-адрес будет просто внутри этой виртуальной сети, и единственный способ для клиентов общаться с нашим бизнесом — это через шлюз. Так что шлюз будет обрабатывать аутентификацию, маршрутизацию и все такое. Я оставил здесь балансировщик нагрузки, и я удалю его на следующих диаграммах, потому что мы также можем захотеть масштабировать шлюз. Например, если шлюз теперь становится единой точкой отказа, также может быть интересно иметь балансировщик нагрузки перед шлюзом, а также внутри нашего кластера. Так что мы перешли к тому, чтобы знать, каким сервисам нужно больше ресурсов, и тогда нам также нужно масштабировать наш шлюз, потому что наш шлюз может фактически масштабироваться вертикально, верно, или в данном случае горизонтально, но я пока оставлю это, но на следующих я всегда буду опускать балансировщик нагрузки, но вы можете представить, что здесь может быть балансировщик нагрузки. Итак, теперь, когда у нас есть наша распределенная архитектура, и нам нужна аутентификация, и имеет смысл, что, конечно, у нас всегда была аутентификация, но давайте углубимся в нее. Давайте фактически поймем, как мы можем фактически сделать это в этой распределенной архитектуре. Это почти то же самое, если это не если это просто монолитный сервис, просто один экземпляр сервера, как у нас был, по сути, здесь, если мы, например, думаем о том, как будет работать конечная точка файлов. Так что мы будем загружать некоторые файлы. Пользователь отправит этот полезный груз, верно? Что произойдет, так это то, что шлюз проверит, имеет ли пользователь токен JWT. Так что своего рода токен. Я говорю об этом на канале и во всех моих курсах, но, по сути, просто токен, подпись, означающая, что вы аутентифицированы. Он несет дату истечения срока действия. Так что шлюз проверит, хотите ли вы перейти в сервис аутентификации или нет. Но это потребует одного сетевого вызова. Что вы также можете захотеть сделать, это просто добавить сюда уровень безопасности на шлюзе или даже перед шлюзом для проверки этого. Так что я не буду вдаваться в подробности, если это сервис, если это шлюз. Каждый сервис отличается. Каждая архитектура отличается. Вы можете фактически иметь это или просто шлюз, обрабатывающий аутентификацию или, по крайней мере, базовую часть аутентификации, просто проверяя, действителен ли токен. Так что пользователь теперь отправляет этот заголовок авторизации с токеном. Вот как это обычно делается с файлами cookie. Так что в нашей архитектуре нет сетевых вызовов аутентификации, потому что мы просто проверим, имеет ли этот запрос действительный токен. Если да, мы просто поверим, что он не истек. Мы проверим это, а затем перенаправим на файлы. Так что прямо на краю мы можем фактически проверить и выдать код состояния 401, если это неавторизовано. Но у нас также есть сервис аутентификации. Так что же он делает? Как мы фактически получаем токен? Вот где мы переходим к генерации токена, и где-то на каждом веб-сайте будет страница входа. И вот как это работает. Вы отправляете свои учетные данные. Так что это может быть электронная почта и пароль или просто электронная почта с токеном единого входа. Но, по сути, пользователь запросит токен с действительными учетными данными. Так что пользователь может, возможно, уже существовать в вашей архитектуре, в вашей базе данных, и произойдет то, что шлюз просто перенаправит на аутентификацию, а аутентификация будет использовать своего рода закрытый ключ для подписи вашего токена, убедившись, что он действителен, а затем он фактически перенаправит. на уровне аутентификации на уровне шлюза, прошу прощения, вы можете фактически проверить, существует ли пользователь сначала, вызвав, например, наш сервис, в данном случае у нас есть этот миниатюра, я создал разные сервисы здесь, так что не беспокойтесь об этом, но, например, сервис пользователей может фактически просто отвечать за создание пользователей или проверку пользователей, а затем только тогда, если пользователь существует, мы фактически вызовем уровень аутентификации. Так что они могут быть вместе. Они могут не быть вместе. Это зависит от вас. Но просто чтобы показать вам, что шлюз будет этим парнем посередине этих решений. Теперь просто еще одна вещь об аутентификации в целом заключается в том, что это аутентификация. Так что это фактически означает, есть ли у вас доступ к приложению, а затем есть еще одна вещь, которая является авторизацией, которая фактически означает, есть ли у вас разрешения на загрузку файлов. Так что авторизация отличается от аутентификации и она полностью связана. Вы можете даже поместить ее в тот же сервис, но вы можете выполнять авторизацию во всех сервисах. Они могут иметь свой собственный уровень авторизации, фактически как разрешения. Итак, теперь, когда у нас есть наша архитектура микросервисов, давайте фактически поймем, как мы можем загружать файлы, потому что здесь есть новый компонент в головоломке, который является объектным хранилищем. Так что до сих пор мы фактически игнорировали, как загружать файлы, что является нашим бизнесом. Но я также хотел поместить все части на стол, чтобы вы поняли, как это работает, потому что это действительно начинает становиться сложным, когда несколько сервисов начинают общаться друг с другом, что мы и начнем делать сейчас. Так что, просто вернувшись немного, как работает загрузка и обслуживание файлов, и это своего рода повторяющаяся проблема, которую вы можете встретить в своей карьере, и вам, возможно, придется реализовать что-то подобное, я делал это много раз. Это интересная проблема, и всегда есть этот шаблон: вы собираетесь загрузить файл, верно, мы уже говорили об этом, и дело в том, что очень важно, этот файл, вы, мы не будем фактически делать прямую загрузку на ваш сервер, так что давайте представим здесь наш файловый сервис, очень простой, и нашу базу данных, это здесь не произойдет, вы не будете загружать файл напрямую в свою базу данных, в свою реляционную базу данных, потому что вы можете загружать 20-гигабайтный фильм, 200-мегабайтный фильм, 200-мегабайтное изображение, а реляционные базы данных не предназначены для этого. Не только это, вы не должны передавать все эти данные на свой сервер, потому что он, возможно, не позволит этого, и мы не должны, потому что это позволит вам предотвратить некоторые злонамеренные атаки со стороны пользователей. Так что вы не должны загружать напрямую на свой сервер. И как вы фактически загружаете файлы? Так что обычно делается то, что вы собираетесь, и да, у вас также может быть, например, тайм-аут, потому что это будет много данных для сервера, и обычно происходит то, что у вас будет объектное хранилище. Представьте, например, S3-ведро, ведро Google Cloud. Так что объектное хранилище, которое просто предназначено для хранения файлов, статических файлов, которые редко или вообще не меняются. Способ работы этого заключается в том, что вы обычно отправляете запрос на свой API, так что на ваш шлюз. Так что с метаданными, так что просто имя файла, размер файла, а также как блоб. Так что, например, ваш фактический файл, изображение. Но что происходит, так это то, что вы просто скажете: я хочу воспроизвести этот файл. Шлюз перейдет в файловый сервис. Он перейдет в реляционную базу данных. Он скажет: эй, пользователь хочет сохранить файл. Так что давайте сгенерируем некоторые метаданные, которые будут выглядеть примерно так. Так что, например, это будет таблица файлов. Так что у нас будет новый сгенерированный идентификатор для этого файла, у нас будет имя файла, мы начнем размер. Вы также можете захотеть сохранить, например, тип. Это в случае PNG, что-то вроде этого, но не сам файл. Так что это важная часть, потому что произойдет то, что хорошо, у нас есть метаданные файла, сохраненные, потому что мы теперь можем проверить, существует ли файл. Что мы должны сделать, это вызвать объектное хранилище, например, Google Cloud или что-то подобное, и сказать: эй, дайте мне ссылку, по которой мои пользователи могут напрямую загружать в ведро. Так что, по сути, это вернет вам ссылку, которая будет аутентифицирована или нет. Так что важно, чтобы вы разрешили очень короткое окно загрузки. Так что это будет иметь дату истечения срока действия, и она может быть или не быть аутентифицирована для загрузки. Я думаю, нормально оставить ее неаутентифицированной, но пока у нее короткое окно загрузки, это нормально, и она также будет ограничивать некоторые размеры. Так что мы можем сказать, например, только 50 мегабайт загрузок через эту ссылку, и с этим решением пользователь теперь будет, так что мы перенаправим с метаданными, мы скажем: эй, это 200, это нормально, и мы также перенаправим эту ссылку прямо здесь, и пользователь или фронтенд автоматически начнет загружать файл или передавать файл в это ведро. Так что у вас будет файл в ведре, загруженный напрямую туда. И если вы думаете об этом решении, оно действительно довольно интересное, потому что мы полностью обходим нашу архитектуру. Так что мы фактически идем напрямую в ведро, и наши серверы полностью не затронуты. Так что, если я вернусь к началу видео, мы говорили, что наш файловый сервис был перегружен запросами на загрузку, и это делало наши серверы очень медленными. Мы только что решили это с помощью объектного хранилища. Так что, вероятно, мне следовало представить вам эту часть раньше. Однако я также хочу показать вам, что это фактически архитектура с несколькими сервисами. Позвольте мне показать вам проблему. Представьте теперь, что мы фактически загрузили файл. Так что файл загружен. Но когда пользователь загружает файл, верно? Так что, когда он попадает в ведро, ведро отправит событие в нашу внутреннюю систему, говоря: "Эй, файл был загружен. Выполните некоторые действия." Некоторые действия могут включать генерацию миниатюры, вызов сервиса реального времени для фактического обновления всех ваших личных компьютеров этим файлом. Это также может быть, я не знаю, сервис уведомлений для уведомления, например, push-уведомлений на ваш мобильный телефон о том, что файл был загружен. Так что это своего рода связь между сервисами, которую вы будете иметь. И для этого не имеет смысла фактически вызывать или, по крайней мере, объектное хранилище, чтобы знать обо всех ваших сервисах. Это нереалистично. Так это не работает. Ему просто нужно знать об одном сервисе, о вашем API. Как это работает? Нам, конечно, нужна новая часть. И это обычно известно как брокер или как очередь, Kafka, RabbitMQ, что угодно, что вы хотите иметь подсистему публикации. Это будет фактически брокер, кто-то посередине, кто будет получать сообщения, а затем он просто позаботится о доставке этих сообщений вашим сервисам. Надеюсь, это не было сложно. Я думаю, было немного, но позвольте мне разбить проблему и показать вам и объяснить, почему вам нужен брокер. Так что это своего рода начало курса по микросервисам, но это идея. Так что у нас есть наше объектное хранилище, может быть, другой сервис. Он будет рассылать множество или просто одно событие этим подписчикам. Хорошо. Так что миниатюра будет генерировать миниатюру из видео, а реальное время также хочет обновлять другие машины на вашей сети с этим новым файлом. Хорошо. Так что поток следующий. Мы, клиент, обращаемся к API-шлюзу, у которого есть токен. Аутентификация проверяет. Все хорошо. Сервис метаданных создает строку. Он возвращает URL для загрузки. Клиент автоматически загрузит это в объектное хранилище напрямую. Так что мы знаем, что не следует загружать напрямую в нашу архитектуру. И затем клиент загружает байты в ведро. Все хорошо. До сих пор мы видели все. И затем сервис миниатюр будет потреблять событие, генерировать предварительный просмотр и записывать обратно. Так что здесь начинается проблема. Что произойдет, если, давайте даже немного усложним проблему. Что произойдет, если мы просто сделаем этот синхронный вызов сервису миниатюр после создания видео. Так что видео создано, загружено или изображение, и нам нужно создать миниатюру, какой-то побочный эффект, и если объект напрямую вызовет наш сервис, что произойдет, если, например, наш сервис миниатюр был отключен? Что произойдет, если наш сервис был медленным, и запрос истек? Так что, например, если мы получили 404 или 503, сервис недоступен. По сути, произойдет то, что наше видео, которое мы только что загрузили, будет без миниатюры, и мы не будем знать почему. Так что, конечно, платформы, такие как 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 кэширует это изображение, так что в следующий раз, когда кто-нибудь, и в данном случае даже он сам или кто-либо в Лиссабоне, получит это изображение из, э, CDN. Таким образом, следующие 499 пользователей, например, получат это изображение намного быстрее, например, за 20 миллисекунд вместо, например, 1 секунды, потому что этот сервер, эта CDN намного ближе к пользователям. Например, если у вас есть другие пользователи в США, задержка до Лиссабона составляет около 300 миллисекунд. Так что приятно иметь изображения, кэшированные рядом с вашими пользователями. Вот где появляется CDN, и для нашего бизнеса это на самом деле очень важный, э, строительный блок. И чтобы завершить этот урок, позвольте мне просто перейти к ограничению скорости, потому что это тоже важно. И всякий раз, когда вы начинаете масштабироваться, очень важно иметь ограничение скорости, иначе злонамеренные пользователи могут попытаться исчерпать ресурсы вашей инфраструктуры, и это приведет к масштабированию, э, и потратит деньги, ресурсы, повлияет на опыт других ваших пользователей просто потому, что они хотят быть злонамеренными. Как мы с этим работаем? Потому что у нас уже есть все строительные блоки. У нас есть кэш, и ограничение скорости будет работать с кэшем. Произойдет следующее: у нас будет какой-то уровень, даже на самом шлюзе или даже у облачного провайдера, он может фактически предоставить вам это бесплатно, но, по сути, у нас будет, э, где-то ограничитель скорости или какой-то технологический блок, который, вы можете представить, может быть на шлюзе, может быть другой сервис, который, по сути, будет видеть запрос, поступающий от вашего пользователя, идентифицировать его по IP-адресу или любой другой идентифицируемой информации, а затем он запишет в кэш что-то вроде этого, и поскольку кэш является хранилищем пар ключ-значение, он быстр, потому что он находится в оперативной памяти, вы можете сказать, например, пользователь, э, один-два-три сделал, я не знаю, как пять запросов за последнюю, э, минуту, и мы заблокируем его, если он достигнет 10 запросов, он получит код состояния 429 в HTTP, так что он будет ограничен по скорости. Это очень упрощенно, у меня на самом деле есть видео об ограничении скорости на моем YouTube-канале, если вы хотите посмотреть его, мы на самом деле рассматриваем несколько алгоритмов, потому что есть разные способы ограничения скорости. Но идея в том, что у нас будет уровень, который будет подсчитывать запросы от каждого пользователя, и мы, конечно, будем, э, пытаться группировать их в, э, некоторые общие суммы. Теперь вы можете быть знакомы с этой идеей по-другому. Например, если вы используете Quadcode SHPTT, у них есть что-то вроде этой идеи ограничений. Вы можете просто использовать определенное количество токенов в день. Это очень похоже. Это немного отличается, потому что это своего рода валюта, но это своего рода та же идея, что вы ограничены по скорости на определенных ресурсах, потому что они дороги в вычислении. И снова, кэш также очень хорошо справляется с этим, с предварительным вычислением дорогих вычислений и их хранением здесь в памяти, потому что, например, получение изображения не так дорого. Но другое дорогое вычисление может быть компиляцией некоторого кода. вы можете захотеть сохранить это в объектном хранилище и получить это из кэша, например. Это еще один пример, который вы можете увидеть. Таким образом, на протяжении всей вашей карьеры все эти шаблоны станут, по сути, вашими друзьями. Вы будете видеть их, вы можете увидеть, какой путь мы прошли. И я действительно хотел показать вам в этом видео, что вы можете перейти от очень простой архитектуры к чему-то сложному и по пути понять преимущества, плюсы, минусы каждого решения, которое мы принимаем в нашей системе. И снова, архитектура — это не только инфраструктура. Как вы видели с кэшированием, мы говорили об алгоритмах, продуктовых решениях, какой тип базы данных мы будем использовать. Так что это понимание проблемы, домена, а затем построение программного решения, которое решит ее наилучшим образом и которое будет масштабироваться в будущем. Так что, надеюсь, из этого видео вы что-то получили, вам понравилось. Дайте мне знать в комментариях, и давайте продолжим обсуждение там. Спасибо за просмотр.