nginx優化——包括https、keepalive等

1、nginx之tcp_nopush、tcp_nodelay、sendfile

一、TCP_NODELAY
你怎麼能夠強制 socket 在它的緩衝區裏發送數據?
一個解決方案是 TCP 堆棧的 TCP_NODELAY選項。這樣就可使緩衝區中的數據當即發送出去。php

Nginx的 TCP_NODELAY 選項使得在打開一個新的 socket 時增長了TCP_NODELAY選項。但這時會形成一種狀況:
終端應用程序每產生一次操做就會發送一個包,而典型狀況下一個包會擁有一個字節的數據以及40個字節長的包頭,因而產生4000%的過載,很輕易地就能令網絡發生擁塞。爲了不這種狀況,TCP堆棧實現了等待數據 0.2秒鐘,所以操做後它不會發送一個數據包,而是將這段時間內的數據打成一個大的包。這一機制是由Nagle算法保證。html

Nagle化後來成了一種標準而且當即在因特網上得以實現。它如今已經成爲默認配置了,但有些場合下把這一選項關掉也是合乎須要的。如今假設某個應用程序發出了一個請求,但願發送小塊數據。咱們能夠選擇當即發送數據或者等待產生更多的數據而後再一次發送兩種策略。
若是咱們立刻發送數據,那麼交互性的以及客戶/服務器型的應用程序將極大地受益。若是請求當即發出那麼響應時間也會快一些。以上操做能夠經過設置套接字的 TCP_NODELAY = on 選項來完成,這樣就禁用了Nagle 算法。(不須要等待0.2s)前端

二、TCP_NOPUSH
在 nginx 中,tcp_nopush 配置和 tcp_nodelay 「互斥」。它能夠配置一次發送數據的包大小。也就是說,它不是按時間累計 0.2 秒後發送包,而是當包累計到必定大小後就發送。java

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

三、sendfile
如今流行的web 服務器裏面都提供 sendfile選項用來提升服務器性能,那到底 sendfile是什麼,怎麼影響性能的呢?
sendfile其實是 Linux2.0+之後的推出的一個系統調用,web服務器能夠經過調整自身的配置來決定是否利用 sendfile這個系統調用。先來看一下不用 sendfile的傳統網絡傳輸過程:
read(file,tmp_buf, len);
write(socket,tmp_buf, len);nginx

硬盤 >> kernel buffer >> user buffer>> kernel socket buffer >>協議棧web

1)通常來講一個網絡應用是經過讀硬盤數據,而後寫數據到socket 來完成網絡傳輸的。上面2行用代碼解釋了這一點,不過上面2行簡單的代碼掩蓋了底層的不少操做。來看看底層是怎麼執行上面2行代碼的:redis

  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;

2、nginx長鏈接——keepalive

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

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

一、保持和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。

二、保持和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的了。

三、綜上,出現大量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

3、nginx配置https

一、配置

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
…

 

二、性能比較:
經過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壓力變小。

 

三、優化: Nginx默認使用DHE算法來產生密匙,該加密算法效率很低。能夠經過以下命令,刪掉了kEDH算法。 ssl_ciphers ALL:!kEDH!ADH:RC4+RSA:+HIGH:+EXP;

相關文章
相關標籤/搜索