
Вам не нужно изучать какую‑либо теорию, кроме этой статьи, чтобы начать собеседоваться. После прочтения смело приступайте к решению типовых System Design задач.
Изучая System Design, вы часто видите только теоретические материалы. В этой статье я постарался показать в том числе практическую реализацию многих вещей, чтобы вы не просто готовились к собеседованиям, но и знали, как эти вещи используются в реальном мире.
Содержание
Зачем изучать проектирование систем? (1 часть)
Что такое сервер?
Задержка и пропускная способность
Масштабирование и его типы
+ Вертикальное
+ ГоризонтальноеАвтоматическое масштабирование
Оценка на коленке
Теорема CAP
Масштабирование базы данных (2 часть)
+ Индексирование
+ Партиционирование
+ Архитектура «master-slave»
+ Multi-master
+ Шардирование
+ Недостатки ШардированияSQL и NoSQL СУБД. Когда какую базу данных использовать?
+ SQL СУБД
+ NoSQL СУБД
+ Особенности масштабирования
+ Когда использовать ту или иную базу данных?Микросервисы (3 часть)
+ Что такое монолит и микросервис?
+ Почему мы разбиваем наше приложение на микросервисы?
+ Когда следует использовать микросервисы?
+ Как клиенты отправляют запросы?Load Balancer
+ Зачем нам нужен балансировщик нагрузки?
+ Алгоритмы балансировщика нагрузкиКэширование
+ Введение в кэширование
+ Преимущества кэширования
+ Типы кэшей
+ Подробное описание RedisХранилище BLOB-объектов (4 часть)
+ Что такое BLOB и зачем нам нужно хранилище BLOB?
+ AWS S3Сеть доставки контента (CDN)
+ Знакомство с CDN
+ Как работает CDN?
+ Ключевые понятия в CDNMessage Broker
+ Асинхронное программирование
+ Зачем мы добавили посредника для передачи сообщений?
+ Queue
+ Stream
+ Кейсы использованияApache Kafka Deep dive
+ Когда использовать Kafka
+ Внутреннее устройство KafkaPub/Sub (текущая 5 часть)
Event-Driven Архитектура
+ Введение
+ Зачем использовать EDA?
+ Система нотификаций с id
+ Система с передачей всего состоянияDistributed Systems
Leader Election
Big Data Tools
Consistency Deep Dive
+ Когда использовать Strong Consistency, Eventual Consistency
+ Как добиться Strong, Eventual ConsistencyConsistent Hashing
Data Redundancy and Data Recovery
+ Зачем мы делаем резервные копии баз данных?
+ Различные способы резервного копирования данных
+ Непрерывное резервное копированиеProxy
+ Что такое прокси сервер?
+ Прямой и обратный прокси сервер
+ Создание собственного обратного прокси-сервераКак решить любую проблему, связанную с проектированием системы?
Мы рассмотрели разделы 1-7 в части I. 8-9 в части II, 10-12 в части III, 13 - 16 в части IV Пришло время Pub/Sub, Event-Driven Архитектуры, ревью распределенных систем. Поехали!
Realtime Pub/Sub
Когда поставщик данных(producer) отправляет сообщение в брокер сообщений, оно остаётся в нём до тех пор, пока потребитель(consumer) не заберёт его. Как забрать сообщение из брокера?
В Pubsub, как только издатель отправляет сообщение брокеру, оно немедленно доставляется подписчикам, которые подписаны на этого брокера с помощью push модели. Здесь подписчики не выполняют вызов API или что-то подобное, чтобы получить сообщение. Сообщение автоматически доставляется подписчику в режиме реального времени.

Здесь следует отметить, что сообщения не сохраняются. Как только брокер получает сообщение, он отправляет его всем подписчикам этого канала и завершает работу нмчего не сохраняя.
Пример брокера Pubsub в реальном времени: Redis
Где использовать Pubsub в реальном времени?
Один из вариантов использования — создание приложения для общения в реальном времени. Для коммуникации приложений мы используем Websocket. Но в горизонтально масштабируемой среде может быть много серверов, подключенных к разным клиентам, как показано на рисунке ниже.

Если клиент-1 хочет отправить сообщение клиенту-3, он не может отправить его напрямую, потому что клиент-3 не подключен к серверу-1, поэтому сервер-1 не сможет передать сообщение клиенту-3 после получения сообщения от клиента-1.
Вам нужно каким-то образом передать сообщение от клиента-1 серверу-2, чтобы сервер-2 мог отправить это сообщение клиенту-3. Вы можете сделать это с помощью Redis Pubsub. Посмотрите на рисунок ниже.

Event-Driven Architecture
Введение
Прежде чем изучать Event-Driven Architecture(EDA)/Событийно-ориентированную архитектуру, давайте разберёмся, зачем она нам нужна.
Предположим, мы создаём платформу для электронной коммерции.
Когда пользователь покупает товар, он отправляет запрос в сервис обработки заказов.
Сервис проверяет товар и вызывает платежный шлюз для оплаты(и может быть бронирует товар)
Если оплата прошла успешно, пользователь перенаправляется на страницу с подтверждением.
Прежде чем пользователь будет перенаправлен на страницу с подтверждением, вызываются два метода: служба инвентаризации для обновления количества товаров на складе и служба уведомлений для отправки пользователю электронного письма с подтверждением заказа.

Вы видите, что службы инвентаризации и уведомлений не имеют никакого отношения к странице успеха. Они не отправляют клиенту никаких ответов. Поэтому клиенту не нужно ждать их ответа. Это некритичные задачи, которые можно выполнять асинхронно.
Что мы можем сделать, так это передать задачу по отправке информации об успешном выполнении заказа в брокер сообщений и позволить службе инвентаризации и уведомлений обрабатывать её асинхронно, не заставляя клиента ждать. Это архитектура, управляемая событиями, в которой производитель отправляет сообщение (называемое событием) в брокер сообщений и забывает о нём. Теперь обрабатывать его должен потребитель.

Зачем использовать EDA?
Decoupling/Уменьшение связанности: поставщикам данных не нужно знать о потребителях. В приведенном выше примере без использования EDA связь была очень тесной. Предположим, что служба инвентаризации выйдет из строя, тогда это может повлиять на всю систему, потому что служба заказов напрямую вызывала ее(прим. переводчика - похожий кейс с сильной связанностью описан у СДЕКа - когда они восстанавливались после архиватора). Но после использования EDA служба заказов никак не связана со службой инвентаризации.
Отказоустойчивость: Отказ одного компонента не блокирует другие.
Масштабируемость: Каждая служба может масштабироваться горизонтально, не влияя друг на друга.
Паттерны
Существует 4 типа паттернов EDA:
Уведомление о событии
Передача состояния, осуществляемая с помощью события
Event Sourcing
Event Sourcing с помощью CQRS
Сконцентрируемся на первых двух паттернах.
Простое уведомление о событии
Приведённый выше пример — это простое уведомление о событии. В этом случае producer/инициатор события уведомляет только о том, что произошло событие. При необходимости потребителям нужно получить дополнительные сведения.
В этом случае производитель отправляет в брокер сообщений только краткую информацию. Он может просто отправить идентификатор заказа. Потребитель в свою очередь получит его из брокера сообщений. Если потребителю нужна дополнительная информация по этому идентификатору заказа, он может запросить её в базе данных.

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

Преимущество: снижение задержки, поскольку пользователям не нужно совершать дополнительные сетевые запросы для получения дополнительной информации.
Недостаток: события становятся больше по размеру, что может увеличить расходы на хранение данных и пропускную способность.
Прим. переводчика - пример с celery для python разработчиков
Здесь упомянуты оба подхода - с передачей лишь id vs передачей всего состояния. Как думаете, где нагрузка на систему была больше?
Распределенные системы
Что такое распределенная система?
Предположим, мы что-то вычисляем. Например, сумму всех простых чисел от 0 до 10000. Тогда мы можем просто запустить цикл for и вычислить её. Но если нам нужно посчитать простые числа от 0 до 10¹⁰⁰, то выполнить это на одном компьютере невозможно. Мы достигнем предела, и наш компьютер зависнет. Если вы не верите, запустите цикл от 0 до 10¹⁰⁰ на своём ноутбуке.
Здесь нам поможет распределённая система. Она использует несколько компьютеров для выполнения задачи. Мы будем использовать 10 компьютеров для параллельного выполнения задачи, разделив её. Это означает, что один компьютер будет вычислять от (0 до 10¹⁰), другой — от (10¹⁰ + 1 до 10²⁰) и так далее. Наконец, объедините результаты и верните их клиенту.
Проще говоря, распределённая система означает, что работа выполняется несколькими машинами, а не одной.
Шардинг — это тоже пример распределённой системы, в которой данные не помещаются на одном компьютере, поэтому мы разбиваем их на части и размещаем на нескольких компьютерах.
Горизонтальное масштабирование также является примером распределённой системы, поскольку запросы обрабатываются несколькими серверами.
Самая сложная часть распределённой системы — это то, как несколько компьютеров взаимодействуют друг с другом для выполнения задачи. И как использовать эти компьютеры и равномерно распределять задачи между ними.
С точки зрения клиента, в распределённой системе клиенты должны чувствовать себя так, будто они отправляют запросы на один компьютер (а не на несколько). Задача системы — принимать запросы от клиента, обрабатывать их распределённым образом и возвращать результат.

Теорема CAP, которую мы изучили ранее, — это фундаментальная теорема, применимая ко всем распределённым системам.
Обычно большинство распределённых систем реализуются следующим образом: один сервер выбирается в качестве ведущего/лидера, а все остальные — в качестве ведомых. Ведущий принимает запросы от клиента и распределяет работу между ведомыми. Ведомые выполняют свою работу и возвращают результат ведущему. Ведущий объединяет результаты всех ведомых и возвращает их клиенту.

Системе необходимо решить, какой сервер должен быть ведущим в двух случаях:
В первый раз, когда запускается вся система, ей нужно решить, какой сервер должен быть ведущим.
Когда ведущий по какой-либо причине выбывает из игры, кто-то из ведомых узлов должен стать лидером.
Как вы решаете, какой сервер станет лидером?
Для этого существует несколько алгоритмов выбора лидера, таких как:
Алгоритм LCR: Его временная сложность равна O(N2)
Алгоритм HS: Сложность O(NlogN)
Алгоритм хулигана: Сложность O(N)
Протокол обмена сплетнями: Сложность O(logN)
Мы не будем обсуждать здесь алгоритмы выбора лидера, потому что это выходит за рамки проектирования системы. Распределённые системы сами по себе — очень обширная тема. Но этой теории о распределённых системах достаточно с точки зрения проектирования системы.
Просто представьте алгоритм выбора лидера как чёрный ящик. Он сделает один из серверов лидером. И всякий раз, когда лидер выйдет из строя или отключится, вся система (то есть большинство серверов) автоматически обнаружит это и снова запустят алгоритм выбора лидера, чтобы сделать лидером какой-то из серверов.
Примеры распределённых баз данных: Apache Cassandra, AWS DynamoDB, MongoDB, Google Spanner, Redis и т. д. Эти базы данных используют сегментирование и горизонтальное масштабирование для хранения больших объёмов данных и эффективного выполнения запросов.
Упражнение для вас: изучите несколько алгоритмов выбора лидера и закодируйте их на своём любимом языке программирования.
Автоматически восстанавливаемая система с использованием выборов лидера
Предположим, вы создали горизонтально масштабируемую систему, в которой несколько серверов находятся за балансировщиком нагрузки для обработки запросов. Вы хотите, чтобы при любых обстоятельствах для обработки запроса всегда было задействовано не менее 4 серверов.
Для этого вам всегда нужно следить за сервером своими глазами, чтобы в случае сбоя или отключения сервера вы могли перезапустить его вручную. Вам не кажется, что это утомительная и скучная задача? В этом разделе мы рассмотрим, как автоматизировать этот процесс. Всякий раз, когда какой-либо сервер отключается, система автоматически обнаруживает это и перезапускает его без нашего участия.
Для этого нам нужен оркестратор. Его задача — постоянно следить за сервером, и всякий раз, когда какой-либо сервер выходит из строя, задача оркестратора — перезапустить его.

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

В этой системе нам не нужно вмешательство человека. Система будет восстанавливаться автоматически.
Всякий раз, когда какой-либо сервер выходит из строя, рабочий-оркестратор перезапускает его. Когда какой-либо из рабочих-оркестраторов выходит из строя, лидер перезапускает его. Когда лидер-оркестратор выходит из строя, один из рабочих-оркестраторов становится лидером с помощью алгоритма выбора лидера.
Инструменты для обработки Больших Данных
Пример инструмента для обработки больших данных: Apache Spark
Если у вас очень большой объём данных, то для их обработки мы используем инструменты для работы с большими данными.
Инструменты обработки больших данных используют метод распределенной системы.
Обработка больших объёмов данных на одном компьютере невозможна. Он будет перегружен или станет работать слишком медленно. В этом случае мы используем распределённую систему.
В этом случае у нас есть координатор и исполнители. Клиент отправляет запрос координатору, и его задача — разделить большой набор данных на более мелкие, передать их исполнителю, получить результат от исполнителя, объединить его и вернуть результат. Каждый исполнитель вычисляет небольшой набор данных, переданный координатором.

В этой системе координатор должен позаботиться о нескольких вещах:
Если какой-либо рабочий компьютер выйдет из строя, переместите его данные на другую машину для обработки.
Восстановление: Это означает, что если какой-либо рабочий компьютер выйдет из строя, то перезапустите его.
Возьмите индивидуальные результаты каждого работника и объедините их, чтобы получить итоговый результат.
Масштабирование и перераспределение данных
Ведение журнала
Когда следует использовать распределенную систему (или инструмент для обработки больших данных)?
Когда один инстанс не справляется с задачами и обработкой больших объёмов данных.
Вот некоторые варианты использования:
Обучающие модели машинного обучения
Анализируйте социальные сети или системы рекомендаций
Сбор больших объёмов данных из нескольких источников и их загрузка в любой хранилище.
Построить такую систему очень сложно, так как вам нужно учесть несколько моментов. Это очень распространённая проблема, поэтому существует несколько инструментов с открытым исходным кодом (например, Spark и Flink), которые создают инфраструктуру из координаторов и исполнителей.
Нас интересует только бизнес-логика, например, написание кода для обучения моделей машинного обучения. Инструменты для работы с большими данными, такие как Apache Spark, предоставляют нам инфраструктуру распределённой системы. Мы используем её как «чёрный ящик» и пишем бизнес-логику только на Python, Java или Scala в виде заданий в Spark, а Spark обрабатывает их распределённым образом.
Здесь мы не будем подробно изучать Apache Spark, потому что разработка приложений, интенсивно использующих данные, сама по себе является обширной областью. Вы познакомитесь с ней, только если будете работать в компании, которая имеет дело с большими данными. Для собеседований по проектированию систем этой теории о больших данных будет достаточно.
На этом пятая часть перевода подошла к концу. В следующий раз в финальной части поговорим про:
Уровни консистентности данных(eventual, strong)
Избыточность
Прокси сервер
Меня зовут Невзоров Владимир. Работаю старшим backend разработчиком на HighLoad проекте с порядком пиковой нагрузки в миллион rps. Приветствую) Веду телеграмм канал по Архитектуре, System Design, Highload бэкэнду.
На канале провожу архитектурные каты, публикую полезные материалы, делюсь опытом. Сейчас с участниками канала разбираем книгу Мартина Клеппмана «Высоконагруженные приложения» на стримах (youtube-запись).
Для пополнения багажа знаний по теме заходите на мой канал System Design World <=
Успехов в дальнейшем изучение темы System Design!