Перейти к основному содержимому

5. Принципы проектирования систем (Middle+)

Middle​

Что такое проектирование систем и почему это важно?​

Ответ: Проектирование систем — это процесс определения архитектуры, компонентов, модулей, интерфейсов и данных системы для удовлетворения заданных требований. Это важно, потому что грамотно спроектированная система может эффективно масштабироваться, устойчиво обрабатывать сбои и экономично обслуживаться. Неудачное проектирование может привести к огромным финансовым потерям и масштабным отказам.

В чём разница между функциональными и нефункциональными требованиями?​

Ответ:

  • Функциональные требования: Определяют, что система должна делать. Это конкретные функции, например «пользователь может забронировать самокат» или «пользователь может просмотреть историю поездок».
  • Нефункциональные требования: Определяют, как система должна работать. Включают такие характеристики, как масштабируемость (обработка 100 тысяч запросов), доступность (99,9% uptime), надёжность и безопасность.

Объясните вертикальное и горизонтальное масштабирование.​

Ответ:

  • Вертикальное масштабирование (Scaling Up): Увеличение мощности одного сервера (например, добавление CPU, RAM или более быстрых SSD). Просто в реализации, но ограничено физическими возможностями одной машины и становится очень дорогим на верхних уровнях.
  • Горизонтальное масштабирование (Scaling Out): Добавление новых серверов в пул и распределение нагрузки между ними с помощью балансировщика нагрузки. Позволяет практически неограниченный рост и обеспечивает лучшую отказоустойчивость, так как система продолжает работать при отказе одного узла.

Каковы основные стратегии масштабирования баз данных?​

Ответ:

  • Репликация: Создание копий базы данных (Master/Slave). Обычно Master обрабатывает запись, а Slave-серверы обрабатывают запросы на чтение для распределения нагрузки.
  • Шардирование: Распределение данных по нескольким независимым серверам баз данных (шардам) на основе определённого ключа (например, User ID, геолокация). Каждый шард хранит подмножество общих данных.
  • Партиционирование: Разделение большой таблицы на меньшие, более управляемые логические части внутри одного экземпляра базы данных для повышения производительности запросов.

Что такое ключ шардирования и как выбрать подходящий?​

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

  • Равномерное распределение: Данные распределяются равномерно по шардам, избегая «горячих точек».
  • Эффективность запросов: Минимизирует необходимость запросов между шардами, сохраняя связанные данные вместе. Часто используемые ключи: User ID, Tenant ID или хеш уникального атрибута.

Каковы лучшие практики проектирования REST API?​

Ответ: Используйте существительные во множественном числе для ресурсов (например, /users), стандартные HTTP-методы (GET, POST, PUT, DELETE), версионирование (например, /v1/...) и соответствующие HTTP-коды статуса для отражения результата запроса.

Какова взаимосвязь между задержкой, пропускной способностью и параллелизмом?​

Ответ: Они связаны законом Литтла: Concurrency = Throughput × Latency.

  • Задержка (Latency): Время обработки одного запроса.
  • Пропускная способность (Throughput): Количество запросов, которые система может обработать в единицу времени.
  • Параллелизм (Concurrency): Количество запросов, обрабатываемых одновременно. Если увеличить количество параллельных запросов (concurrency) без увеличения пропускной способности системы, задержка автоматически возрастёт, что приведёт к узкому месту.

Что такое балансировщик нагрузки и каковы распространённые алгоритмы балансировки?​

Ответ: Балансировщик нагрузки (например, Nginx, HAProxy, AWS ELB) — это компонент, распределяющий входящий сетевой трафик между несколькими серверами, чтобы ни один из них не оказался перегруженным. Распространённые алгоритмы:

  • Round Robin: Запросы распределяются последовательно.
  • Least Connections: Трафик направляется на сервер с наименьшим количеством активных соединений.
  • IP Hash: IP-адрес клиента определяет, какой сервер получит запрос (полезно для привязки сессий).
  • Latency-Aware: Балансировщик отслеживает историю времени ответа и избегает «медленных» узлов.

Что такое паттерны Circuit Breaker и Back Pressure?​

Ответ:

  • Circuit Breaker: Отслеживает вызовы к отказывающему сервису. Если частота ошибок превышает порог, «цепь размыкается», и система прекращает вызовы к сервису, возвращая быструю ошибку-заглушку вместо ожидания таймаутов. Это предотвращает каскадные отказы.
  • Back Pressure: Механизм, при котором система «отталкивает» высокий поток входящих запросов, которые не может обработать. Вместо бесконечного роста очередей (что увеличивает задержку) она отклоняет новые запросы (например, HTTP 429) или просит клиента замедлиться.

Что такое идемпотентность в проектировании API и почему она важна?​

Ответ: Идемпотентная операция — это операция, которая даёт одинаковый результат независимо от количества выполнений. Например, PUT и DELETE должны быть идемпотентными, тогда как POST обычно таковым не является. В распределённых системах идемпотентность критически важна для обработки сетевых сбоев. Если клиент не получает подтверждение из-за таймаута, он может повторить запрос. Если API идемпотентен, повторный запрос не вызовет нежелательных побочных эффектов (например, двойного списания средств с клиента).


Senior​

Сравните 2PC, 3PC, TCC и паттерн Saga.​

Ответ:

  • 2PC (Two-Phase Commit): Синхронный протокол для распределённых транзакций. Обеспечивает строгую согласованность, но является блокирующим и может стать узким местом производительности.
  • 3PC (Three-Phase Commit): Расширение 2PC, добавляющее фазу «Pre-commit» для уменьшения блокировки в случае отказа координатора. На практике используется редко.
  • TCC (Try-Confirm-Cancel): Паттерн уровня приложения. Вы резервируете ресурсы (Try), затем либо подтверждаете (Confirm), либо освобождаете (Cancel). Гибкий, но сложный в реализации.
  • Паттерн Saga: Последовательность локальных транзакций, где каждый шаг имеет соответствующую «компенсирующую транзакцию» для отмены изменений при сбое. Хорошо масштабируется и широко используется в микросервисах, но обеспечивает только конечную согласованность (eventual consistency).

Как уменьшить хвостовую задержку (Tail Latency) в распределённой системе?​

Ответ: Хвостовая задержка (P99+) может быть уменьшена с помощью нескольких техник:

  • Hedged Requests (Tied Requests): Отправка одного и того же запроса нескольким репликам и использование результата от первой ответившей.
  • Request Coalescing: Объединение идентичных параллельных запросов в один вызов к бэкенду.
  • Background Jittering: Добавление случайных задержек к запланированным задачам для предотвращения проблемы «громового стада» (thundering herd).
  • Deadline Propagation: Передача максимально допустимого времени выполнения запроса всем подсервисам, чтобы они могли прекратить обработку, если дедлайн уже истёк.
  • Latency-Aware Balancing: Избегание узлов, которые в данный момент работают медленно.

Сравните различные стратегии маршрутизации шардирования: на уровне приложения, через прокси и через координатор.​

Ответ:

  • Маршрутизация на уровне приложения: Код приложения содержит логику определения нужного шарда. Это быстро (без дополнительного сетевого перехода), но сложно в поддержке, так как логика шардирования дублируется между сервисами.
  • Маршрутизация через прокси: Отдельный слой (например, Vitess, ProxySQL) располагается между приложением и базой данных. Он берёт на себя маршрутизацию, делая шардирование прозрачным для приложения. Добавляет небольшой сетевой переход, но централизует управление.
  • Маршрутизация через координатор: Отдельный сервис (например, Zookeeper или пользовательский сервис метаданных) хранит информацию о расположении шардов. Клиенты обращаются к координатору для определения нужного шарда. Надёжный, но сложный в реализации подход.

Объясните разницу между конечной и строгой согласованностью.​

Ответ:

  • Конечная согласованность (Eventual Consistency): Реплики в итоге придут к одному значению, но возможна задержка, когда разные пользователи видят разные данные. Обеспечивает высокую доступность и низкую задержку. Подходит для комментариев, лайков или каталогов товаров.
  • Строгая согласованность (Strong Consistency): Гарантирует, что после успешной записи любое последующее чтение вернёт новое значение. Необходима для финансовых транзакций, управления складскими остатками или систем бронирования.
  • Линеаризуемость (Linearizability): Более строгая форма согласованности, при которой операции выглядят так, как будто выполняются мгновенно и в порядке их поступления.

Что такое хвостовая задержка (Tail Latency) и почему она важна в распределённых системах?​

Ответ: Хвостовая задержка — это высокая задержка, которую испытывает небольшой процент запросов (например, 99,9-й перцентиль). В крупномасштабной системе, где один пользовательский запрос может порождать сотни подзапросов к различным микросервисам, высокая хвостовая задержка даже в одном сервисе может замедлить весь ответ для пользователя, что делает её критически важной метрикой для надёжности системы.

Подробное руководство по принципам проектирования систем