Nginx proxy_read_timeout vs. proxy_connect_timeout

15

Zacząłem używać Nginx jako odwrotnego proxy dla zestawu serwerów, które świadczą jakąś usługę.

Usługa może być czasami dość powolna (działa na Javie, a JVM czasami utknie w „pełnym śmieciu”, co może potrwać kilka sekund), więc ustawiłem na proxy_connect_timeout2 sekundy, co da Nginxowi wystarczająco dużo czasu na wymyślenie obecnie usługa utknęła na GC i nie odpowie na czas i powinna przekazać żądanie do innego serwera.

Ustawiłem również, proxy_read_timeoutaby zapobiec blokowaniu się zwrotnego serwera proxy, jeśli sama usługa zajmuje zbyt dużo czasu na obliczenie odpowiedzi - ponownie powinna przenieść żądanie na inny serwer, który powinien być wystarczająco wolny, aby zwrócić szybką odpowiedź.

Uruchomiłem kilka testów porównawczych i wyraźnie widzę, że proxy_connect_timeoutdziała poprawnie, ponieważ niektóre żądania zwracają się dokładnie w czasie określonym dla limitu czasu połączenia, ponieważ usługa jest zablokowana i nie przyjmuje połączeń przychodzących (usługa używa Jetty jako wbudowanego pojemnik serwletu). proxy_read_timeoutRównież działa, jak widzę, że wnioski o zwrot Po upływie czasu określonego tam.

Problem polega na tym, że spodziewałbym się, że niektóre żądania przekroczą limit czasu proxy_read_timeout + proxy_connect_timeoutlub prawie tak długo, jeśli usługa utknie i nie zaakceptuje połączeń, gdy Nginx spróbuje uzyskać do niej dostęp, ale zanim Nginx zdoła przekroczyć limit czasu - zostanie zwolniona i rozpoczyna przetwarzanie, ale jest zbyt wolny i Nginx przerwałby z powodu przekroczenia limitu czasu odczytu. Uważam, że usługa ma takie przypadki, ale po uruchomieniu kilku testów porównawczych, w sumie kilku milionów żądań - nie widziałem żadnego żądania, które zwraca coś powyżej proxy_read_timeout(co oznacza dłuższy limit czasu).

Byłbym wdzięczny za każdy komentarz na ten temat, choć myślę, że może to wynikać z błędu w Nginx (muszę jeszcze przyjrzeć się kodowi, więc to tylko założenie), że licznik limitu czasu nie resetuje się po połączeniu powiedzie się, jeśli Nginx nie odczytał niczego z serwera nadrzędnego.

Guss
źródło
1
Jaka wersja NGINX? Myślę, że pamiętam coś podobnego w starszej wersji (może około 0,6 / 7), ale zostało to naprawione w nowszej wersji (najnowsza stabilna wersja to 1.0.5), ale to może być źle. Nadal wiedza na temat twojej wersji pomogłaby
Smudge
Zauważ, że dokumenty mówią, że proxy_read_timeoutnie jest to „globalny limit czasu”, ale między 2 operacjami odczytu.
poige
@Sam: Używam Nginx 1.0.0. @poige - tak, zdaję sobie z tego sprawę, dlatego spodziewam się całkowitego limitu czasu proxy_read_timeout + proxy_connect_timeout.
Guss,
1
Na marginesie, powinieneś prawdopodobnie zbadać trochę tuningu zbierania śmieci dla JVM: en.wikipedia.org/wiki/…
wielomian
@polynomial: zrobiliśmy, ale zgodnie z naszymi testami współbieżna funkcja wyrzucania elementów bezużytecznych powoduje, że GC traci więcej czasu procesora w porównaniu do GC „stop the world”, dlatego wolimy inwestować w tuning Nginx :-)
Guss

Odpowiedzi:

18

Nie byłem w stanie odtworzyć tego na:

2011/08/20 20:08:43 [notice] 8925#0: nginx/0.8.53
2011/08/20 20:08:43 [notice] 8925#0: built by gcc 4.1.2 20080704 (Red Hat 4.1.2-48)
2011/08/20 20:08:43 [notice] 8925#0: OS: Linux 2.6.39.1-x86_64-linode19

Skonfigurowałem to w moim pliku nginx.conf:

proxy_connect_timeout   10;
proxy_send_timeout      15;
proxy_read_timeout      20;

Następnie skonfigurowałem dwa serwery testowe. Taki, który po prostu przekroczy limit czasu w SYN i taki, który zaakceptuje połączenia, ale nigdy nie odpowie:

upstream dev_edge {
  server 127.0.0.1:2280 max_fails=0 fail_timeout=0s; # SYN timeout
  server 10.4.1.1:22 max_fails=0 fail_timeout=0s; # accept but never responds
}

Następnie wysłałem jedno połączenie testowe:

[m4@ben conf]$ telnet localhost 2480
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
GET / HTTP/1.1
Host: localhost

HTTP/1.1 504 Gateway Time-out
Server: nginx
Date: Sun, 21 Aug 2011 03:12:03 GMT
Content-Type: text/html
Content-Length: 176
Connection: keep-alive

Potem obejrzał error_log, który pokazał to:

2011/08/20 20:11:43 [error] 8927#0: *1 upstream timed out (110: Connection timed out) while connecting to upstream, client: 127.0.0.1, server: ben.dev.b0.lt, request: "GET / HTTP/1.1", upstream: "http://10.4.1.1:22/", host: "localhost"

następnie:

2011/08/20 20:12:03 [error] 8927#0: *1 upstream timed out (110: Connection timed out) while reading response header from upstream, client: 127.0.0.1, server: ben.dev.b0.lt, request: "GET / HTTP/1.1", upstream: "http://127.0.0.1:2280/", host: "localhost"

A następnie access.log, który ma spodziewany limit 30s (10 + 20):

504:32.931:10.003, 20.008:.:176 1 127.0.0.1 localrhost - [20/Aug/2011:20:12:03 -0700] "GET / HTTP/1.1" "-" "-" "-" dev_edge 10.4.1.1:22, 127.0.0.1:2280 -

Oto używany przeze mnie format dziennika, który obejmuje indywidualne przekroczenia limitu czasu:

log_format  edge  '$status:$request_time:$upstream_response_time:$pipe:$body_bytes_sent $connection $remote_addr $host $remote_user [$time_local] "$request" "$http_referer" "$http_user_agent" "$http_x_forwarded_for" $edge $upstream_addr $upstream_cache_status';
wielomian
źródło
1
Moje pytanie powyżej, w twoim scenariuszu, jest mniej więcej takie: załóżmy, że serwer testowy akceptuje połączenie po losowym czasie od 0 do 20 sekund, a następnie czeka losowy czas od 19 sekund do 21 sekund przed odpowiedzią. Następnie uruchom prosty test porównawczy. Spodziewam się, że około 50% wyników wniosków zostanie przekroczonych z 10-sekundowym limitem czasu, 25% wyniku z 20 ~ 30 sekundowym limitem czasu, a 25% otrzyma pozytywną odpowiedź. W takim przypadku ile udanych żądań zajmie więcej niż 20 sekund? Według mojego testu żaden z nich nie jest - i to mnie niepokoi.
Guss
Testowałem, ustawiając losową stratę na SYN, a następnie mając CGI, który wypluwa linie naprawdę powoli przez około 50 sekund. Mogłem zobaczyć, że żądania trwają znacznie dłużej niż oba limity czasu łącznie, ale nadal są skuteczne: box.access.log 200: 69.814: 67.100:.: 1579 33 127.0.0.1 test.host - [21 / sie / 2011: 20: 30:52 -0700] "GET / huugs HTTP / 1.1" "-" "-" "-" dev_edge 127.0.0.1:2280 -
wielomian
Ok, to dziwne na zupełnie innym poziomie :-). Jednym z możliwych wyjaśnień jest to, że Nginx potrzebuje czasu na napisanie żądania ( proxy_send_timeout), a ponieważ ustawiłeś go na wyższą wartość proxy_connection_timeout, może to faktycznie powodować opóźnienie w ciągu 20 sekund proxy_read_timeout. Kiedy mówisz „wypluwaj wiersze naprawdę powoli” - co masz na myśli?
Guss,
spać 1 między drukowaniem linii HTML w treści odpowiedzi. Po prostu ujawniając, jak proxy_read_timeout jest między odczytami, a nie cały odczyt.
wielomian
1
O, rozumiem. Cóż, to zdecydowanie nie moja sprawa i przepraszam, że nie wyjaśniłem tego w moim OP. W moim przypadku serwer aplikacji kończy całe przetwarzanie przed zwróceniem jakiejkolwiek odpowiedzi, a następnie zwraca wszystko na raz - więc proxy_read_timeoutalbo całkowicie zawiedzie żądanie, albo całkowicie na to pozwala. To wyjaśnia także różnicę między zachowaniem, które widzisz, a zachowaniem, które widzę.
Guss
3

Problem polega na tym, że spodziewałbym się, że niektóre żądania przekroczą limit czasu po proxy_read_timeout + proxy_connect_timeout lub prawie po tym czasie, jeśli usługa utknęła i nie akceptuje połączeń, gdy Nginx próbuje uzyskać do niej dostęp, ale zanim Nginx zdoła przekroczyć limit czasu - zostaje zwolniony i rozpoczyna przetwarzanie, ale jest zbyt wolny i Nginx przerwałby z powodu przekroczenia limitu czasu odczytu.

Limit czasu połączenia oznacza opóźnienie TCP podczas uzgadniania (np. Nie było SYN_ACK). TCP spróbuje ponownie wysłać SYN, ale dałeś tylko 2 sekundy. do Nginx, aby przejść na inny Serwer, więc po prostu nie ma czasu na ponowne wysyłanie SYN.

UPD.: Nie można znaleźć w dokumentach, ale tcpdump pokazuje, że są 3 sekundy. opóźnienie między 1. wysłanym SYN a 2. próbą wysłania SYN.

poige
źródło
Nie sądzę, że o to właśnie pytam - pytanie brzmi: jeśli upstream utknie i zwróci SYN_ACK po 1,999 sekundy, dlaczego nginx nie będzie kontynuował procesu z obecnym upstream?
Guss,
Cóż, możesz użyć sniffera, jeśli chcesz być dokładnie pewien. Może się okazać, że w ciągu <2 sekund nie ma żadnych ACK.
poige
Naprawdę nie mogę użyć sniffera, ponieważ spodziewam się, że takie zachowanie się zdarzy, gdy system będzie obciążony dużym obciążeniem. Wyjaśnienie, że nigdy nie ma ACK później niż niektóre X, ale wcześniej niż 2 sekundy, nawet biorąc pod uwagę miliony żądań, wydaje się nieprawdopodobne.
Guss