1. 程式人生 > >對於nginx $request_time的一些理解

對於nginx $request_time的一些理解

以前的理解

$request_time是 nginx收到第一個位元組 到 nginx把所有響應內容放到tcp 傳送緩衝區的時間。

很長一段時間,我都覺得上面的說法是正確的。

直到前兩天跟同事探討$request_time,才發現並不完全正確,才把以前的一些疑問解釋了,覺得挺有意義,這裡記錄一下。

request_timeCDNrequest_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則有兩種可能:

  1. ngx_http_close_request;
  2. 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錯誤.