天天看點

nginx優化筆記(keepalive、https等)

一、nginx之tcp_nopush、tcp_nodelay、sendfile

1、TCP_NODELAY

你怎麼可以強制 socket 在它的緩沖區裡發送資料?

一個解決方案是 TCP 堆棧的 TCP_NODELAY選項。這樣就可以使緩沖區中的資料立即發送出去。

Nginx的 TCP_NODELAY 選項使得在打開一個新的 socket 時增加了TCP_NODELAY選項。但這時會造成一種情況:

終端應用程式每産生一次操作就會發送一個包,而典型情況下一個包會擁有一個位元組的資料以及40個位元組長的標頭,于是産生4000%的過載,很輕易地就能令網絡發生擁塞。為了避免這種情況,TCP堆棧實作了等待資料 0.2秒鐘,是以操作後它不會發送一個資料包,而是将這段時間内的資料打成一個大的包。這一機制是由Nagle算法保證。

Nagle化後來成了一種标準并且立即在網際網路上得以實作。它現在已經成為預設配置了,但有些場合下把這一選項關掉也是合乎需要的。現在假設某個應用程式發出了一個請求,希望發送小塊資料。我們可以選擇立即發送資料或者等待産生更多的資料然後再一次發送兩種政策。

如果我們馬上發送資料,那麼互動性的以及客戶/伺服器型的應用程式将極大地受益。如果請求立即發出那麼響應時間也會快一些。以上操作可以通過設定套接字的 TCP_NODELAY = on 選項來完成,這樣就禁用了Nagle 算法。(不需要等待0.2s)

2、TCP_NOPUSH

在 nginx 中,tcp_nopush 配置和 tcp_nodelay “互斥”。它可以配置一次發送資料的包大小。也就是說,它不是按時間累計 0.2 秒後發送包,而是當包累計到一定大小後就發送。

注:在 nginx 中,tcp_nopush 必須和 sendfile 搭配使用。

3、sendfile

現在流行的web 伺服器裡面都提供 sendfile選項用來提高伺服器性能,那到底 sendfile是什麼,怎麼影響性能的呢?

sendfile實際上是 Linux2.0+以後的推出的一個系統調用,web伺服器可以通過調整自身的配置來決定是否利用 sendfile這個系統調用。先來看一下不用 sendfile的傳統網絡傳輸過程:

read(file,tmp_buf, len);

write(socket,tmp_buf, len);

硬碟 >> kernel buffer >> user buffer>> kernel socket buffer >>協定棧

1)一般來說一個網絡應用是通過讀硬碟資料,然後寫資料到socket 來完成網絡傳輸的。上面2行用代碼解釋了這一點,不過上面2行簡單的代碼掩蓋了底層的很多操作。來看看底層是怎麼執行上面2行代碼的:

  1. 系統調用 read()産生一個上下文切換:從 user mode 切換到 kernel mode,然後 DMA 執行拷貝,把檔案資料從硬碟讀到一個 kernel buffer 裡。
  2. 資料從 kernel buffer拷貝到 user buffer,然後系統調用 read() 傳回,這時又産生一個上下文切換:從kernel mode 切換到 user mode。
  3. 系統調用write()産生一個上下文切換:從 user mode切換到 kernel mode,然後把步驟2讀到 user buffer的資料拷貝到 kernel buffer(資料第2次拷貝到 kernel buffer),不過這次是個不同的 kernel buffer,這個 buffer和 socket相關聯。
  4. 系統調用 write()傳回,産生一個上下文切換:從 kernel mode 切換到 user mode(第4次切換了),然後 DMA 從 kernel buffer拷貝資料到協定棧(第4次拷貝了)。

上面4個步驟有4次上下文切換,有4次拷貝,我們發現如果能減少切換次數和拷貝次數将會有效提升性能。在kernel2.0+ 版本中,系統調用 sendfile() 就是用來簡化上面步驟提升性能的。sendfile() 不但能減少切換次數而且還能減少拷貝次數。

2)再來看一下用 sendfile()來進行網絡傳輸的過程:

sendfile(socket,file, len);

硬碟 >> kernel buffer (快速拷貝到kernelsocket buffer) >>協定棧
  1. 系統調用sendfile()通過 DMA把硬碟資料拷貝到 kernel buffer,然後資料被 kernel直接拷貝到另外一個與 socket相關的 kernel buffer。這裡沒有 user mode和 kernel mode之間的切換,在 kernel中直接完成了從一個 buffer到另一個 buffer的拷貝。
  2. DMA 把資料從 kernelbuffer 直接拷貝給協定棧,沒有切換,也不需要資料從 user mode 拷貝到 kernel mode,因為資料就在 kernel 裡。

步驟減少了,切換減少了,拷貝減少了,自然性能就提升了。這就是為什麼說在Nginx 配置檔案裡打開 sendfile on 選項能提高 web server性能的原因。

綜上,這三個參數都應該配置成on:sendfile on; tcp_nopush on; tcp_nodelay on;

二、nginx長連接配接——keepalive

當使用nginx作為反向代理時,為了支援長連接配接,需要做到兩點:

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

1、保持和client的長連接配接:

預設情況下,nginx已經自動開啟了對client連接配接的keep alive支援(同時client發送的HTTP請求要求keep alive)。一般場景可以直接使用,但是對于一些比較特殊的場景,還是有必要調整個别參數(keepalive_timeout和keepalive_requests)。

1
2
3
4
      
http {
    keepalive_timeout  120s 120s;
    keepalive_requests 10000;
}
      

1)keepalive_timeout

文法:

keepalive_timeout timeout [header_timeout];
  1. 第一個參數:設定keep-alive用戶端連接配接在伺服器端保持開啟的逾時值(預設75s);值為0會禁用keep-alive用戶端連接配接;
  2. 第二個參數:可選、在響應的header域中設定一個值“Keep-Alive: timeout=time”;通常可以不用設定;

注:keepalive_timeout預設75s,一般情況下也夠用,對于一些請求比較大的内部伺服器通訊的場景,适當加大為120s或者300s;

2)keepalive_requests:

keepalive_requests指令用于設定一個keep-alive連接配接上可以服務的請求的最大數量,當最大請求數量達到時,連接配接被關閉。預設是100。這個參數的真實含義,是指一個keep alive建立之後,nginx就會為這個連接配接設定一個計數器,記錄這個keep alive的長連接配接上已經接收并處理的用戶端請求的數量。如果達到這個參數設定的最大值時,則nginx會強行關閉這個長連接配接,逼迫用戶端不得不重建立立新的長連接配接。

大多數情況下當QPS(每秒請求數)不是很高時,預設值100湊合夠用。但是,對于一些QPS比較高(比如超過10000QPS,甚至達到30000,50000甚至更高) 的場景,預設的100就顯得太低。

簡單計算一下,QPS=10000時,用戶端每秒發送10000個請求(通常建立有多個長連接配接),每個連接配接隻能最多跑100次請求,意味着平均每秒鐘就會有100個長連接配接是以被nginx關閉。同樣意味着為了保持QPS,用戶端不得不每秒中重新建立100個連接配接。是以,就會發現有大量的TIME_WAIT的socket連接配接(即使此時keep alive已經在client和nginx之間生效)。是以對于QPS較高的場景,非常有必要加大這個參數,以避免出現大量連接配接被生成再抛棄的情況,減少TIME_WAIT。

2、保持和server的長連接配接:

為了讓nginx和後端server(nginx稱為upstream)之間保持長連接配接,典型設定如下:(預設nginx通路後端都是用的短連接配接(HTTP1.0),一個請求來了,Nginx 新開一個端口和後端建立連接配接,後端執行完畢後主動關閉該連結)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
      
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 "";
        }
    }
}
      

1)location中有兩個參數需要設定:

1
2
3
4
5
6
7
8
      
http {
    server {
        location /  {
            proxy_http_version 1.1; // 這兩個最好也設定
            proxy_set_header Connection "";
        }
    }
}
      

HTTP協定中對長連接配接的支援是從1.1版本之後才有的,是以最好通過proxy_http_version指令設定為”1.1”;

而”Connection” header應該被清理。清理的意思,我的了解,是清理從client過來的http header,因為即使是client和nginx之間是短連接配接,nginx和upstream之間也是可以開啟長連接配接的。這種情況下必須清理來自client請求中的”Connection” header。

2)upstream中的keepalive設定:

此處keepalive的含義不是開啟、關閉長連接配接的開關;也不是用來設定逾時的timeout;更不是設定長連接配接池最大連接配接數。官方解釋:

  1. The connections parameter sets the maximum number of idle keepalive connections to upstream servers connections(設定到upstream伺服器的空閑keepalive連接配接的最大數量)
  2. When this number is exceeded, the least recently used connections are closed. (當這個數量被突破時,最近使用最少的連接配接将被關閉)
  3. It should be particularly noted that the keepalive directive does not limit the total number of connections to upstream servers that an nginx worker process can open.(特别提醒:keepalive指令不會限制一個nginx worker程序到upstream伺服器連接配接的總數量)

我們先假設一個場景: 有一個HTTP服務,作為upstream伺服器接收請求,響應時間為100毫秒。如果要達到10000 QPS的性能,就需要在nginx和upstream伺服器之間建立大約1000條HTTP連接配接。nginx為此建立連接配接池,然後請求過來時為每個請求配置設定一個連接配接,請求結束時回收連接配接放入連接配接池中,連接配接的狀态也就更改為idle。我們再假設這個upstream伺服器的keepalive參數設定比較小,比如常見的10.

A、假設請求和響應是均勻而平穩的,那麼這1000條連接配接應該都是一放回連接配接池就立即被後續請求申請使用,線程池中的idle線程會非常的少,趨進于零,不會造成連接配接數量反複震蕩。

B、顯示中請求和響應不可能平穩,我們以10毫秒為一個機關,來看連接配接的情況(注意場景是1000個線程+100毫秒響應時間,每秒有10000個請求完成),我們假設應答始終都是平穩的,隻是請求不平穩,第一個10毫秒隻有50,第二個10毫秒有150:

  1. 下一個10毫秒,有100個連接配接結束請求回收連接配接到連接配接池,但是假設此時請求不均勻10毫秒内沒有預計的100個請求進來,而是隻有50個請求。注意此時連接配接池回收了100個連接配接又配置設定出去50個連接配接,是以連接配接池内有50個空閑連接配接。
  2. 然後注意看keepalive=10的設定,這意味着連接配接池中最多容許保留有10個空閑連接配接。是以nginx不得不将這50個空閑連接配接中的40個關閉,隻留下10個。
  3. 再下一個10個毫秒,有150個請求進來,有100個請求結束任務釋放連接配接。150 - 100 = 50,空缺了50個連接配接,減掉前面連接配接池保留的10個空閑連接配接,nginx不得不建立40個新連接配接來滿足要求。

C、同樣,如果假設相應不均衡也會出現上面的連接配接數波動情況。

造成連接配接數量反複震蕩的一個推手,就是這個keepalive 這個最大空閑連接配接數。畢竟連接配接池中的1000個連接配接在頻繁利用時,出現短時間内多餘10個空閑連接配接的機率實在太高。是以為了避免出現上面的連接配接震蕩,必須考慮加大這個參數,比如上面的場景如果将keepalive設定為100或者200,就可以非常有效的緩沖請求和應答不均勻。

總結:

keepalive 這個參數一定要小心設定,尤其對于QPS比較高的場景,推薦先做一下估算,根據QPS和平均響應時間大體能計算出需要的長連接配接的數量。比如前面10000 QPS和100毫秒響應時間就可以推算出需要的長連接配接數量大概是1000. 然後将keepalive設定為這個長連接配接數量的10%到30%。比較懶的同學,可以直接設定為keepalive=1000之類的,一般都OK的了。

3、綜上,出現大量TIME_WAIT的情況

1)導緻 nginx端出現大量TIME_WAIT的情況有兩種:

  • keepalive_requests設定比較小,高并發下超過此值後nginx會強制關閉和用戶端保持的keepalive長連接配接;(主動關閉連接配接後導緻nginx出現TIME_WAIT)
  • keepalive設定的比較小(空閑數太小),導緻高并發下nginx會頻繁出現連接配接數震蕩(超過該值會關閉連接配接),不停的關閉、開啟和後端server保持的keepalive長連接配接;

2)導緻後端server端出現大量TIME_WAIT的情況:

nginx沒有打開和後端的長連接配接,即:沒有設定proxy_http_version 1.1;和proxy_set_header Connection “”;進而導緻後端server每次關閉連接配接,高并發下就會出現server端出現大量TIME_WAIT

三、nginx配置https

1、配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
      
server {
    listen                  80  default_server;
    listen          443 ssl;
    server_name     toutiao.iqiyi.com  toutiao.qiyi.domain m.toutiao.iqiyi.com;
    root            /data/none;
    index           index.php index.html index.htm;


    ###ssl settings start
    ssl_protocols                   TLSv1 TLSv1.1 TLSv1.2;
    ssl_certificate                 /usr/local/nginx/conf/server.pem;
    ssl_certificate_key             /usr/local/nginx/conf/server.key;
    ssl_session_cache               shared:SSL:10m;
    ssl_session_timeout             10m;
    ssl_ciphers ALL:!kEDH!ADH:RC4+RSA:+HIGH:+EXP;
    ssl_prefer_server_ciphers       on;
    ###ssl settings end
…
      

2、性能比較:

通過https通路Nginx一般會比http通路慢30%(https方式通路主要是耗Nginx伺服器的cpu)通過下面實驗驗證:

  1. nginx後端挂了5台java伺服器,java伺服器中有個簡單的java程式,從redis緩存随機讀取一個value值輸出到前端;(挂的java伺服器越多,對nginx壓力越大)
  2. 壓測nginx,3000并發,一共請求30000次,傳回結果都是200的情況下進行對比;

    實驗結果:

    A、伺服器負載對比:

    https通路,伺服器cpu最高可以達到20%,而http的通路,伺服器cpu基本在1%左右;無論那種通路,nginx伺服器負載、記憶體都不高;

    B、nginx吞吐量對比(qps):

    • https通路,30000次請求花了28s;(是http的3倍)

    • http通路,30000次請求花了9s;

統計qps時,每次清空nginx日志,然後加壓,執行完畢後使用如下指令檢視qps:

1
2
      
# cat log.2.3000https | grep '/api/news/v1/info?newsId=' | awk '{print$3}'| uniq | wc -l
37
      

注:不能持續加壓,否則無限加大壓力後往往是後端java服務出現瓶頸,導緻傳回給nginx的響應變慢,進而使得nginx壓力變小。

3、優化:

Nginx預設使用DHE算法來産生密匙,該加密算法效率很低。可以通過如下指令,删掉了kEDH算法。

ssl_ciphers ALL:!kEDH!ADH:RC4+RSA:+HIGH:+EXP;

本文為學習筆記:文章來源:http://blog.csdn.net/dream_flying_bj/article/details/54709549