[Перевод] System Design для начинающих: всё, что вам нужно. Часть 4

eeb0ed2ce5d36db28c11e21cad5280b6.png

Вам не нужно изучать какую‑либо теорию, кроме этой статьи, чтобы начать собеседоваться. После прочтения смело приступайте к решению типовых System Design задач.

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

Содержание

  1. Зачем изучать проектирование систем?

  2. Что такое сервер?

  3. Задержка и пропускная способность

  4. Масштабирование и его типы
    + Вертикальное
    + Горизонтальное

  5. Автоматическое масштабирование

  6. Оценка на коленке

  7. Теорема CAP

  8. Масштабирование базы данных
    + Индексирование
    + Партиционирование
    + Архитектура «master-slave»
    + Multi-master
    + Шардирование
    + Недостатки Шардирования

  9. SQL и NoSQL СУБД. Когда какую базу данных использовать?
    + SQL СУБД
    + NoSQL СУБД
    + Особенности масштабирования
    + Когда использовать ту или иную базу данных?

  10. Микросервисы
    + Что такое монолит и микросервис?
    + Почему мы разбиваем наше приложение на микросервисы?
    + Когда следует использовать микросервисы?
    + Как клиенты отправляют запросы?

  11. Load Balancer
    + Зачем нам нужен балансировщик нагрузки?
    + Алгоритмы балансировщика нагрузки

  12. Кэширование
    + Введение в кэширование
    + Преимущества кэширования
    + Типы кэшей
    + Подробное описание Redis

  13. Хранилище BLOB-объектов
    + Что такое BLOB и зачем нам нужно хранилище BLOB?
    + AWS S3

  14. Сеть доставки контента (CDN)
    + Знакомство с CDN
    + Как работает CDN?
    + Ключевые понятия в CDN

  15. Message Broker
    + Асинхронное программирование
    + Зачем мы добавили посредника для передачи сообщений?
    + Queue
    + Stream
    + Кейсы использования

  16. Apache Kafka Deep dive
    + Когда использовать Kafka
    + Внутреннее устройство Kafka

  17. Pub/Sub

  18. Event-Driven Архитектура
    + Введение
    + Зачем использовать EDA?
    + Система нотификаций с id
    + Система с передачей всего состояния

  19. Distributed Systems

  20. Leader Election

  21. Big Data Tools

  22. Consistency Deep Dive
    + Когда использовать Strong Consistency, Eventual Consistency
    + Как добиться Strong, Eventual Consistency

  23. Consistent Hashing

  24. Data Redundancy and Data Recovery
    + Зачем мы делаем резервные копии баз данных?
    + Различные способы резервного копирования данных
    + Непрерывное резервное копирование

  25. Proxy
    + Что такое прокси сервер?
    + Прямой и обратный прокси сервер
    + Создание собственного обратного прокси-сервера

  26. Как решить любую проблему, связанную с проектированием системы?

Мы рассмотрели разделы 1–7 в части I. 8–9 в части II, 10–12 в части III. Пришло время Blob Storage, CDN, Брокеров сообщений. Поехали!

Хранилище больших двоичных объектов

Что такое большой двоичный объект и зачем нам нужно хранилище больших двоичных объектов?

В базах данных текст и числа хранятся в том виде, в котором они есть. Но подумайте о чём-то вроде файла, напримерmp4, png, jpeg, pdf и т. д. Такие файлы нельзя просто хранить в строках и столбцах.

Эти файлы можно представить в виде набора нулей и единиц. Такое двоичное представление называется BLOB (большой двоичный объект). Хранить данные в формате mp4 невозможно, но хранить BLOB-объект легко, потому что это просто набор нулей и единиц.

Размер этого BLOB-объекта может быть очень большим, например, одно видео в формате MP4 может занимать 1 ГБ. Если вы храните его в базах данных, таких как MySQL или MongoDB, ваши запросы будут выполняться слишком медленно. 

Кроме того, при хранении такого большого объёма данных вам нужно будет позаботиться о:

  • Масштабировании

  • Резервном копировании

  • Доступности

Вот несколько причин, по которым мы не храним BLOB-объекты в базе данных. Вместо этого мы сохраняем их в хранилище BLOB-объектов, которое является управляемым сервисом (управляемый сервис означает, что его масштабирование, безопасность и т. д. обеспечиваются такими компаниями, как Amazon, а мы используем его как «чёрный ящик»).

Пример хранилища BLOB-объектов:  AWS S3, Cloudflare R2.

AWS S3

Это лишь введение в S3. Если вы хотите изучить его подробно с примерами и реализацией, то можете следить за моей серией статей об AWS.

S3 используется для хранения файлов (данных в виде больших двоичных объектов), таких как mp4, png, jpeg, pdf, html или любые другие файлы, которые вы можете себе представить.

Вы можете представить S3 как Google Диск, где вы храните все свои файлы.

Самое лучшее в S3 — это его низкая стоимость. Хранение 1 ГБ данных в S3 намного дешевле, чем в RDS (RDS — это сервис баз данных AWS, предоставляющий различные базы данных, такие как PostgreSQL, MySQL и т. д.).

Особенности S3:

  • Масштабируемость: автоматическое масштабирование для обработки больших объемов данных.

  • Долговечность: S3 обеспечивает 99,999999999% (11 9) долговечности.

  • Доступность: Высокая доступность с различными соглашениями об уровне обслуживания.

  • Экономическая эффективность: модель с оплатой по мере поступления.

  • Безопасность: шифрование при хранении и передаче данных, политики сегментов и разрешения IAM.

  • Контроль доступа: детальный контроль с помощью политик, списков управления доступом и предварительно подписанных URL-адресов.

Я не буду здесь подробно описывать S3.

Упражнение для вас:  ваша задача — создать приложение на выбранном вами языке (например, на Node.js, Spring Boot, Fast API и т. д.) с функцией загрузки изображений. Когда пользователь загружает изображение, оно сохраняется на S3. Выполните это упражнение в коде, и вы узнаете много нового об S3, а не просто прочитаете теорию.

Сеть доставки контента (CDN)

Введение CDN

CDN — это самый простой способ масштабирования статических файлов (таких как mp4, jpeg, pdf, png и т.д.). Предположим, что файлы хранятся на сервере S3, который расположен в регионе Индия. Когда пользователи из США, Австралии или далеко за пределами Индии запрашивают эти файлы, их обработка займет много времени. Запрос, отправленный из ближайшего местоположения, всегда выполняется быстрее, чем на большом расстоянии. Теперь мы хотим, чтобы эти файлы хранились в ближайшем к ним месте с низкой задержкой. Это мы делаем с помощью CDNs.

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

Примеры CDN:  AWS CloudFront, CDN Cloudflare

Как работает CDN?

Когда пользователь запрашивает контент, запрос отправляется на ближайший сервер CDN (edge server). Если контент есть присутствует, он возвращается. Если его нет, он загружается с исходного сервера (оригинального S3, на котором хранится контент), затем кэшируется на сервере и, наконец, возвращается пользователю. При последующих запросах, если контент есть на edge сервере, он возвращается оттуда.

7822ba2fe3aaf8c9d8be31f651b626d1.png

Ключевые концепции CDNs

1. Пограничные серверы (edge servers)

  • Это географически распределенные серверы, на которых провайдеры CDN кэшируют ваш контент.

  • Пользователи перенаправляются на ближайший пограничный сервер для более быстрой доставки.

2. Исходный сервер (origin server)

  • Это ваш основной веб-сервер (например, AWS S3).

  • CDN загружает контент отсюда, если он ещё не кэширован на пограничном сервере.

3. Кэширование

  • Сети доставки контента хранят копии вашего статического контента (например, изображений, видео, HTML) на своих периферийных серверах.

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

4. TTL

  • Продолжительность, в течение которой файл кэшируется на пограничном сервере CDN.

  • Пример: у изображения может быть TTL 24 часа, то есть оно будет кэшироваться в течение 24 часов, прежде чем обновится.

5. Геоданные

  • Сети доставки контента используют GeoDNS для перенаправления пользователей на ближайший пограничный сервер в зависимости от их географического местоположения.

Брокер сообщений

Асинхронное программирование

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

52c1f7edeaffc5f972b1c2b381199f21.png

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

77a96a6db268f15179d1657e5cbae0d3.png

Здесь мы не передаём задачу напрямую работнику, а используем посредника — брокера сообщений.

c0f7b9d6bfcdf5141b0bc2d673b145c5.png

Этот брокер сообщений действует как промежуточная очередь. Сервер помещает задачу в очередь в виде сообщения, а работник извлекает её из очереди и обрабатывает. После завершения обработки работник может удалить сообщение с задачей из очереди.

Сервис, который помещает сообщение в очередь, называется producer/поставщиком данных, а сервис, который извлекает и обрабатывает сообщение, называется consumer/ потребителем.

Почему мы поместили посредника сообщений между ними?

  1. Это обеспечивает надёжность. Предположим, поставщик выходит из строя. Тем не менее, воркеры/worker/потребители могут продолжать работать без каких-либо проблем.

  2. Также предусмотрена функция повторной отправки. Если потребитель не сможет обработать сообщение, он может повторить попытку через некоторое время, поскольку сообщение всё ещё находится в брокере сообщений.

  3. Это делает систему независимой. Поставщики и потребители могут выполнять задачи в своём собственном темпе и не зависят друг от друга.

Брокеры сообщений бывают двух типов:

  1. Очереди сообщений (пример: RabbitMQ, AWS SQS)

  2. Потоки сообщений (пример: Apache Kafka, AWS Kinesis)

Очередь сообщений

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

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

Предположим, у вас есть очередь сообщений, в которую вы помещаете метаданные видео и позволяете получателю извлекать видео из метаданных и преобразовывать его в различные форматы (480p, 720p и т. д.).

fd73b9bc246d40ab985a34041b7b0c60.png

После того как служба транскодирования завершит перекодировку видео, она удаляет сообщение из очереди. Различные очереди сообщений, такие как RabbitMQ, AWS SQS и т. д., предоставляют API для удаления сообщений из очереди.

Если одного сервера транскодирования видео недостаточно, что мы будем делать?
Ответ — мы будем масштабировать его по горизонтали. Любой свободный сервер может получить сообщение из очереди сообщений, обработать его и удалить из очереди после обработки.

04db3a080a4b6e19af522e8e0049657c.png

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

Зачем нам нужен поток сообщений?

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

Поток сообщений

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

Когда использовать поток сообщений?

Когда вы хотите: «Написать одному и прочитать многим».

d5eba07458b8adc6af62cbb9fff22b66.png

Вам может быть интересно, как сервис видеоконвертора удалит сообщение после обработки, если сервис генератора субтитров получит это сообщение. А если сервис видеоконвертора не удалит сообщение, то он может обработать его несколько раз. Но так не происходит.

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

Я надеюсь, что разница между очередями сообщений и потоками сообщений понятна.

Когда мы используем брокеры сообщений?

У вас есть два микросервиса. Два наиболее распространённых способа взаимодействия между ними — это API-интерфейсы Rest и брокер сообщений.

0d31e98806d2af9813847d7dd9836399.png

Мы используем брокеры сообщений, когда:

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

  • Задача требует длительного вычисления. Например — перекодировка видео, создание PDF-файла и т. д.

Глубокое Погружение Apache Kafka

Когда использовать Kafka?

Kafka используется в качестве потока сообщений.

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

Пример с Uber

Предположим, что Uber отслеживает местоположение водителя. Каждые 2 секунды получайте данные о местоположении водителя и вставляйте их. Если водителей тысячи и мы вставляем тысячи данных в базу данных каждые 2 секунды, то база данных может зависнуть, потому что пропускная способность базы данных (количество операций в секунду) низкая. Мы можем каждые 2 секунды отправлять эти данные в Kafka, потому что пропускная способность Kafka очень высокая. Каждые 10 минут потребитель будет получать эти большие объёмы данных из Kafka и записывать их в базу данных. Таким образом, мы выполняем операции в БД каждые 10 минут, а не каждые 2 секунды.

Внутренности Кафки

  • Производитель/producer:  публикует сообщения в Kafka. Что касается отправки электронных писем, производитель может отправить {«электронное письмо», «сообщение»} в Kafka.

  • Потребитель/consumer:  подписывается на топики Kafka и обрабатывает поток сообщений.

  • Брокер:  сервер Kafka, который хранит топики и управляет ими.

  • Топик/Topic:  название категории/канала, в который публикуются записи.
    sendEmail может быть топиком.
    writeLocationToDB может быть топиком.

  • Давайте возьмём в качестве аналогии базу данных:
    Брокер = Сервер базы данных
    Топики = Таблицы

  • Партиция/partition:  каждый топик разделен на партиции для обеспечения параллелизма. Разделение на партиции аналогично сегментированию в таблицах баз данных. На каком основании мы разделяем?  Для этого мы должны сами принять решение и закодировать его. Предположим, что для нашего топика sendNotification мы разделяем по местоположению. Данные из Европы попадают в партицию 1, а данные из Азии — в партицию 2.

  • Группы потребителей:  когда мы создаём потребителя, который подписывается на топик, мы должны назначить ему группу. Каждый потребитель в группе выполняет один тип обработки из подмножества партиций.
    Пример — для обработки видео, как мы видели ранее, у нас может быть две группы потребителей. Одна группа потребителей предназначена для перекодирования видео, а другая — для создания субтитров.

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

Партиции:  [Партиция-0, Партиция-1, Партиция-2, Партиция-3]

Потребители:  [Потребитель-1, Потребитель-2, Потребитель-3]

В этом сценарии Kafka выполняет ребалансировку для распределения партиций между потребителями. Эта ребалансировка Kafka выполняется автоматически. Нам не нужно писать для этого код.

Потребитель-1 назначен в партицию-0, Потребитель-2 — в партицию-1, Потребитель-3 — в партицию-2 и Потребитель-4 — в партицию-3.

3db43a9a8125e41fe08ae689c7b92aaa.png

Если количество потребителей группы, подписанных на топик, превышает количество партиций этого топика, то каждый потребитель обрабатывает 1 партицию, а остальные потребители ничего не делают.

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

0174f058e7881a62ce3ee0cc451d2cf8.png

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

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

0c56e3290cdc3a89a870977020990f98.png

Вот и все, что касается теоретической части Кафки.

Упражнение для вас:  

Установить Kafka локально на вашем ноутбуке и написать любое приложение, использующее Kafka, на NodeJS (или любом другом фреймворке), чтобы лучше разобраться. Я не показываю здесь код, но рассказал о необходимой теории. Написать код несложно, если вы понимаете теорию.

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

Меня зовут Невзоров Владимир. Работаю старшим backend разработчиком на HighLoad проекте с порядком пиковой нагрузки в миллион rps. Приветствую) Веду телеграмм канал по Архитектуре, System Design, Highload бэкэнду.

На канале провожу архитектурные каты, публикую полезные материалы, делюсь опытом. Сейчас с участниками канала разбираем книгу Мартина Клеппмана «Высоконагруженные приложения» на стримах (youtube-запись).

Для пополнения багажа знаний по теме заходите на мой канал System Design World <=

Материал для подготовки к System Design интервью в виде чек листов на моём boosty. Смотреть.

Успехов в дальнейшем изучение темы System Design!

© Habrahabr.ru