對於nginx $request_time的一些理解
以前的理解
$request_time是 nginx收到第一個位元組 到 nginx把所有響應內容放到tcp 傳送緩衝區的時間。
很長一段時間,我都覺得上面的說法是正確的。
直到前兩天跟同事探討$request_time,才發現並不完全正確,才把以前的一些疑問解釋了,覺得挺有意義,這裡記錄一下。
Nginx文件對於$request_time的定義如下:
request processing time in seconds with a milliseconds resolution; time elapsed since the first bytes were read from the client
文件只介紹了何時開始計算request_time,沒有說何時結束。
新的理解
對於開啟長連線的請求處理,上面的說法是完全正確的。
但是對於短連線的呢?
大概是16年初,曾經某水果臺曾使用我們公司CDN。客戶使用幾家CDN,對比發現我們的CDN慢速請求(byte_sent/request_time)數較多。
講道理的話,這時候應該優化了。當然首先想到的肯定是sndbuf,恩,其實很多公司也確實是這麼做的。
把sndbuf調整到2M, 果然排名靠前了不少。
這時候售前同學就統計了,說Http 1.0的慢速請求數還是很多。
sndbuf還區分協議的?
顯然,http1.0和http1.1的一個主要區別就是http1.0預設是關閉長連線的。
查了下程式碼,發現 ngx_http_finalize_connection
中對於是否開啟長連線,走了兩種不同的路徑。
顯然,開啟了keep_alive,會走ngx_http_set_keepalive分支;但是,如若不開啟,keepalive則有兩種可能:
- ngx_http_close_request;
- ngx_http_set_lingering_close; 顯然呼叫lingering_close的話,會導致列印日誌的時間推遲;
if (!ngx_terminate
&& !ngx_exiting
&& r->keepalive
&& clcf->keepalive_timeout > 0 )
{
ngx_http_set_keepalive(r);
return;
}
if (clcf->lingering_close == NGX_HTTP_LINGERING_ALWAYS
|| (clcf->lingering_close == NGX_HTTP_LINGERING_ON
&& (r->lingering_close
|| r->header_in->pos < r->header_in->last
|| r->connection->read->ready)))
{
ngx_http_set_lingering_close(r);
return;
}
ngx_http_close_request(r, 0);
Nginx對於epoll採用的是NGX_USE_GREEDY_EVENT策略(The event filter requires to do i/o operation until EAGAIN: epoll.)。
這種策略導致,在呼叫ngx_http_finalize_connection
時,read->ready一直為1,從而呼叫ngx_http_set_lingering_close關閉連線。
但是這個問題在1.11.13版本,nginx引入epoll對於EPOLL_RDHUP的處理後有所改變,程式碼如下:
if (n == 0) { //讀取到的位元組數為0,表示對端關閉連線
rev->ready = 0;
rev->eof = 1;
return 0;
}
if (n > 0) {
#if (NGX_HAVE_EPOLLRDHUP)
if ((ngx_event_flags & NGX_USE_EPOLL_EVENT)
&& ngx_use_epoll_rdhup)
{
if ((size_t) n < size) {
if (!rev->pending_eof) {
rev->ready = 0;
}
rev->available = 0;
}
return n;
}
#endif
if ((size_t) n < size
&& !(ngx_event_flags & NGX_USE_GREEDY_EVENT))
{
rev->ready = 0;
}
return n;
}
err = ngx_socket_errno;
if (err == NGX_EAGAIN || err == NGX_EINTR) {
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err,
"recv() not ready");
n = NGX_AGAIN;
} else {
n = ngx_connection_error(c, err, "recv() failed");
break;
}
所以對於nginx 1.11.13之後的版本,無論是否開啟keepalive上述的結論仍然是正確的。
對於之前版本,如果想避免lingering_close,可以在配置項中lingering_close off
。
TCP Socket對於linger close的支援
close(l_onoff=0預設狀態):在套介面上不能在發出傳送或接收請求;套介面傳送緩衝區中的內容被髮送到對端.如果描述字引用計數變為0;在傳送完傳送緩衝區中的資料後,跟以正常的TCP連線終止序列(傳送FIN);套介面接受緩衝區中內容被丟棄
close(l_onoff = 1, l_linger =0):在套介面上不能再發出發送或接受請求,如果描述子引用計數變為0,RST被髮送到對端;連線的狀態被置為CLOSED(沒有TIME_WAIT狀態),套介面傳送緩衝區和套介面接受緩衝區的資料被丟棄
close(l_onoff =1, l_linger != 0):在套介面上不能在發出傳送或接收請求;套介面傳送緩衝區中的內容被髮送到對端.如果描述字引用計數變為0;在傳送完傳送緩衝區中的資料後,跟以正常的TCP連線終止序列(傳送FIN);套介面接受緩衝區中內容被丟棄;如果在連線變成CLOSED狀態前延滯時間到,那麼close返回EWOULDBLOCK錯誤.