Теория
Connection Lifecycle
accept → read → create request → run phases → content → write → keepalive/close
accept— worker принимает TCP-соединение на listening socket.read— читает старт запроса и заголовки.create request— создаёт внутренний объектrequestи связывает сconnection.run phases— выполняет цепочку фаз HTTP (rewrite, access, content и т.д.).content— формирует ответ локально или идёт в upstream.write— отправляет ответ клиенту (частями, неблокирующе).keepalive/close— оставляет соединение для следующего запроса (по умолчаниюkeepalive_timeout 75s) либо закрывает.
connection vs request vs subrequest
connection:
- TCP-соединение (сокет, состояние keepalive, таймауты);
- может жить дольше одного HTTP-запроса.
request:
- один HTTP-запрос внутри соединения;
- у HTTP/1.1 обычно по одному за раз на connection;
- завершается после отправки ответа.
subrequest:
- внутренний дочерний запрос (
auth_request,SSI,mirror); - не отдельное клиентское соединение;
- нужен для внутренней логики до финального ответа.
Что нужно уметь объяснить
Почему keepalive уменьшает CPU?
- меньше новых
accept()и TCP/TLS handshakes; - меньше системных вызовов на открытие/закрытие соединений;
- меньше затрат ядра на socket state;
- больше полезной работы на запрос.
При том же трафике CPU ниже, latency стабильнее.
Что происходит при медленном клиенте?
- ответ готов быстро, но отправляется медленно;
- сокет часто «не готов к записи», данные висят в буферах;
- connection дольше занят, растут
active connectionsи память на буферы; - при большом числе таких клиентов упираемся в
worker_connections(дефолт512, лимит учитывает все типы соединений).
Что происходит при медленном upstream?
- клиент пришёл быстро, backend отвечает медленно;
- request дольше «в полёте», растёт очередь активных запросов;
- увеличиваются latency, возможны
upstream timed outи 502/504; - воркеры не блокируются, но держат больше незавершённых состояний.
Практика
1. Отключить keepalive
keepalive_timeout 0;
nginx -t && sudo nginx -s reload
2. Сравнить RPS и CPU
wrk -t4 -c200 -d30s http://127.0.0.1/
top -H -p $(pgrep -d',' -f 'nginx: worker process')
Сравнивать: requests/sec, latency p95/p99, CPU по worker.
3. Upstream с искусственной задержкой
python3 -c '
from http.server import BaseHTTPRequestHandler, HTTPServer
import time
class H(BaseHTTPRequestHandler):
def do_GET(self):
time.sleep(1)
body = b"slow backend\n"
self.send_response(200)
self.send_header("Content-Type", "text/plain")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
HTTPServer(("127.0.0.1", 18080), H).serve_forever()
'
upstream slow_backend {
server 127.0.0.1:18080;
}
location /slow {
proxy_pass http://slow_backend;
proxy_read_timeout 5s;
}
wrk -t4 -c200 -d30s http://127.0.0.1/slow
4. Сводка сокетов
ss -s
Сравнивать до/после: TCP: inuse, estab, timewait, orphaned.