Что такое keep-alive, пул соединений и head-of-line blocking?
HTTP и HTTPS
Keep-alive переиспользует одно TCP-соединение для многих запросов, пул держит набор таких соединений наготове, а head-of-line blocking — ситуация, когда застрявший первый элемент очереди задерживает всех за ним.
Keep-alive
- В HTTP/1.1 соединение постоянное по умолчанию, закрывает его
Connection: close. Экономятся TCP и TLS handshake и разогретое окно перегрузки. - Сервер закрывает простаивающие соединения по таймауту: в nginx
keepalive_timeoutпо умолчанию 75 с. - Классическая ошибка: клиент держит соединение дольше, чем сервер или балансировщик, пишет в закрытый сокет и получает
ECONNRESET.
Пул соединений
- HTTP/1.1 обслуживает один запрос за раз на соединение, поэтому браузер открывает до 6 соединений на хост.
- В бэкенде пул ограничивает параллелизм к апстриму и защищает от исчерпания эфемерных портов.
// пул у браузера: ≤ 6 сокетов на хост в HTTP/1.1
await Promise.all([...Array(10)].map((_, i) => fetch(`/api/${i}`).then((r) => r.text())));
for (const e of performance.getEntriesByType('resource'))
console.log(e.nextHopProtocol, e.connectStart === e.connectEnd ? 'из пула' : 'новое');
document.head.append(Object.assign(document.createElement('link'),
{ rel: 'preconnect', href: 'https://api.example.com' })); // прогрев DNS+TCP+TLSHead-of-line blocking
- HTTP-уровень: в HTTP/1.1 ответы идут строго по порядку, pipelining не прижился именно поэтому. HTTP/2 мультиплексирует потоки в одном соединении.
- TCP-уровень: потеря одного пакета стопорит все потоки HTTP/2 до ретрансмита. QUIC в HTTP/3 держит потоки независимыми.
Коротко для собеседования
Переиспользование соединений экономит рукопожатия, а пул ещё и ограничивает нагрузку на апстрим. Типичная ошибка — клиентский таймаут простоя длиннее серверного, отсюда ECONNRESET; а при сильных потерях одно соединение HTTP/2 может проиграть шести соединениям HTTP/1.1.