深入淺出學習透析Nginx服務器的基本原理和配置指南「Keepalive性能分析實戰篇」

  • Linux系統:Centos 7 x64
  • Nginx版本:1.11.5

Nginx 是一款面向性能設計的 HTTP 服務器,能反向代理 HTTP,HTTPS 和郵件相關(SMTP,POP3,IMAP)的協議鏈接。並且提供了負載均衡以及 HTTP 緩存。它的設計充分使用異步事件模型,削減上下文調度的開銷,提高服務器併發能力。採用了模塊化設計,提供了豐富模塊的第三方模塊。所以關於 Nginx,有這些標籤:「異步」「事件」「模塊化」「高性能」「高併發」「反向代理」「負載均衡」「長連接」。本章內容主要就是針對於長連接請求模塊。

爲什麼要單獨講解keepalive指令?

upstream設置中,有個參數要特別的小心,就是這個keepalive。

大多數未仔細研讀過nginx的同學通常都會誤解這個參數,有些人理解爲這裏的keepalive是設置是否打開長連接,以爲應該設置爲on/off。有些人會被前面的keepalive_timeout誤導,以爲這裏也是設置keepalive的timeout。

但是實際上這個keepalive參數的含義非常的奇特,請小心看nginx文檔中的說明,因爲Keepalive長連接非常重要而且容易理解錯誤,所以專門做連一期專門講解keepalive的文章。

本文介紹如何使用Nginx進行配置和實現長連接Keepalive,並介紹如何設置 nginx 以提供靜態內容服務,如何配置 nginx 作爲代理服務器。

keepalive指令

支持keepalive長連接,當使用nginx作爲反向代理時,爲了支持長連接,需要做到兩點:

  • 從client到nginx的連接是長連接
  • 從nginx到server的連接是長連接

從HTTP協議的角度看,Nginx在這個過程中,對於客戶端它扮演着HTTP服務器端的角色。而對於真正的服務器端(在nginx的術語中稱爲upstream)Nginx又扮演着HTTP客戶端的角色,keepalive指令出現在版本1.1.4。

keepalive指令格式

  • keepalive不是on/off之類的開關
  • keepalive不是timeout,不是用來設置超時值
Syntax:    keepalive connections;
Default:    —
Context:    upstream
Activates the cache for connections to upstream servers.

connections的取值代表着連接到upstream服務器的持續連接(即長連接)的數量。很多人都會有一個誤解:認爲這個參數是設置到upstream服務器的長連接的數量,分歧在於是最大連接數還是最小連接數

官方文檔的介紹

The connections parameter sets the maximum number of idle keepalive connections to upstream servers

  • connections參數設置到upstream服務器的空閒keepalive連接的最大數量,這個”idle”的概念,何爲idle。大多數人之所以誤解爲是到upstream服務器的最大長連接數,一般都是因爲看到了文檔中的這句話,而漏看了這個”idle”一詞。

When this number is exceeded, the least recently used connections are closed.

  • 當這個數量被突破時,最近使用最少的連接將被關閉。

Nginx的官方文檔給出了指示,否定了最大連接數的可能:keepalive指令不會限制一個nginx worker進程到upstream服務器連接的總數量請注意空閒keepalive連接的最大數量中空閒這個關鍵字

keepalive實際場景分析

  • 先假設一個場景: 有一個HTTP服務,作爲upstream服務器接收請求,響應時間爲100毫秒。如果要達到10000 QPS的性能,就需要在nginx和upstream服務器之間建立大約1000條HTTP連接。nginx爲此建立連接池,然後請求過來時爲每個請求分配一個連接,請求結束時回收連接放入連接池中,連接的狀態也就更改爲idle。

  • 之後假設這個upstream服務器的keepalive參數設置比較小,比如常見的10.

  • 再次假設請求和響應是均勻而平穩的,那麼這1000條連接應該都是一放回連接池就立即被後續請求申請使用,線程池中的idle線程會非常的少,趨進於零。我們以10毫秒爲一個單位,來看連接的情況(注意場景是1000個線程+100毫秒響應時間,每秒有10000個請求完成):

    • 每10毫秒有100個新請求,需要100個連接
    • 每10毫秒有100個請求結束,可以釋放100個連接
    • 如果請求和應答都均勻,則10毫秒內釋放的連接剛好夠用,不需要新建連接,連接池空閒連接爲零
  • 如果請求通常不是足夠的均勻和平穩,爲了簡化問題,我們假設應答始終都是平穩的,只是請求不平穩,第一個10毫秒只有50,第二個10毫秒有150:

    • 下一個10毫秒,有100個連接結束請求回收連接到連接池,但是假設此時請求不均勻10毫秒內沒有預計的100個請求進來,而是隻有50個請求。注意此時連接池回收了100個連接又分配出去50個連接,因此連接池內有50個空閒連接。

    • 最後注意看keepalive=10的設置,這意味着連接池中最多容許保留有10個空閒連接。因此nginx不得不將這50個空閒連接中的40個關閉,只留下10個

  • 再下一個10個毫秒,有150個請求進來,有100個請求結束任務釋放連接。150 - 100 = 50,空缺了50個連接,減掉前面連接池保留的10個空閒連接,nginx不得不新建40個新連接來滿足要求

    • 可以看到,在短短的20毫秒內,僅僅因爲請求不夠均勻,就導致nginx在前10毫秒判斷空閒連接過多關閉了40個連接,而後10毫秒又不得不新建40個連接來彌補連接的不足。

    • 再來一次類似的場景,假設請求是均勻的,而應答不再均勻,前10毫秒只有50個請求結束,後10毫秒有150個:

  • 前10毫秒,進來100個請求,結束50個請求,導致連接不夠用,nginx爲此新建50個連接

  • 後10毫秒,進來100個請求,結束150個請求,導致空閒連接過多,ngixn爲此關閉了150-100-10=40個空閒連接

    • 第二個應答不均勻的場景實際上是對應第一個請求不均勻的場景:正是因爲請求不均勻,所以導致100毫秒之後這些請求的應答必然不均勻

現實世界中的請求往往和理想狀態有巨大差異,請求不均勻,服務器處理請求的時間也不平穩,這理論上的大概1000個連接在反覆的回收和再分配的過程中,必然出現兩種非常矛盾場景在短時間內反覆:

  1. 連接不夠用,造成新建連接
  2. 連接空閒,造成關閉連接。從而使得總連接數出現反覆震盪,不斷的創建新連接和關閉連接,使得長連接的效果被大大削弱。

Keepalive參數建議

造成連接數量反覆震盪的一個推手,就是這個keepalive 這個最大空閒連接數。畢竟連接池中的1000個連接在頻繁利用時,出現短時間內多餘10個空閒連接的概率實在太高。因此爲了避免出現上面的連接震盪,必須考慮加大這個參數,比如上面的場景如果將keepalive設置爲100或者200,就可以非常有效的緩衝請求和應答不均勻

keepalive參數分析

  • 對應的參數設置爲每個worker子進程在緩衝中保持的到upstream服務器的空閒keepalive連接的最大數量。當超過這個數量值的時候,會將最近最少使用(LRU)的連接進行關閉。需要考慮的是keepalive指令不會限制一個worker進程到upstream服務器連接的總數量。其參數應該設置爲一個足夠小的數字來讓upstream服務器來處理新進來的連接。
  • 如果想讓upstream每次都處理新的進來的連接,就應該將這個值放的足夠小。反過來理解,就是如果不想讓upstream服務器處理新連接,就應該放大一些?

Keepalive的使用案例

Keepalive對接memcached服務

使用keepalive連接的memcached upstream配置的例子:

upstream memcached_backend {
    server 127.0.0.1:11211;
    server 10.0.0.2:11211;
    keepalive 32;
}
server {
    ...
    location /memcached/ {
        set $memcached_key $uri;
        memcached_pass memcached_backend;
    }
}
Keepalive對接Http1.1的Web服務

對於HTTP,proxy_http_version指定應該設置爲”1.1”,而”Connection” header應該被清理:

upstream http_backend {
    server 127.0.0.1:8080;
    keepalive 16;
}
server {
    ...
    location /http/ {
        proxy_pass http://http_backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        ...
    }
}
Keepalive對接Http1.0的Web服務

HTTP/1.0 持久連接可以通過傳遞”Connection: Keep-Alive” header 到upstream server, 但是不推薦使用這種方法。

Keepalive對接FastCGI的Web服務

對於FastCGI服務器,要求設置fastcgi_keep_conn來讓長連接工作:

upstream fastcgi_backend {
    server 127.0.0.1:9000;
    keepalive 8;
}
server {
    ...
    location /fastcgi/ {
        fastcgi_pass fastcgi_backend;
        fastcgi_keep_conn on;
        ...
    }
}

當使用默認的round-robin之外的負載均衡算法時,必須在keepalive指令之前激活他們。SCGI 和 uwsgi 協議沒有keepalive連接的概念。

保持和client的長連接

爲了在client和nginx之間保持上連接,有兩個要求:

  • client發送的HTTP請求要求keep alive
  • nginx設置上支持keep alive

HTTP配置

默認情況下,Nginx已經自動開啓了對client連接的keep alive支持。一般場景可以直接使用,但是對於一些比較特殊的場景,還是有必要調整個別參數。需要修改nginx的配置文件(在nginx安裝目錄下的conf/nginx.conf):

http {
    keepalive_timeout  120s 120s;
    keepalive_requests 10000;
}

keepalive_timeout指令

keepalive_timeout指令的語法:

Syntax:    keepalive_timeout timeout [header_timeout];
Default:    keepalive_timeout 75s;
Context:    http, server, location
  1. 第一個參數設置keep-alive客戶端連接在服務器端保持開啓的超時值。值爲0會禁用keep-alive客戶端連接。
  2. 可選的第二個參數在響應的header域中設置一個值“Keep-Alive: timeout=time”。這兩個參數可以不一樣。

注:默認75s一般情況下也夠用,對於一些請求比較大的內部服務器通訊的場景,適當加大爲120s或者300s。第二個參數通常可以不用設置

keepalive_requests指令

keepalive_requests指令用於設置一個keep-alive連接上可以服務的請求的最大數量。當最大請求數量達到時,連接被關閉。默認是100。

keepalive_requests指令的實現原理
  • 指一個keepalive請求建立之後,Nginx就會爲這個連接設置一個計數器,記錄這個keep alive的長連接上已經接收並處理的客戶端請求的數量。如果達到這個參數設置的最大值時,則Nginx會強行關閉這個長連接,逼迫客戶端不得不重新建立新的長連接。

這個參數往往被大多數人忽略,因爲大多數情況下當QPS(每秒請求數)不是很高時,默認值100湊合夠用。但是,對於一些QPS比較高(比如超過10000QPS,甚至達到30000,50000甚至更高) 的場景,默認的100就顯得太低

  • 簡單計算一下,QPS=10000時,客戶端每秒發送10000個請求(通常建立有多個長連接),每個連接只能最多跑100次請求,意味着平均每秒鐘就會有100個長連接因此被nginx關閉

  • 爲了保持QPS,客戶端不得不每秒中重新新建100個連接。因此,如果用netstat命令看客戶端機器,就會發現有大量的TIME_WAIT的socket連接(即使此時keep alive已經在client和nginx之間生效)。

因此對於QPS較高的場景,非常有必要加大這個參數,以避免出現大量連接被生成再拋棄的情況,減少TIME_WAIT

保持和server的長連接

爲了讓Nginx和server(Nginx稱爲upstream)之間保持長連接,典型設置如下:

http {
    upstream  BACKEND {
        server   192.168.0.1:8080  weight=1 max_fails=2 fail_timeout=30s;
        server   192.168.0.2:8080  weight=1 max_fails=2 fail_timeout=30s;
        keepalive 300;        // 這個很重要!
    }
    server {
        listen 8080 default_server;
        server_name "";
        location /  {
            proxy_pass http://BACKEND;
            proxy_set_header Host  $Host;
            proxy_set_header x-forwarded-for $remote_addr;
            proxy_set_header X-Real-IP $remote_addr;
            add_header Cache-Control no-store;
            add_header Pragma  no-cache;
            proxy_http_version 1.1;                    // 這兩個最好也設置
            proxy_set_header Connection "";
            client_max_body_size  3072k;
            client_body_buffer_size 128k;
        }
    }
}

總結

  • keepalive 這個參數一定要小心設置,尤其對於QPS比較高的場景,推薦先做一下估算,根據QPS和平均響應時間大體能計算出需要的長連接的數量。比如前面10000 QPS和100毫秒響應時間就可以推算出需要的長連接數量大概是1000. 然後將keepalive設置爲這個長連接數量的10%到30%。

  • 如果不喜歡計算的同學也可以直接設置爲keepalive=1000之類的,一般都OK的了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章