Перейти к содержанию

Typical web-application inner architecture

The First Nine Guide. Блок 3


Illustration

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


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

Я пытался найти готовую литературу, которая бы объясняла веб-приложения независимо от рантайма и языка, но не нашел. Поэтому собрал свою интерпретацию, чтобы решить две проблемы:

  1. Многие инженеры не хотят погружаться в глубины работы приложения: кажется, что это невероятно сложно или непонятно с чего начать.
  2. Встречается и обратное - слишком упрощенный взгляд: "есть коробочка, вход, выход и бизнес-логика посередине". Это размывает понимание того, какой ресурс потребляет та или иная функция.

Поехали - посмотрим и ресурсы, и кишки.


Слои HTTP-запроса

Начнем с понимания того, через какие слои проходит HTTP-запрос. Для простоты выделим 4 слоя внутри приложения.

NETWORK LAYER

Суть: превращает HTTP-запрос в структурированные данные для бизнес-логики.

Illustration

Что тут происходит:

  • socket listen на порту (8080, 443)
  • accept connections - принимаем новые подключения
  • TLS handshake - расшифровка HTTPS трафика
  • HTTP parsing - разбор заголовков и тела запроса

Часто тут думают, что это только IO-bound. На больших телах с HTTPS CPU тоже начинает болеть.

REQUEST PROCESSING

Суть: превращает HTTP-запрос в вызов функции и маршрутизирует к нужному обработчику.

Illustration

Что тут происходит:

  • deserialize (JSON/XML/Proto) - разбор тела запроса в объекты
  • validation & auth - проверка токенов, прав доступа, валидация данных
  • rate limiting - контроль частоты запросов
  • request routing - определение handler по URL

Про рейты важно сказать: memory-bound - это чаще локальные лимиты. Распределенные лимиты обычно в Redis и это уже IO.

BUSINESS LOGIC

Суть: выполняет основную логику приложения - то, ради чего существует сервис.

Illustration

Что тут происходит:

  • domain operations - основные алгоритмы и бизнес-правила
  • calculations - вычисления и обработка данных
  • external API calls - обращения к сторонним сервисам
  • database queries - запросы к PostgreSQL/MySQL/MongoDB
  • cache operations - чтение/запись Redis/Memcached

Этот слой получает приз SRE симпатий как самый проблемный. Тут и медленные SQL, и блокировки, и внешние API без таймаутов, и тяжелые вычисления O(n!).

RESPONSE FORMATION

Суть: упаковывает результат обратно в HTTP-ответ и отправляет клиенту.

Illustration

Что тут происходит:

  • сериализация (JSON/XML/Proto)
  • compression (gzip/brotli) - сжатие больших ответов
  • encryption - шифрование для HTTPS
  • send to socket - отправка байтов по сети

Проблемы те же, что и у первого слоя.

Полная картина

Illustration


Внутренняя архитектура

Теперь, когда у нас есть представление о слоях, попробуем нарисовать это как абстрактную архитектуру, будто у нас микросервисы.

Illustration

Знакомимся с объектами. Их может быть больше или меньше в реальности. Тут главное подход: выносим в отдельную сущность все, что имеет независимый пул воркеров или любую работу за пределами приложения.

Список:

  • HTTP Ingress - входная точка, первичная маршрутизация, load balancing между инстансами.
  • Dispatcher - распределяет запросы между воркерами, управляет очередями и приоритетами.
  • Worker Execution - место выполнения бизнес-логики, контроллеры и сервисы.
  • Runtime Overhead - GC, планировщики, thread scheduling. Название нарочито жесткое, чтобы подчеркнуть цену рантайма.
  • Cache Access - in-memory кеши и внешние кеши (Redis/Memcached).
  • Scripting & Serialization - JSON/XML/Protobuf сериализация, template engines, трансформации данных.
  • Logging & Instrumentation - структурированное логирование и трейсинг.
  • External Interaction - базы, очереди, HTTP API, микросервисы.

Почему это важно? Простой пример с таймаутами:

  • В бизнес-логике делаем SQL-запрос в БД. У базы свой пул, например Hikari.
  • Пулы к БД заняты, воркер висит и ждет, когда освободится подключение.
  • В итоге копятся горутины или забивается пул воркеров.

Да, это решается таймаутом в пуле, но чем более реактивный рантайм, тем чаще это упускают.

Мешочек с советами

Работая с внутренней архитектурой, проще соблюдать паттерны отказоустойчивости:

  • таймауты и TTL на внутренних очередях
  • bulkhead и разделение пулов внутри приложения (например, отдельные для CPU и IO)
  • fallback механики, вплоть до внутренних circuit breaker

Про паттерны будет много отдельных статей.


На этом пока все.

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

В предыдущей серии - разбирались с тем, какие есть модели рантайма.

Подписывайся на канал @r9yo11yp9e - будем искать девятки вместе.