...

Optimering af NGINX Keepalive-anmodninger: Optimal webserverydelse gennem målrettet finjustering

Med nginx keepalive Jeg sænker omkostningerne ved oprettelse af forbindelser, reducerer antallet af håndtryk og fremskynder svartiderne mærkbart. Målrettet tilpassede timeouts, anmodningsbegrænsninger pr. forbindelse og genbrugte upstream-sockets giver målbare ydeevneforbedringer uden behov for ny hardware.

Centrale punkter

  • Timeouts Vælg med omtanke: Tomgangstid så kort som nødvendigt, så lang som nyttigt.
  • Forespørgsler Begræns pr. forbindelse: Holdbare sockets, ingen frysninger.
  • Upstream-puljer Aktivér: Permanente backend-forbindelser pr. worker.
  • Arbejder og tilpasse forbindelserne: Nok slots til inaktive og aktive klienter.
  • Overvågning Etablere: Overvåge forbindelseshastighed, ventetid og fejl.

NGINX Keepalive: Effekt og omkostninger

Jeg holder bevidst TCP-forbindelser åbne, fordi Håndtryk er er dyre og dominerer ved mange små forespørgsler. Persistente sockets sparer ikke kun RTT’er, de udjævner også CPU-belastningen, da kryptografi til TLS sjældnere skal startes. Hver åben forbindelse optager dog Ressourcer, f.eks. filbeskrivere og buffere, som jeg skal holde øje med. Kunsten ligger i balancen: tilstrækkelig genbrug for at sikre hastighed og tilstrækkeligt budget til nye forbindelser ved spidsbelastninger. Den, der finder den rette balance, opnår konstant lave TTFB-værdier og en hurtig brugeroplevelse.

HTTP/2 og HTTP/3: Multiplexing møder Keepalive

Med HTTP/2 og HTTP/3 Antallet af nødvendige forbindelser pr. klient falder, fordi flere streams kører over én forbindelse. Keepalive er dog stadig relevant: Den ene forbindelse bør forblive åben pålideligt, ellers går fordelen ved multiplexing tabt på grund af hyppige genopkoblinger.

Jeg holder øje med de specifikke idle-parametre for moderne protokoller og sørger for, at værdierne passer til mine klient-timeouts. Ved test starter jeg med moderate værdier og øger dem, når belastningen er stabil, indtil antallet af genopkoblinger falder, og latenstiderne forbliver konstante.

http {
    # HTTP/2: Idle-tid for ubrugte, men åbne streams
    http2_idle_timeout 60s;

    # HTTP/3/QUIC: lignende logik for UDP-baserede forbindelser
    http3_idle_timeout 60s;

    # TLS-genoptagelse reducerer omkostningerne ved håndtryk ved nye forbindelser
    ssl_session_cache shared:SSL:50m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;
}

Multiplexing reducerer det nødvendige antal parallelle TCP/QUIC-forbindelser, men ikke betydningen af korrekte timeouts. Hvis man bruger HTTP/2/3, kan man ofte indstille klient-timeouts lidt mere generøst, fordi mange små ressourcer sendes via samme kanal. Vigtigt: Det skal stadig være muligt at måle Tid til første byte, fejlprocenter og åbne streams pr. forbindelse.

Indstil Client-Keepalive korrekt

For browser-klienter styrer jeg genbrug via keepalive_timeout og keepalive_requests, så sockets holder længe nok uden at blokere i evigheder. Som udgangspunkt bruger jeg en timeout på 30–60 sekunder og 100–300 anmodninger pr. forbindelse, hvorefter jeg justerer ud fra målingerne. En detaljeret oversigt findes her Vejledning i Keepalive-timeout, der forklarer virkningerne på ventetiden og serverressourcerne. Kortere timeouts er velegnede ved meget mange korte opkald, mens længere tidsvinduer er en fordel ved periodiske API-adgange. Til at begynde med indstiller jeg klare standardværdier og måler effekten på åbne forbindelser samt fejlmønstre.

http {
    # Inaktive forbindelser til klienten
    keepalive_timeout 60s;
    # Øvre grænse for antal anmodninger pr. TCP-forbindelse
    keepalive_requests 200;

    # Valgfrit: Fjern Keep-Alive for bestemte klienter (ældre fejl)
    # keepalive_disable msie6;
}

Upstream-keepalive i reverse-proxyen

Mellem NGINX og backend-apps bruger jeg persistente upstream-sockets, fordi opkoblingen til PHP-FPM, Node.js eller Python-tjenester ligeledes Forsinkelse koster. Til det aktiverer jeg i upstream-poolen et passende antal genanvendelige forbindelser pr. worker. Det er vigtigt, at HTTP/1.1 sendes bagud, og at Connection-headeren er tom, ellers afbryder klientens anmodning om „close“ backend-persistensen. Jeg tager udgangspunkt i antallet af samtidige anmodninger og indstiller puljen således, at der næsten ikke opstår nye forbindelser. På den måde falder backend-forbindelsestiden, og hele kæden leverer hurtigere Svar på spørgsmål.

upstream backend {
    server 127.0.0.1:9000;
    keepalive 64; # Antal vedvarende upstream-forbindelser pr. worker
}

server {
    location / {
 proxy_pass http://backend;
        proxy_http_version 1.1;
 proxy_set_header Connection "";
 # TCP-keepalive for upstream-sockets på OS-niveau
 proxy_socket_keepalive on;
    }
}

Dimensionering af bassinet og budget for tilslutning

Jeg beregner puljer på en realistisk måde: Antallet af vedvarende upstream-forbindelser beregnes ud fra worker_processes × keepalive pr. upstream. Hvis man bruger 8 workere og keepalive 64, holder man op til 512 sockets åbne pr. upstream – pr. instans. Bag en load balancer eller ved flere upstream-mål kan det hurtigt løbe op.

Mit mål: nok ledige sockets til, at størstedelen af forespørgslerne uden ny Connect opfyldes, men der stadig er plads til spidsbelastninger. Jeg overvåger målet „nye upstream-forbindelser pr. sekund“ og sænker det, indtil yderligere forøgelser af poolstørrelsen ikke længere medfører nogen nævneværdig forbedring af latenstiden.

Jeg tager også højde for Retfærdighed: For store puljer kan stille nyankomne klienter i en ugunstig situation, fordi worker-slots optages af inaktive forbindelser. En moderat grænse med aktiv overvågning er som regel hurtigere end at indstille maksimale værdier på en mavefornemmelse.

Finjustering: Timeouts og anmodningsgrænser

Jeg kombinerer timeout og anmodningsbegrænsning på en sådan måde, at forbindelser effektivt genbruges uden at Langrendsløber at blive. Høje værdier på begge akser minimerer antallet af forbindelser, men øger risikoen for fastlåste sockets ved netværksproblemer. Lave værdier sikrer friske forbindelser, men kræver ekstra håndtryk. Jeg går frem i små skridt, observerer fejl og justerer med jævne mellemrum. Den følgende tabel viser fornuftige startintervaller for forskellige brugsmønstre og giver et kompakt Orientering.

Scenarie keepalive_timeout keepalive_requests Hint
Mange korte sidevisninger 10–30 sekunder 100-300 Hurtig genbrug, lav idle-binding
Typisk hjemmeside 60–120 sekunder 200–400 Et godt gennemsnit for Assets og HTML
API med periodiske opkald 60–120 sekunder 300–1000 Højere genbrugsprocent for kunder
Interne tjenester / gateways 30–90 sekunder 500–1000+ Konstans er vigtigere end minimale forbindelser

Arbejderoptimering og forbindelser

Jeg satte arbejdsprocesser på »auto« eller på antallet af CPU-kerner, og sørg for, at der er tilstrækkelig arbejdstager_forbindelser fordi inaktive sockets optager slots. For lave grænser forhindrer nye forbindelser, selvom der stadig er ledig CPU-kapacitet. Hvis man kører store keepalive-puljer, skal man have tilstrækkeligt med deskriptorer og event-slots pr. worker. En god introduktion findes i „Skalering af Worker-Connections“, der forklarer sammenhængene mellem begivenheder, forbindelser og belastning. Velgennemtænkte værdier sikrer, at genbrug af inaktivitet og nye forbindelser kan eksistere side om side.

worker_processes auto;

events {
    worker_connections 4096;
    # Valgfrit: reuseport kan forbedre fordelingen på kernelniveau
    # multi_accept on;
}

http {
    keepalive_timeout 60s;
    keepalive_requests 200;

 upstream backend {
 server 127.0.0.1:9000;
 keepalive 64;
    }
}

Optimering af operativsystem og socket

Jeg tjekker systemgrænserne, så Keepalive kan udnytte sit fulde potentiale. For få deskriptorer eller overfyldte socket-køer medfører kunstige flaskehalse. Ud over ulimit og worker_rlimit_nofile spiller kernebegrænsninger en afgørende rolle.

# Eksempler på sysctl-værdier (juster med forsigtighed og efter afprøvning)
fs.file-max = 1000000
net.core.somaxconn = 65535
net.core.netdev_max_backlog = 16384
net.ipv4.ip_local_port_range = 1024 65000
net.ipv4.tcp_fin_timeout = 15
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_max_syn_backlog = 262144

Jeg tilpasser disse værdier til omgivelserne: Mange kortvarige forbindelser drager fordel af et bredere portinterval og korte FIN-/TIME_WAIT-tider. Ved upstream-keepalive reducerer jeg Neuconnects, hvilket mindsker TIME_WAIT-belastningen. Derudover tager jeg højde for NAT-Enheder mellem proxy og backend: For strenge timeout-indstillinger for inaktivitet i netværket afbryder forbindelserne på uforudsigelig vis. En moderat anmodningsgrænse pr. socket og TCP-keepalives (proxy_socket_keepalive on;) forhindrer, at forbindelserne bliver „forældede“.

Indstil header og HTTP-version korrekt

Jeg er opmærksom på HTTP/1.1 til backend, fordi Upstream-Keepalive kun fungerer med det. Derudover fjerner jeg aktiv forbindelsesstyring via header, så NGINX selvstændigt administrerer persistensen. På klientsiden lader jeg Keep-Alive køre i overensstemmelse med standarden og begrænser levetiden via timeout og anmodningsbegrænsning. Derudover kontrollerer jeg backend-idle-timeouts og indstiller dem til en værdi, der er minimalt højere end NGINX, for at undgå reset-fejl. Rene headers sikrer Genbrug uden uønskede lukninger.

# Eksempel: Proxy-placering med korrekte headere
location /api/ {
    proxy_pass http://backend;
    proxy_http_version 1.1;
    proxy_set_header Connection "";
}

Forskel: HTTP Keep-Alive vs. TCP-Keepalive

Jeg skelner skarpt mellem HTTP Keep-Alive (flere HTTP-anmodninger pr. forbindelse) og TCP-Keepalive (Test på operativsystemniveau for at opdage inaktive modparter). HTTP Keep-Alive styrer jeg med keepalive_timeout og keepalive_requests, mens TCP-keepalives, afhængigt af stakken, går over proxy_socket_keepalive on; og systemparametre. For backends, der kører over ustabile netværk, aktiverer jeg TCP-keepalives for hurtigere at rydde fastlåste sockets.

Langvarige processer og særlige tilfælde: WebSockets, SSE, gRPC

WebSockets og server-sendte begivenheder er Langrendsløber, der holder en forbindelse åben i lang tid – her spiller klassisk Reuse en underordnet rolle. Jeg sørger for passende proxy_read_timeout og beskyt mig med send_timeout mod Slowloris-effekter. For gRPC (baseret på HTTP/2) gælder overvejelserne vedrørende multiplexing; jeg indstiller idle-timeouts således, at streams ikke lukkes unødigt ned.

location /ws/ {
    proxy_pass http://backend;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_read_timeout 300s;
    send_timeout 30s;
}

Overvågning og målinger

Jeg måler succesen ud fra nøgletal som andelen af nye upstream-forbindelser, upstream_connect_time og andelen af åbne forbindelser pr. worker. Faldende forbindelsesrater ved uændret eller stigende antal anmodninger tyder på vellykket genbrug. Markante timeouts eller forbindelsesnulstillinger indikerer uoverensstemmende timeouts mellem NGINX og backend. Derudover overvåger jeg hukommelse, filbeskrivere og begivenhedskøer under belastning. Ved regelmæssig kontrol opdager man tendenser tidligt og forhindrer dyre Fejl og mangler.

Forbedringer af logfiler for at skabe større gennemsigtighed ved genbrug

For at få et bedre overblik udvider jeg adgangsloggen med oplysninger om forbindelserne. På den måde kan jeg se, hvor ofte en TCP-forbindelse genbruges, og hvordan forbindelsestiderne udvikler sig.

log_format keepalive_fmt
  '$remote_addr $host "$request" $status $body_bytes_sent '
  '$request_time $upstream_connect_time '
  'conn:$connection reqs:$connection_requests';

access_log /var/log/nginx/access_keepalive.log keepalive_fmt;

Jeg observerer median- og P95/P99-værdier for upstream_connect_time samt fordelingen af $-forbindelsesanmodninger. Stigende Reuse-tal ved stabil latenstid betyder, at puljer og timeouts er valgt korrekt.

Typiske snublesten og løsninger

For store puljer fylder forbindelsesslots, mens nye klienter venter, derfor holder jeg størrelserne moderate og justerer dem løbende. Forskellige timeout-værdier for inaktivitet mellem proxyen og backend’et medfører genstart, så jeg indstiller backend’et til en værdi, der er minimalt højere end NGINX. Et glemt „Connection: close“ i proxy-headeren afbryder persistensen, derfor sletter jeg konsekvent dette element fra headeren. TLS-forhandling kan belaste CPU’en ved mange nye forbindelser, hvilket jeg afbøder ved at øge genbrugsandelen. Ved sporadiske netværksfejl hjælper en moderat anmodningsgrænse pr. socket, så gamle Sessioner ikke leve for evigt.

Praktisk orienterede konfigurationer

For meget besøgte websteder vælger jeg en kort timeout og en middelhøj anmodningsgrænse, så ressourcerne kører effektivt. Ved API’er med tilbagevendende opkald hæver jeg grænsen for yderligere at reducere TCP- og TLS-handshakes. Jeg dimensionerer upstream-puljer ud fra den forventede parallelitet og tester med realistisk trafik. Hvert miljø opfører sig forskelligt, derfor tjekker jeg latenstiden og fejlmønstrene efter ændringer. To eksempler viser Startværdier, som jeg derefter finjusterer ved hjælp af målepunkter.

#-scenarie 1: Højt besøgt websted
http {
    keepalive_timeout 30s;
    keepalive_requests 300;

 upstream app {
 server 127.0.0.1:8080;
        keepalive 32;
    }

 server {
 listen 443 ssl http2;
 Hold øje med # HTTP/2-inaktivitet
 http2_idle_timeout 45s;
    }
}
# Scenarie 2: API med periodiske opkald
http {
    keepalive_timeout 75s;
    keepalive_requests 1000;

    upstream api_backend {
 server 127.0.0.1:9001;
 keepalive 64;
    }

    server {
 listen 443 ssl http2;
 # Lidt længere inaktivitetsvindue for tilbagevendende opkald
 http2_idle_timeout 75s;
    }
}

Tjekliste til iterativ optimering

Jeg starter med en analyse af den nuværende situation: Trafikmønstre, svartider og fejlprocent sætter retningen. Derefter indstiller jeg klient-timeout og anmodningsgrænse til solide startværdier og aktiverer upstream-puljer. Jeg indstiller backend-idle-timeouts lidt højere end NGINX, så der ikke opstår uventede Nulstillinger opstår. Derefter overvåger jeg forbindelseshastigheder, forbindelsestid og åbne sockets pr. worker. Hvis man ønsker at dykke dybere ned i genbrugsgraden, kan man finde inspiration omkring Genbrug af forbindelser og fornuftige øvre grænser.

Yderligere diagnose: Mismatch og tidsadfærd

Når forbindelser tilsyneladende afbrydes „uden grund“, leder jeg efter Uoverensstemmelser i kæden: Client-Idle vs. NGINX-Timeout vs. Backend-Idle og mellemliggende NAT/gateways. Jeg øger backend-timeout-værdien en smule i forhold til NGINX-værdien, tjekker reset-koder i fejlloggen og observerer, om upstream_connect_time viser spidser. Ofte er en lille buffer (f.eks. +10–20%) ved backend-timeout nok til at undgå genstart.

Jeg bemærker desuden, at „langvarig nærhed“-faser: Når forbindelsen lukkes, lader NGINX indgående data løbe ud i et kort øjeblik, hvilket optager worker-ressourcer. Et meget stort antal samtidige lukninger kan binde begivenheder. I sådanne tilfælde justerer jeg lukkevinduerne og holder det samlede antal åbne forbindelser i balance ved hjælp af fornuftige keepalive-værdier.

Resumé: Keepalive som middel til at forbedre ydeevnen

Jeg bruger Keepalive målrettet, fordi det reducerer omkostningerne ved oprettelse af forbindelser, mindsker latenstiden og aflaster CPU’en. Kombinationen af en passende timeout, en velafbalanceret anmodningsgrænse og passende upstream-puljer giver mærkbare Hastighed. Uden overvågning forbliver potentialet uudnyttet, derfor tjekker jeg løbende nøgletallene og justerer værdierne trin for trin. Hvis man har brug for yderligere ressourcer, skal man være opmærksom på antallet af arbejdere, forbindelsesslots og korrekt håndtering af headere. Professionelle opsætninger, f.eks. hos webhoster.de, udnytter disse justeringsmuligheder fuldt ud og leverer hurtige, pålidelige tjenester.

Aktuelle artikler