awesome-everything EN
↑ Обратно к восхождению

Деплой и инфра

Балансировка L4 vs L7: что каждый уровень видит и чего не видит

Суть L4-балансировщик маршрутизирует по IP и порту, не читая payload — быстро, но слепо к URL. L7-балансировщик терминирует соединение и читает HTTP, давая маршрутизацию по пути и retry ценой CPU. Выберешь не тот уровень — и маршрутизация по пути просто невозможна.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на junior-высоте — поверхность
◷ 16 min

Команда пилит монолит: /api/* должен идти в новый сервис, всё остальное — в старый. У них уже крутится AWS NLB «ради производительности», поэтому они добавляют правило маршрутизации по пути — и обнаруживают, что такого правила нет. NLB не видит путь. Он форвардит сырой TCP; URL появляется только после парсинга HTTP, а L4-балансировщик HTTP никогда не парсит. Миграция встаёт, пока кто-то не поставит ALB. Урок стоил дня: выбранный уровень решает, какая маршрутизация вообще возможна.

L4 форвардит байты; L7 читает запросы

Балансировщик уровня 4 работает на транспортном уровне. Он видит TCP/UDP-соединение — source IP, destination IP, порты — и форвардит пакеты на бэкенд, ни разу не декодируя содержимое. По сути это быстрый, соединение-знающий NAT. Так как payload он не парсит, он протокол-агностичен (балансирует любой TCP- или UDP-сервис, а не только HTTP) и крайне дёшев на пакет — AWS NLB держит миллионы запросов в секунду с накладными в микросекунды на пакет и выдаёт статические IP.

Балансировщик уровня 7 работает на прикладном уровне. Он терминирует клиентское соединение, читает весь HTTP-запрос — метод, хост, путь, заголовки, cookie — и затем открывает собственное соединение к выбранному бэкенду. Это «терминировать и переоткрыть» — весь источник его мощи и его цены. Раз балансировщик держит распарсенный запрос, он может маршрутизировать на /api/* против /, по заголовку Host:, по cookie или по кастомному заголовку для канареек. Цена: он обязан буферизировать и парсить каждый запрос, а если в игре TLS — расшифровывать и шифровать заново. Реальный CPU на запрос, и он говорит только на тех протоколах, которые понимает (HTTP/1.1, HTTP/2, gRPC).

Решение не в том, «что лучше», а в том, «что мне нужно видеть». Если нужно лишь раскидать сырой TCP по одинаковым бэкендам — L4 быстрее и проще. В момент, когда маршрутизация зависит от чего-то внутри запроса, нужен L7 — и никакой тюнинг L4 не наколдует URL, который тот не декодировал.

ВозможностьL4 (транспортный)L7 (прикладной)
Маршрутизирует поIP + портпути, хосту, заголовку, cookie
Читает payload?Нет — форвардит байтыДа — парсит HTTP
Протоколылюбой TCP/UDPHTTP/1.1, HTTP/2, gRPC
Терминация TLSpassthrough (обычно)терминирует + может шифровать заново
HTTP-aware retryНетДа (идемпотентные запросы)
Цена на запросочень низкаяпарсинг + буфер + крипто
Пример в AWSNLBALB

Где терминируется TLS — меняет всё

Терминация TLS — самая чистая призма на раздел L4/L7. L7-балансировщик завершает зашифрованную сессию на краю: он держит сертификат, расшифровывает запрос, читает открытый HTTP, принимает решение о маршрутизации и форвардит — либо открытым текстом внутри доверенной сети, либо заново зашифрованным к бэкенду. Именно эта расшифровка и позволяет маршрутизировать по пути или заголовку. Она же централизует управление сертификатами и снимает крипто с серверов приложения.

L4-балансировщик обычно не может терминировать TLS, потому что терминировать — значит расшифровывать, а расшифровывать — значит стать HTTP-aware. Поэтому он делает TLS passthrough: зашифрованные байты идут прямо на бэкенд, который держит сертификат и терминирует. Это держит балансировщик слепым по дизайну — отлично для end-to-end шифрования и для не-HTTP протоколов, бесполезно, если хотелось маршрутизации по пути. Это и есть конкретная причина, почему «просто возьми быстрый» бьёт в обратку: быстрый буквально не может прочитать то, по чему ты хочешь маршрутизировать.

Почему это работает

«L4 всегда быстрее» — полуправда и обман. На пакет — да, нет парсинга, нет крипто. Но L7-балансировщик, терминирующий TLS один раз на краю, может быть дешевле в сумме, чем прокидывание шифрования на каждый бэкенд, потому что каждый сервер приложения больше не платит за handshake и расшифровку. Правильный вопрос — где работу лучше сделать, а не какой уровень выигрывает микробенчмарк.

Алгоритмы, health-чеки и вынос мёртвых бэкендов

Оба уровня всё равно должны выбрать бэкенд. Round-robin раскидывает соединения поровну и нормален, когда запросы стоят примерно одинаково. Least-connections отправляет следующий запрос на бэкенд с наименьшим числом активных соединений — куда лучше, когда длительности запросов сильно разнятся, потому что один медленный эндпоинт не будет получать всё больше нагрузки. Хеширование (по IP клиента или consistent hashing) прикрепляет данный ключ к данному бэкенду — так L4-балансировщик имитирует stickiness без cookie.

Health-чеки держат пул честным. Активные чеки опрашивают каждый бэкенд по интервалу (скажем, раз в несколько секунд) и выносят его из ротации после N подряд неудач; пассивные помечают бэкенд мёртвым после того, как он вернул ошибки реальному трафику. Open-source nginx опирается на пассивные чеки; ALB/NLB и nginx Plus делают активный опрос. То, что не доходит до интуиции: упавший бэкенд выносят только после того, как health-чек это заметит — поэтому слишком медленный интервал даёт окно, когда живой трафик продолжает бить в мёртвую коробку и 502 утекают пользователям.

Выбери лучший вариант

Нужно маршрутизировать /api/* в новый сервис, / в старый, терминировать TLS на краю и катить канарейку по заголовку запроса. Выбери балансировщик.

Sticky-сессии и connection draining: два способа сломать деплой

Sticky-сессии (session affinity) прикрепляют клиента к одному бэкенду — через L4 IP-хеш или L7-cookie — чтобы in-memory состояние сессии оставалось на месте. Это работает и тихо вредит: нагрузка перестаёт распределяться ровно (один «кит»-клиент молотит одну коробку), и этот бэкенд нельзя чисто слить, потому что он владеет сессиями, которых нет ни у кого. Рефлекс сеньора — делать бэкенды stateless (сессии в Redis или JWT), чтобы любой бэкенд обслуживал любой запрос; тянуться к stickiness только когда состояние реально нельзя вынести.

Убийца деплоя — отсутствие connection draining. Когда ты убираешь бэкенд во время выката, на нём всё ещё обслуживаются in-flight запросы. Если балансировщик вырывает его мгновенно, эти запросы умирают — пользователи видят сброс на середине оформления заказа. Connection draining (AWS зовёт это deregistration delay) велит балансировщику перестать слать на бэкенд новые соединения, но дать существующим доработать в течение grace-окна. В AWS дефолт — 300 секунд, настраиваемо от 0 до 3600. Поставишь короче самого долгого запроса — всё равно режешь живые запросы; забудешь включить — каждый деплой это маленький простой. Сочетай с health-чеками: слить, подождать, потом гасить.

Викторина

Твоему L4 (NLB) балансировщику нужно слать /admin/* в отдельный пул бэкендов. Как настроить правило по пути?

Викторина

Во время rolling-деплоя пользователи жалуются на сброс соединения на середине запроса каждый раз, когда убирают старый инстанс. Какой фикс?

Расставь шаги по порядку

Расставь шаги, чтобы мягко убрать бэкенд во время деплоя:

  1. 1 Пометить бэкенд на дерегистрацию, чтобы балансировщик перестал слать ему новые соединения
  2. 2 Дать connection draining отработать: существующие in-flight запросы продолжают обслуживаться
  3. 3 Выждать окно слива (например, AWS deregistration delay, дефолт 300с)
  4. 4 Как соединения доработают или окно истечёт — погасить инстанс
  5. 5 Health-чеки подтверждают, что оставшийся пул обслуживает трафик
Вспомните перед уходом
  1. 01
    Коллега хочет маршрутизацию по пути, но команда уже крутит L4 (NLB) балансировщик «потому что быстрее». Объясни точно, почему маршрутизация по пути там невозможна и что изменится при переходе на L7.
  2. 02
    Пройди по тому, что делает connection draining во время деплоя и что ломается без него. Включи дефолт AWS.
Итог

Выбранный уровень решает, по чему балансировщик может маршрутизировать, потому что решает, что он может видеть. L4-балансировщик работает на транспортном уровне — IP, порт, сырой TCP/UDP — и форвардит байты без парсинга, что делает его быстрым, протокол-агностичным и способным на TLS passthrough, но структурно слепым к URL, заголовкам и cookie. L7-балансировщик терминирует соединение, парсит HTTP и маршрутизирует по пути, хосту, заголовку или cookie — это и даёт маршрутизацию по пути, терминацию TLS на краю, канарейки по заголовку и HTTP-aware retry — оплаченные CPU на запрос. Поверх выбора уровня лежат операционные детали, решающие надёжность: round-robin, least-connections и хеширование балансируют по-разному при неровной нагрузке; активные и пассивные health-чеки выносят мёртвые бэкенды, но лишь так быстро, как их интервал; sticky-сессии держат состояние на месте ценой ровного распределения и чистого слива; а connection draining (AWS deregistration delay, дефолт 300 секунд) даёт in-flight запросам доработать до удаления бэкенда. Два продакшен-провала падают прямо отсюда: потребовать маршрутизацию по пути на L4-балансировщике, что невозможно, и убрать бэкенд без draining, что убивает живые запросы на каждом деплое.

Продолжить восхождение ↑Уровни балансировки: тест с выбором
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources4
expand
  1. 01
  2. 02
  3. 03
  4. 04

Trademarks belong to their respective owners. Editorial reference only.