一、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行代碼的:
- 系統調用 read()產生一個上下文切換:從 user mode 切換到 kernel mode,然後 DMA 執行拷貝,把文件數據從硬盤讀到一個 kernel buffer 裏。
- 數據從 kernel buffer拷貝到 user buffer,然後系統調用 read() 返回,這時又產生一個上下文切換:從kernel mode 切換到 user mode。
- 系統調用write()產生一個上下文切換:從 user mode切換到 kernel mode,然後把步驟2讀到 user buffer的數據拷貝到 kernel buffer(數據第2次拷貝到 kernel buffer),不過這次是個不同的 kernel buffer,這個 buffer和 socket相關聯。
- 系統調用 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) >>協議棧
- 系統調用sendfile()通過 DMA把硬盤數據拷貝到 kernel buffer,然後數據被 kernel直接拷貝到另外一個與 socket相關的 kernel buffer。這裏沒有 user mode和 kernel mode之間的切換,在 kernel中直接完成了從一個 buffer到另一個 buffer的拷貝。
- DMA 把數據從 kernelbuffer 直接拷貝給協議棧,沒有切換,也不需要數據從 user mode 拷貝到 kernel mode,因爲數據就在 kernel 裏。
步驟減少了,切換減少了,拷貝減少了,自然性能就提升了。這就是爲什麼說在Nginx 配置文件裏打開 sendfile on 選項能提高 web server性能的原因。
綜上,這三個參數都應該配置成on:sendfile on; tcp_nopush on; tcp_nodelay on;
後臺開發第三十四講|網站被攻擊了,nginx是不是該背這個鍋|項目實戰、手寫代碼
更多Linux服務器開發高階知識Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協程,DPDK等等學習資料可以加入到羣裏一起探討技術交流
二、nginx長連接——keepalive
當使用nginx作爲反向代理時,爲了支持長連接,需要做到兩點:
- 從client到nginx的連接是長連接
- 從nginx到server的連接是長連接
1、保持和client的長連接:
默認情況下,nginx已經自動開啓了對client連接的keep alive支持(同時client發送的HTTP請求要求keep alive)。一般場景可以直接使用,但是對於一些比較特殊的場景,還是有必要調整個別參數(keepalive_timeout和keepalive_requests)。
http {
keepalive_timeout 120s 120s;
keepalive_requests 10000;
}
1)keepalive_timeout
語法:
keepalive_timeout timeout [header_timeout];
- 第一個參數:設置keep-alive客戶端連接在服務器端保持開啓的超時值(默認75s);值爲0會禁用keep-alive客戶端連接;
- 第二個參數:可選、在相應的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 新開一個端口和後端建立連接,後端執行完畢後主動關閉該鏈接)
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中有兩個參數需要設置:
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;更不是設置長連接池最大連接數。官方解釋:
- The connections parameter sets the maximum number of idle keepalive connections to upstream servers connections(設置到upstream服務器的空閒keepalive連接的最大數量)
- When this number is exceeded, the least recently used connections are closed. (當這個數量被突破時,最近使用最少的連接將被關閉)
- 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:
- 下一個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個新連接來滿足要求。
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、配置
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)通過下面實驗驗證:
- nginx後端掛了5臺java服務器,java服務器中有個簡單的java程序,從redis緩存隨機讀取一個value值輸出到前端;(掛的java服務器越多,對nginx壓力越大)
- 壓測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:
# 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;