Системный дизайн может казаться ошеломляющим, особенно когда вы только начинаете и не знаете, с чего начать.
Но как только вы поймете основные концепции и строительные блоки, он перестает быть таким пугающим — готовитесь ли вы к собеседованиям или проектируете масштабируемые системы на работе.
В этой статье я проведу вас через 30 самых важных концепций системного дизайна, которые должен знать каждый разработчик.
Изучение этих концепций помогло мне получить предложения от нескольких крупных технологических компаний. И за последние 8 лет работы инженером я видел, как они неоднократно используются при создании и масштабировании больших систем.
1. Клиент-серверная архитектура
Почти каждое веб-приложение, которым вы пользуетесь, построено на этой простой, но мощной концепции, называемой клиент-серверной архитектурой.
С одной стороны у нас есть клиент — это может быть веб-браузер, мобильное приложение или любое другое фронтенд-приложение.
А с другой стороны — сервер, машина, которая работает непрерывно, ожидая входящие запросы.
Клиент отправляет запрос на сохранение, получение или изменение данных.
Сервер получает запрос, обрабатывает его, выполняет необходимые операции и отправляет ответ обратно.
Это звучит просто, но возникает большой вопрос: как клиент вообще узнает, где найти сервер?
2. IP-адрес
Клиент не знает магическим образом, где находится сервер; ему нужен адрес, чтобы найти его и связаться с ним.
В интернете компьютеры идентифицируют друг друга с помощью IP-адресов, которые работают как телефонные номера для серверов.
Каждый общедоступный сервер имеет уникальный IP-адрес. Когда клиент хочет взаимодействовать с сервисом, он должен отправлять запросы на правильный IP-адрес.
Но есть проблема:
Когда мы посещаем веб-сайт, мы не вводим его IP-адрес — мы просто вводим имя сайта.
Мы не можем ожидать, что пользователи (или даже системы) будут запоминать строки случайных чисел для каждого сервиса, к которому они подключаются.
И если мы перенесем наш сервис на другой сервер, его IP-адрес может измениться, нарушив все прямые соединения.
3. DNS
Вместо того чтобы полагаться на трудно запоминаемые IP-адреса, мы используем нечто более удобное для человека: доменные имена.
Но нам нужен способ сопоставить доменное имя с соответствующим IP-адресом.
Здесь на помощь приходит DNS (Система доменных имен). Она сопоставляет легко запоминающиеся доменные имена с соответствующими IP-адресами.
Вот что происходит за кулисами:
Когда вы вводите dzen.ru в браузере, ваш компьютер спрашивает DNS-сервер о соответствующем IP-адресе.
Как только DNS-сервер отвечает с IP-адресом, ваш браузер использует его для установления соединения с сервером и отправки запроса.
Вы можете найти IP-адрес любого домена с помощью команды ping. Просто откройте терминал и введите ping, а затем доменное имя. Он вернет IP-адрес, назначенный этому домену в данный момент.
4. Прокси / Обратный прокси
Когда вы посещаете веб-сайт, ваш запрос не всегда идет напрямую к серверу — иногда он сначала проходит через прокси или обратный прокси.
Прокси-сервер действует как посредник между вашим устройством и интернетом.
Когда вы запрашиваете веб-страницу, прокси пересылает ваш запрос целевому серверу, получает ответ и отправляет его обратно вам.
Прокси скрывает ваш IP-адрес, сохраняя ваше местоположение и личность в тайне.
Обратный прокси работает наоборот. Он перехватывает клиентские запросы и перенаправляет их на внутренние серверы на основе заданных правил.
Предоставление прямого доступа к серверам может создавать риски безопасности, подвергая их угрозам, таким как хакеры и DDoS-атаки.
Обратный прокси снижает эти риски, действуя как контролируемая точка входа, которая регулирует входящий трафик и скрывает IP-адреса серверов.
Он также может выступать в роли балансировщика нагрузки, распределяя трафик между несколькими серверами.
Если вы хотите узнать больше о прокси и обратном прокси, ознакомьтесь с этой статьей:
[Ссылка на статью]
5. Задержка (Latency)
Всякий раз, когда клиент общается с сервером, всегда есть некоторая задержка. Одной из главных причин этой задержки является физическое расстояние.
Например, если наш сервер находится в Нью-Йорке, но пользователь в Индии отправляет запрос, данные должны пройти половину земного шара, а затем ответ должен проделать тот же долгий путь обратно.
Эта задержка "туда-обратно" называется латентностью (задержкой) — общее время, необходимое для передачи данных между клиентом и сервером. Высокая задержка может сделать приложения медленными и неотзывчивыми.
Один из способов уменьшить задержку — развернуть наш сервис в нескольких центрах обработки данных по всему миру.
Таким образом, пользователи могут подключаться к ближайшему серверу, вместо того чтобы ждать, пока данные пересекут земной шар.
Как только соединение установлено, как клиенты и серверы на самом деле общаются?
6. HTTP/HTTPS
Каждый раз, когда вы посещаете веб-сайт, ваш браузер и сервер общаются, используя набор правил, называемых HTTP (Протокол передачи гипертекста).
Вот почему большинство URL-адресов начинаются с http:// или его защищенной версии https://.
Вот как это работает:
Клиент отправляет запрос на сервер. Этот запрос включает заголовок (содержащий детали, такие как тип запроса, тип браузера и куки) и иногда тело запроса (которое содержит дополнительные данные, например, введенные в форму).
Сервер обрабатывает запрос и отвечает HTTP-ответом — либо возвращая запрошенные данные, либо сообщение об ошибке, если что-то пошло не так.
У HTTP есть серьезный недостаток безопасности: он отправляет данные в виде открытого текста. Это серьезная проблема, особенно для конфиденциальной информации, такой как пароли, данные кредитных карт и личные данные.
Вот почему современные веб-сайты используют HTTPS (Защищенный протокол передачи гипертекста). HTTPS шифрует все данные с помощью SSL/TLS, гарантируя, что даже если кто-то перехватит запрос, он не сможет его прочитать или изменить.
Но клиенты и серверы не обмениваются напрямую необработанными HTTP-запросами и ответами.
HTTP — это всего лишь протокол для передачи данных, но он не определяет:
- Как должны быть структурированы запросы
- В каком формате должны быть ответы
- Или как разные клиенты должны взаимодействовать с сервером.
Здесь на помощь приходят API (или Интерфейсы прикладного программирования).
7. API
Представьте API как посредника, который позволяет клиентам (таким как веб-и мобильные приложения) общаться с серверами, не беспокоясь о деталях низкого уровня.
Почти каждый цифровой сервис, которым вы пользуетесь — социальные сети, электронная коммерция, онлайн-банкинг, приложения для вызова такси — построен на API, работающих за кулисами.
Вот как это обычно работает:
Клиент отправляет запрос к API.
API, размещенный на сервере, обрабатывает запрос, взаимодействует с базами данных или другими сервисами и подготавливает ответ.
API отправляет ответ обратно в структурированном формате, обычно JSON или XML, который клиент понимает и может отобразить.
API обеспечивают уровень абстракции — клиенту не нужно знать, как сервер обрабатывает запрос, важно только, что он возвращает ожидаемые данные.
Если вы хотите узнать больше об API, ознакомьтесь с этой статьей:
[Ссылка на статью]
Но не все API одинаковы. Существуют разные стили API для разных нужд. Два самых популярных — REST и GraphQL.
8. REST API
Среди различных стилей API, REST (Передача состояния представления) является наиболее широко используемым.
REST API следует набору правил, определяющих, как клиенты и серверы общаются через HTTP структурированным образом.
REST является:
- Stateless (Без сохранения состояния): Каждый запрос независим; сервер не хранит состояние клиента.
- Resource-Based (Основан на ресурсах): Все рассматривается как ресурс (например, /users, /orders, /products).
- Использует стандартные HTTP-методы: Клиенты взаимодействуют с ресурсами, используя HTTP-методы, такие как:
GET → Получает данные (например, получение профиля пользователя).
POST → Создает новые данные (например, добавление нового пользователя).
PUT/PATCH → Обновляет существующие данные (например, изменение настроек пользователя).
DELETE → Удаляет данные (например, удаление аккаунта).
REST API хороши, потому что они просты, масштабируемы и их легко кэшировать, но у них есть ограничения, особенно при работе со сложным извлечением данных.
Конечные точки REST часто возвращают больше данных, чем нужно, что приводит к неэффективному использованию сети. Если API не возвращает связанные данные, клиенту может потребоваться выполнить несколько запросов для получения всей необходимой информации.
Чтобы решить эти проблемы, в 2015 году Facebook представил GraphQL.
9. GraphQL
В отличие от REST, который заставляет клиентов получать фиксированные наборы данных, GraphQL позволяет клиентам запрашивать именно то, что им нужно — не больше и не меньше.
С REST API, если вам нужны детали пользователя, его профиль и последние посты, вам, возможно, придется выполнить несколько запросов к разным конечным точкам:
- GET /api/users/123 → получить данные пользователя
- GET /api/users/123/profile → получить профиль пользователя
- GET /api/users/123/posts → получить посты пользователя
С GraphQL вы можете объединить эти запросы в один и получить именно те данные, которые вам нужны, в одном запросе.
Сервер отвечает только запрошенными полями, уменьшая избыточную передачу данных и повышая эффективность.
Однако у GraphQL также есть компромиссы — он требует больше обработки на стороне сервера и его не так легко кэшировать, как REST.
Узнайте больше о REST против GraphQL здесь:
[Ссылка на статью]
Когда клиент делает запрос, он обычно хочет сохранить или получить данные.
Но это поднимает другой вопрос — где хранятся сами данные?
10. Базы данных
Если наше приложение работает с небольшими объемами данных, мы могли бы хранить их в памяти.
Но современные приложения обрабатывают огромные объемы данных — намного больше, чем память может эффективно обработать.
Поэтому нам нужен выделенный сервер для хранения и управления данными — база данных.
База данных — это основа любого приложения. Она гарантирует, что данные хранятся, извлекаются и управляются эффективно, сохраняя при этом их безопасность, согласованность и долговечность.
Когда клиент запрашивает сохранение или получение данных, сервер взаимодействует с базой данных, извлекает необходимую информацию и возвращает ее клиенту.
Но не все базы данных одинаковы. Разные приложения имеют разные требования к масштабируемости, производительности и согласованности, поэтому выбор правильного типа базы данных важен.
Если вы хотите узнать о различных типах баз данных, ознакомьтесь с этой статьей:
[Ссылка на статью]
В системном дизайне мы обычно выбираем между SQL и NoSQL базами данных.
11. SQL vs NoSQL
SQL-базы данных хранят данные в таблицах со строгой предопределенной схемой и следуют свойствам ACID.
- Атомарность (Atomicity): Транзакция выполняется полностью или не выполняется вовсе.
- Согласованность (Consistency): Данные всегда остаются действительными и соответствуют заданным правилам.
- Изоляция (Isolation): Транзакции не мешают друг другу.
- Долговечность (Durability): После сохранения данные не будут потеряны, даже в случае сбоя системы.
Благодаря этим гарантиям, SQL-базы данных идеально подходят для приложений, требующих строгой согласованности и структурированных связей, таких как банковские системы.
Примеры популярных SQL-баз данных: MySQL и PostgreSQL.
NoSQL-базы данных, с другой стороны, разработаны для высокой масштабируемости и производительности.
Они не требуют фиксированной схемы и используют различные модели данных, включая:
- Хранилища ключ-значение: Быстрый поиск для простых пар ключ-значение (например, Redis).
- Документоориентированные хранилища: Хранят гибкие, подобные JSON документы (например, MongoDB).
- Графовые базы данных: Лучше всего подходят для сильно связанных данных (например, Neo4j).
- Колоночные хранилища: Оптимизированы для крупномасштабных распределенных данных (например, Cassandra).
Итак, какую из них использовать? Это зависит от требований системы.
- Если вам нужны структурированные, реляционные данные со строгой согласованностью → SQL лучше.
- Если вам нужна высокая масштабируемость, гибкие схемы или быстрые операции чтения/записи в масштабе → NoSQL лучше.
Многие современные приложения используют и SQL, и NoSQL вместе.
Например, платформа электронной коммерции может:
- Хранить заказы клиентов в SQL (потому что они требуют строгой согласованности).
- И хранить рекомендации продуктов в NoSQL (потому что им нужен гибкий и быстрый поиск).
Если вы хотите узнать больше о SQL против NoSQL, ознакомьтесь с этой статьей:
[Ссылка на статью]
12. Вертикальное масштабирование
По мере роста нашей пользовательской базы растет и количество запросов, поступающих на наши серверы приложений.
Первоначально одного сервера может быть достаточно для обработки нагрузки. Но по мере увеличения трафика этот единственный сервер может стать узким местом, замедляя все.
Одно из самых быстрых решений — модернизировать существующий сервер, добавив больше CPU, RAM или памяти.
Этот подход называется Вертикальным масштабированием (Scaling Up) — сделать одну машину более мощной.
Но у этого подхода есть серьезные ограничения:
- Аппаратные ограничения: Вы не можете постоянно модернизировать сервер. У каждой машины есть максимальная емкость.
- Стоимость: Более мощные серверы становятся экспоненциально дороже.
- Единая точка отказа (SPOF): Если этот один сервер выйдет из строя, вся система упадет.
Таким образом, хотя вертикальное масштабирование и является быстрым решением, оно не является долгосрочным решением для обработки высокого трафика и обеспечения надежности системы.
Давайте посмотрим на лучший подход — тот, который делает нашу систему более масштабируемой и отказоустойчивой.
13. Горизонтальное масштабирование
Вместо модернизации одного сервера, что если мы добавим больше серверов, чтобы распределить нагрузку?
Этот подход называется Горизонтальным масштабированием (Scaling Out) — когда мы распределяем рабочую нагрузку по нескольким машинам.
Этот подход лучше, потому что:
- Больше серверов = Больше мощностей: Система может более эффективно справляться с растущим трафиком.
- Нет единой точки отказа: Если один сервер выйдет из строя, другие могут взять на себя его функции, повышая надежность.
- Экономичность: Вместо инвестиций в одну супер-дорогую машину мы можем использовать несколько доступных.
Но горизонтальное масштабирование создает новую проблему: как клиенты узнают, к какому серверу подключаться?
Здесь на помощь приходит Балансировщик нагрузки.
14. Балансировщики нагрузки
Балансировщик нагрузки находится между клиентами и внутренними серверами, действуя как диспетчер трафика, который распределяет запросы по нескольким серверам.
Если один сервер выходит из строя, балансировщик нагрузки автоматически перенаправляет трафик на другой здоровый сервер.
Но как балансировщик нагрузки решает, какой сервер должен обрабатывать следующий запрос?
Он использует алгоритмы балансировки нагрузки, такие как:
- Round Robin (Циклический): Запросы отправляются на серверы последовательно, один за другим, по циклу.
- Least Connections (Наименьшее количество соединений): Запросы отправляются на сервер с наименьшим количеством активных соединений.
- IP Hashing (Хеширование по IP): Запросы с одного и того же IP-адреса всегда идут на один и тот же сервер, что помогает с согласованностью сессий.
Узнайте больше об алгоритмах балансировки нагрузки здесь:
[Ссылка на статью]
До сих пор мы говорили о масштабировании наших серверов приложений, но по мере роста трафика объем данных также увеличивается.
Сначала мы можем масштабировать базу данных вертикально, добавляя больше CPU, RAM и памяти, но существует предел того, с чем может справиться одна машина.
Итак, давайте рассмотрим другие методы масштабирования баз данных, которые помогают эффективно управлять большими объемами данных.
15. Индексирование базы данных
Один из самых быстрых и эффективных способов ускорить запросы на чтение из базы данных — это индексирование.
Представьте это как индекс в конце книги — вместо того чтобы пролистывать каждую страницу, вы сразу переходите к нужному разделу.
Индекс базы данных работает так же. Это супер-эффективная поисковая таблица, которая помогает базе данных быстро найти необходимые данные, без сканирования всей таблицы.
Индекс хранит значения столбцов вместе с указателями на фактические строки данных в таблице.
Индексы обычно создаются для столбцов, которые часто запрашиваются, таких как:
- Первичные ключи
- Внешние ключи
- Столбцы, используемые в условиях WHERE
Но будьте осторожны: хотя индексы ускоряют чтение, они замедляют запись (INSERT, UPDATE, DELETE), поскольку индекс необходимо обновлять при каждом изменении данных.
Вот почему мы должны индексировать только наиболее часто используемые столбцы.
Узнайте больше об индексах базы данных здесь:
[Ссылка на статью]
Индексирование значительно улучшает производительность чтения, но что, если даже индексации недостаточно и наша база данных не справляется с растущим количеством запросов на чтение?
Здесь на помощь приходит следующий метод масштабирования базы данных — Репликация.
16. Репликация
Точно так же, как мы добавили больше серверов приложений для обработки трафика, мы можем масштабировать нашу базу данных, создавая ее копии на нескольких серверах.
Вот как это работает:
- У нас есть одна основная база данных (также называемая первичной репликой), которая обрабатывает все операции записи (INSERT, UPDATE, DELETE).
- У нас есть несколько реплик для чтения, которые обрабатывают запросы на чтение (SELECT).
- Когда данные записываются в основную базу данных, они копируются в реплики для чтения, чтобы они оставались синхронизированными.
Репликация улучшает производительность чтения, поскольку запросы на чтение распределяются по нескольким репликам, снижая нагрузку на каждую из них.
Это также повышает доступность, так как если первичная реплика выходит из строя, реплика для чтения может взять на себя роль новой первичной.
Репликация отлично подходит для масштабирования приложений с интенсивным чтением, но что, если нам нужно масштабировать запись или хранить огромные объемы данных?
17. Шардирование
Допустим, теперь у нашего сервиса миллионы пользователей, и наша база данных выросла до терабайтов данных.
Один сервер базы данных в конечном итоге не сможет эффективно обрабатывать все эти данные.
Вместо того чтобы хранить все в одном месте, мы разделяем базу данных на более мелкие, более управляемые части и распределяем их по нескольким серверам.
Этот метод называется Шардированием.
- Мы делим базу данных на более мелкие части, называемые шардами.
- Каждый шард содержит подмножество общих данных.
- Данные распределяются на основе ключа шардирования (например, ID пользователя).
Распределяя данные таким образом, мы:
- Уменьшаем нагрузку на базу данных: Каждый шард обрабатывает только часть запросов.
- Ускоряем производительность чтения и записи: Запросы распределяются по нескольким шардам вместо того, чтобы попадать на одну базу данных.
Шардирование также называют горизонтальным секционированием, поскольку оно разделяет данные по строкам.
Если вы хотите узнать больше о шардировании, ознакомьтесь с этой статьей:
[Ссылка на статью]
Но что, если проблема не в количестве строк, а в количестве столбцов?
В таких случаях мы используем Вертикальное секционирование, где мы разделяем базу данных по столбцам. Давайте рассмотрим это дальше.
18. Вертикальное секционирование
Представьте, что у нас есть таблица User, которая хранит:
- Детали профиля (имя, email, фото профиля)
- Историю входов (последний вход, IP-адреса)
- Платежную информацию (адрес для выставления счетов, детали платежа)
По мере роста этой таблицы запросы становятся медленнее, потому что база данных должна сканировать множество столбцов, даже если запросу нужны только несколько конкретных полей.
Чтобы оптимизировать это, мы используем Вертикальное секционирование, где мы разделяем таблицу пользователя на более мелкие, более сфокусированные таблицы на основе шаблонов использования.
- User_Profile → Хранит имя, email, фото профиля.
- User_Login → Хранит временные метки входа.
- User_Billing → Хранит адрес для выставления счетов, детали платежа.
Это улучшает производительность запросов, поскольку каждый запрос сканирует только соответствующие столбцы, а не всю таблицу.
Это уменьшает ненужный ввод-вывод на диск, делая извлечение данных быстрее.
Однако, как бы мы ни оптимизировали базу данных, извлечение данных с диска всегда медленнее, чем из памяти.
Что, если мы сможем хранить часто используемые данные в памяти для молниеносного доступа?
Это называется Кэшированием.
19. Кэширование
Кэширование используется для оптимизации производительности системы путем хранения часто используемых данных в памяти, вместо того чтобы каждый раз извлекать их из базы данных.
Одна из самых распространенных стратегий кэширования — это шаблон Cache Aside.
Вот как это работает:
- Когда пользователь запрашивает данные, приложение сначала проверяет кэш.
- Если данные есть в кэше, они возвращаются мгновенно, избегая вызова базы данных.
- Если данных нет в кэше, приложение извлекает их из базы данных, сохраняет в кэше для будущих запросов и возвращает пользователю.
В следующий раз, когда те же данные будут запрошены, они будут получены непосредственно из кэша, что сделает запрос намного быстрее.
Чтобы предотвратить выдачу устаревших данных, мы используем Time-to-Live (TTL) — время жизни, установленное для кэшированных данных, чтобы они автоматически обновлялись через определенный период.
Популярные инструменты кэширования включают Redis и Memcached.
Если вы хотите узнать больше о стратегиях кэширования, ознакомьтесь с этой статьей:
[Ссылка на статью]
Давайте рассмотрим следующий метод масштабирования базы данных.
20. Денормализация
Большинство реляционных баз данных используют Нормализацию для эффективного хранения данных, разбивая их на отдельные таблицы.
Например, в системе электронной коммерции:
- Таблица Users хранит данные пользователей.
- Таблица Orders хранит их заказы.
- Таблица Products хранит детали продуктов.
Хотя это уменьшает избыточность, это также вводит объединения (JOIN). При извлечении данных из нескольких таблиц база данных должна объединять их с помощью операций JOIN, что может замедлять запросы по мере роста набора данных.
sql
SELECT o.order_id, u.name, u.email, o.product, o.amount
FROM orders o
JOIN users u ON o.user_id = u.user_id;
Денормализация уменьшает количество объединений, объединяя связанные данные в одну таблицу, даже если это означает дублирование некоторых данных.
Пример: Вместо хранения Users и Orders в отдельных таблицах мы создаем таблицу UserOrders, которая хранит данные пользователей вместе с их последними заказами.
Теперь, при получении истории заказов пользователя, нам не нужна операция JOIN — данные уже хранятся вместе, что приводит к более быстрым запросам и лучшей производительности чтения.
sql
SELECT order_id, user_name AS name, user_email AS email, product, amount
FROM orders;
Денормализация часто используется в приложениях с интенсивным чтением, где скорость важнее, но недостатком является увеличение использования хранилища и более сложные запросы на обновление.
21. Теорема CAP
По мере масштабирования нашей системы по нескольким серверам, базам данных и центрам обработки данных, мы вступаем в мир распределенных систем.
Одним из фундаментальных принципов распределенных систем является Теорема CAP, которая гласит: Ни одна распределенная система не может одновременно достичь всех трех следующих свойств:
- Согласованность (Consistency - C): Каждый узел всегда возвращает самые свежие данные.
- Доступность (Availability - A): Система всегда отвечает на запросы, даже если некоторые узлы вышли из строя (но данные могут быть не самыми свежими).
- Устойчивость к разделению (Partition Tolerance - P): Система продолжает работать, даже если есть сбой сети между узлами.
Поскольку сбои сети (P) неизбежны, мы должны выбирать между:
- Согласованность + Устойчивость к разделению (CP): Гарантирует, что каждый запрос получает самые свежие данные, но может отклонять запросы во время сбоев. Пример: SQL-базы данных, такие как MySQL.
- Доступность + Устойчивость к разделению (AP): Гарантирует, что система всегда отвечает, даже если некоторые данные устарели. Пример: NoSQL-базы данных, такие как Cassandra и DynamoDB.
Чтобы узнать больше о теореме CAP, ознакомьтесь с этой статьей:
[Ссылка на статью]
В распределенных NoSQL-базах данных достижение мгновенной согласованности на всех серверах слишком медленно.
Вместо этого мы используем Согласованность в конечном счете (Eventual Consistency) — что означает:
- Не все узлы обновляются мгновенно, но с течением времени они в конечном итоге синхронизируются и возвращают одни и те же данные.
- Это позволяет системе оставаться высокодоступной и быстрой даже при экстремальных нагрузках.
Как работает согласованность в конечном счете:
- Пользователь обновляет данные в одной реплике базы данных.
- Система немедленно подтверждает обновление, обеспечивая высокую доступность.
- Обновление затем асинхронно распространяется на другие реплики.
- После небольшой задержки все реплики имеют самые свежие данные, обеспечивая согласованность с течением времени.
22. Blob-хранилище
Большинство современных приложений не просто хранят текстовые записи, им также нужно обрабатывать изображения, видео, PDF-файлы и другие большие файлы.
Но вот в чем проблема: традиционные базы данных не предназначены для эффективного хранения больших неструктурированных файлов.
Так в чем же решение?
Мы используем Blob-хранилище, такое как Amazon S3 — высокомасштабируемый и экономически эффективный способ хранения больших неструктурированных файлов в облаке.
- Blob (двоичный большой объект) — это отдельные файлы, такие как изображения, видео или документы.
- Эти blob-объекты хранятся внутри логических контейнеров или корзин (buckets) в облаке.
- Каждый файл получает уникальный URL-адрес, что упрощает его получение и доставку через интернет.
Пример: https://my-bucket-name.s3.amazonaws.com/videos/tutorial.mp4
Использование blob-хранилища имеет несколько преимуществ, таких как:
- Масштабируемость: Может хранить петабайты данных без усилий.
- Оплата по факту использования: Вы платите только за то хранилище и извлечение, которые фактически используете.
- Автоматическая репликация: Данные копируются в несколько центров обработки данных и зон доступности для долговечности.
- Легкий доступ: Файлы можно получить с помощью REST API или прямых URL-адресов.
Один из распространенных случаев использования — потоковая передача аудио или видео файлов в пользовательское приложение в реальном времени.
Но прямая потоковая передача из blob-хранилища может быть медленной, особенно если данные хранятся в удаленном месте.
23. CDN (Сеть доставки контента)
Например, представьте, что вы в Индии пытаетесь посмотреть видео на YouTube, которое размещено на сервере в Калифорнии.
Поскольку данные видео должны путешествовать по всему миру, это может привести к буферизации и медленной загрузке.
Сеть доставки контента (CDN) решает эту проблему, доставляя контент быстрее пользователям в зависимости от их местоположения.
CDN — это глобальная сеть распределенных серверов, которые работают вместе для доставки веб-контента (такого как HTML-страницы, JavaScript-файлы, таблицы стилей, изображения и видео) пользователям на основе их географического положения.
Вместо обслуживания контента из одного центра обработки данных, CDN кэширует статический контент на множестве периферийных серверов по всему миру.
Когда пользователь запрашивает контент, ближайший CDN-сервер доставляет его вместо того, чтобы обращаться к исходному серверу.
Поскольку контент обслуживается с ближайшего CDN-узла, пользователи испытывают более быстрое время загрузки с минимальной буферизацией.
Чтобы узнать больше о CDN, ознакомьтесь с этой статьей:
[Ссылка на статью]
24. WebSockets
Большинство веб-приложений используют HTTP, который следует модели запрос-ответ.
- Клиент отправляет запрос.
- Сервер обрабатывает запрос и отправляет ответ.
- Если клиенту нужны новые данные, он должен отправить другой запрос.
Это хорошо работает для статических веб-страниц, но слишком медленно и неэффективно для приложений реального времени, таких как: приложения для живого чата, биржевые панели мониторинга и многопользовательские онлайн-игры.
С HTTP единственный способ получать обновления в реальном времени — это опрос (polling) — отправка повторяющихся запросов каждые несколько секунд.
Но опрос неэффективен, потому что увеличивает нагрузку на сервер и расходует пропускную способность, так как большинство ответов пусты (когда нет новых данных).
WebSockets решают эту проблему, позволяя непрерывную двустороннюю связь между клиентом и сервером по одному постоянному соединению.
Вот как работают WebSockets:
- Клиент инициирует соединение WebSocket с сервером.
- После установки соединение остается открытым.
- Сервер может отправлять обновления клиенту в любое время, не дожидаясь запроса.
- Клиент также может мгновенно отправлять сообщения на сервер.
Это обеспечивает взаимодействие в реальном времени и устраняет необходимость в опросе.
Чтобы узнать больше о WebSockets, ознакомьтесь с этой статьей:
[Ссылка на статью]
WebSockets обеспечивают связь в реальном времени между клиентом и сервером, но что, если серверу нужно уведомить другой сервер о наступлении события?
Пример:
- Когда пользователь совершает платеж, Stripe должен мгновенно уведомить ваше приложение.
- Если кто-то отправляет код в GitHub, система CI/CD (например, Jenkins) должна запуститься автоматически.
Вступают в игру Вебхуки (Webhooks).
25. Вебхуки
Вместо постоянного опроса API для проверки наступления события, Вебхуки позволяют серверу отправлять HTTP-запрос на другой сервер, как только событие происходит.
Вот как это работает:
- Получатель (ваше приложение) регистрирует URL-адрес вебхука у поставщика (например, Stripe, GitHub, Twilio).
- Когда событие происходит (например, пользователь совершает платеж), поставщик отправляет HTTP POST-запрос на URL-адрес вебхука с деталями события.
- Ваше приложение обрабатывает входящий запрос и соответствующим образом обновляет данные.
Это экономит ресурсы сервера и сокращает количество ненужных вызовов API.
26. Микросервисы
Традиционно приложения строились с использованием монолитной архитектуры, где:
- Все функции (например, аутентификация, платежи, заказы, доставка) находятся в одной большой кодовой базе.
- Если одна часть системы выходит из строя или требует масштабирования, страдает вся система.
- Развертывание рискованно — одно неудачное обновление может привести к падению всего приложения.
Пример: Представьте приложение для электронной коммерции, где модули заказов, платежей, инвентаря и доставки тесно связаны в единой кодовой базе.
Если система инвентаризации выйдет из строя, все приложение может упасть.
Монолиты хорошо работают для небольших приложений, но для крупномасштабных систем ими становится трудно управлять, масштабировать и развертывать.
Решение — разбить ваше приложение на более мелкие, независимые сервисы, называемые микросервисами, которые работают вместе.
Каждый микросервис:
- Выполняет одну функцию.
- Имеет свою собственную базу данных и логику, поэтому может масштабироваться независимо.
- Взаимодействует с другими микросервисами с помощью API или очередей сообщений.
Таким образом, сервисы можно масштабировать и развертывать по отдельности, не влияя на всю систему.
Однако, когда нескольким микросервисам необходимо взаимодействовать, прямые вызовы API не всегда эффективны — здесь на помощь приходят Очереди сообщений.
27. Очереди сообщений
В монолитной системе функции вызывают друг друга напрямую и ожидают ответа.
Но в системе на основе микросервисов этот подход неэффективен, потому что:
- Если один сервис медленный или недоступен, все ждут.
- Высокий трафик может перегрузить один сервис.
- Синхронное общение (ожидание немедленных ответов) плохо масштабируется.
Очередь сообщений позволяет сервисам общаться асинхронно, позволяя обрабатывать запросы без блокировки других операций.
Вот как это работает:
- Производитель (например, сервис оформления заказа) помещает сообщение в очередь (например, "Обработать платеж").
- Очередь временно хранит сообщение, пока Потребитель (например, платежный сервис) не будет готов его обработать.
- Потребитель извлекает сообщение и обрабатывает его.
Используя очереди сообщений, мы можем развязать сервисы и повысить масштабируемость и отказоустойчивость.
Распространенные системы очередей сообщений включают: Apache Kafka, Amazon SQS и RabbitMQ.
Чтобы узнать больше об очередях сообщений, ознакомьтесь с этой статьей:
[Ссылка на статью]
Используя очереди сообщений, мы можем предотвратить перегрузку внутренних сервисов в нашей системе.
Но как предотвратить перегрузку публичных API и сервисов, которые мы развертываем?
Мы используем Лимитирование запросов (Rate Limiting).
28. Лимитирование запросов
Представьте, что бот начинает отправлять тысячи запросов в секунду на ваш веб-сайт.
Без ограничений это может:
- Обрушить ваши серверы, потребляя все доступные ресурсы.
- Увеличить облачные расходы из-за чрезмерного использования API.
- Ухудшить производительность для легитимных пользователей.
Лимитирование запросов ограничивает количество запросов, которые клиент может отправить в течение определенного промежутка времени.
Вот как это работает:
- Каждому пользователю или IP-адресу назначается квота запросов (например, 100 запросов в минуту).
- Если они превышают этот лимит, сервер временно блокирует дополнительные запросы и возвращает ошибку (HTTP 429 — Too Many Requests).
Существуют различные алгоритмы лимитирования запросов. Некоторые из популярных:
- Fixed Window (Фиксированное окно): Ограничивает запросы на основе фиксированного временного окна (например, 100 запросов в минуту).
- Sliding Window (Скользящее окно): Более гибкая версия, которая динамически корректирует лимиты, чтобы сгладить всплески запросов.
- Token Bucket (Корзина токенов): Пользователи получают токены для запросов, которые пополняются с течением времени с фиксированной скоростью.
Чтобы узнать больше об алгоритмах лимитирования запросов, ознакомьтесь с этой статьей:
[Ссылка на статью]
Нам не нужно реализовывать собственную систему лимитирования запросов — это может обрабатываться чем-то, называемым API-шлюзом.
29. API-шлюзы
API-шлюз — это централизованный сервис, который обрабатывает аутентификацию, лимитирование запросов, логирование, мониторинг и маршрутизацию запросов.
Представьте приложение на основе микросервисов с несколькими сервисами.
Вместо того чтобы предоставлять каждый сервис напрямую, API-шлюз действует как единая точка входа для всех клиентских запросов.
Как работают API-шлюзы:
- Клиент отправляет запрос на API-шлюз.
- Шлюз проверяет запрос (например, аутентификация, лимиты запросов).
- Он направляет запрос к соответствующему микросервису.
- Ответ отправляется обратно через шлюз клиенту.
API-шлюз упрощает управление API и повышает масштабируемость и безопасность.
Популярные решения для API-шлюзов включают NGINX, Kong и AWS API Gateway.
Если вы хотите узнать больше об API-шлюзах, ознакомьтесь с этой статьей:
[Ссылка на статью]
30. Идемпотентность
В распределенных системах сбои сети и повторные попытки сервисов являются обычным явлением. Если пользователь случайно обновит страницу оплаты, система может получить два платежных запроса вместо одного.
Идемпотентность гарантирует, что повторяющиеся запросы дают тот же результат, как если бы запрос был сделан только один раз.
Вот как это работает:
- Каждому запросу присваивается уникальный идентификатор (например, request_1234).
- Перед обработкой система проверяет, был ли запрос уже обработан.
- Если да → Она игнорирует повторяющийся запрос.
- Если нет → Она обрабатывает запрос обычным образом.
Идемпотентность предотвращает дублирование транзакций и обеспечивает согласованность данных в распределенных системах.
Если вы хотите узнать больше об идемпотентности, ознакомьтесь с этой статьей:
[Ссылка на статью]
Большое спасибо за чтение!
Если вы нашли это полезным, поставьте лайк ❤️ и подумайте о подписке на новые материалы каждую неделю.
Страховка на собеседовании и поиск работы
Знание есть, но стресс мешает ?
Сообщество для прокачки карьеры и поиска работы в IT:
Платформа - https://itcareergym.tech/#home
Подпишись на - https://t.me/IT_Interview_Partner_Bot
Подпишись на - https://t.me/LyakhovEugene
Курс System Design Website - https://stepik.org/a/279467
C4 + AI: документируем архитектуру быстрее и понятнее - https://stepik.org/a/295262