高負載下Nginx,Node.JS和網絡的優化

jopen 10年前發布 | 32K 次閱讀 Nginx Web服務器 Node.js

  Nginx和Node.JS通常一起使用,在高吞吐量的Web應用程序中是一對完美的組合。它們都基于事件驅動原則設計,并且能夠越過困擾傳統Web服務器如Apache的C10K限制擴展到更高的水平。即插即用的配置可以使你的應用工作的足夠好,但是當你需要在商業硬件上支撐每秒上千的請求時,你必需作一些針對性的優化最大程度的提高服務器的性能。

    這篇文章假設你使用Nginx的HttpProxyModule模塊將負載均衡一個或更多的upstream Node.JS服務器,主要涉及Ubuntu 10.04下調整sysctl參數、Node.JS和Nginx的參數,如果使用Debian Linux 版本,也可以得到類似的結果,其它版本的不一定。

調整網絡

    如果首先沒有理解和優化業務數據的傳輸機制,對Nginx和Node.JS的配置就可能是徒勞的。大多數情況下,Nginx通過TCP sockets連接Web客戶端和upstream應用程序,但是系統通過內核參數的配置,對TCP傳輸規定了各種閾值和限制。默認的設置是在普通網絡情況下使用的,對擁有大量短連接的Web服務器卻不適用,以下列出來的參數是調整服務器TCP吞吐量的主要候選集合,為了使得改動生效,可以在/etc /sysctl.conf文件里面修改或者創建一個新的配置文件比如:/etc/sysctl.d/99-tuning.conf 然后運行 sysctl -p 使內核檢測到相關的改動。我們使用asyctl-cookbook來完成這個棘手的工作。以下參數的值僅作為參考,你可以安全的使用它們,但是建議根據你的負載、硬件和應用場景選擇合適的參數來進行設置。

                        net.ipv4.ip_local_port_range='1024 65000'
                        net.ipv4.tcp_tw_reuse='1'
                        net.ipv4.tcp_fin_timeout='15'
                        net.core.netdev_max_backlog='4096'
                        net.core.rmem_max='16777216'
                        net.core.somaxconn='4096'
                        net.core.wmem_max='16777216'
                        net.ipv4.tcp_max_syn_backlog='20480'
                        net.ipv4.tcp_max_tw_buckets='400000'
                        net.ipv4.tcp_no_metrics_save='1'
                        net.ipv4.tcp_rmem='4096 87380 16777216'
                        net.ipv4.tcp_syn_retries='2'
                        net.ipv4.tcp_synack_retries='2'
                        net.ipv4.tcp_wmem='4096 65536 16777216'
                        vm.min_free_kbytes='65536'
對其中一些重要參數作下解釋:
net.ipv4.ip_local_port_range

    通過upstream應用響應客戶端請求,Nginx必須開啟2個TCP連接,一個連接客戶端,另一個連接upstream。如果服務器接受了大量的請求,這會使得系統可用的端口數量迅速下降。這個參數可以直接增加比默認返回更大的閾值,這樣就可以申請更多可用的端口。如果你在/var/log /syslog中看到 “possible SYN flooding on port 80. Sending cookies ”,這個意味著系統為掛起的連接找不到有效的端口,增加端口的容量可以緩解這種情況。
net.ipv4.tcp_tw_reuse

    當服務器需要回收大量的TCP連接的時候,可能會使得大量的連接處于TIME_WAIT狀態,這個狀態表示連接已經被關閉但是分配的資源還沒有釋放。將這個參數設置為1,這樣內核就會在安全狀態下為一些新連接回收資源,這個比重新新建一個連接的代價小的多。
net.ipv4.tcp_fin_timeout

    在回收一個處于TIME_WAIT狀態的連接時必須等待的時間(秒),降低這個值意味著加快資源的回收。

    檢查連接狀態的命令:

    使用netstat:
    netstat -tan | awk '{print $6}' | sort | uniq -c
    使用 ss:
    ss -s

Nginx

    隨著負載的逐漸增加,開始達到了Nginx集群的一些限制,我注意到連接數正在下降,并且前文提到的內核錯誤也在不斷增多,令人沮喪的是,我知道服務器可以處理更多的連接,因為平均負載和cpu的使用率都可以忽略。通過進一步的研究,我注意到很多的連接處于TIME_WAIT狀態,以下是在服務器上ss -s的輸出:
ss -s
Total: 388 (kernel 541)
TCP:
47461 (estab 311, closed 47135, orphaned 4, synrecv 0,
timewait 47135/0), ports 33938
Transport Total IP IPV6
 *  541 - -

RAW 0 0 0

UDP 13 10 3

TCP 326 325 1

INET 339 335 4

FRAG 0 0 0

    47135連接處于TIME_WAIT狀態,更進一步,ss表明這些都是已經關閉的連接,服務器已經占用了大量可用的端口,意味著它為每一個連接都申請新的端口,修改網絡設置只能輕微的解決這個問題,但是端口數量仍然趨于飽和,經過查閱資料,我發現了關于upstreamkeepalive的文檔中說明如下:設置在worker進程緩存中連接到upstream服務器keepalive連接的最大數量。這個比較有趣,理論上說,有助于減少那些已經建立或者緩存的連接損耗。除此之外,文檔中還提到,proxy_http_version版本應該設置為1.1并且"Connection"為清除狀態,進一步研究表明:HTTP/1.1優化了TCP連接的使用效率,比HTTP/1.0高效的多,而HTTP/1.0是Nginx Proxy的默認設置。當作了以上修改之后,upstream的配置如下:
upstream backend_nodejs {
    server nodejs-3:5016 max_fails=0 fail_timeout=10s;
    server nodejs-4:5016 max_fails=0 fail_timeout=10s;
    server nodejs-5:5016 max_fails=0 fail_timeout=10s;
    server nodejs-6:5016 max_fails=0 fail_timeout=10s;
    keepalive 512;
}

    對服務器的proxy指令作了修改,并且增加了proxy_next_upstream跳過宕機的服務器(利用zero-downtime部署),調整了客戶端的keepalive_timeout參數,禁用所有的logging,配置如下:

server {
listen 80;
server_name fast.gosquared.com;
client_max_body_size 16M;
keepalive_timeout 10;
location / {
proxy_next_upstream error timeout http_500 http_502 http_503
http_504;

proxy_set_header
Connection "";
proxy_http_version 1.1;
proxy_pass http://backend_nodejs;
}
access_log off;
error_log /dev/null crit;
}

    當我將以上的配置更新到nginx cluster,sockets數量降低了90%,Nginx用更少的連接處理大量請求,ss輸出如下:

Total: 558 (kernel 604)
TCP:
4675 (estab 485, closed 4183, orphaned 0, synrecv 0,
timewait 4183/0), ports 2768
Transport Total IP IPV6
 *  604 - -

RAW 0 0 0

UDP 13 10 3

TCP 492 491 1

INET 505 501 4

 

Node.JS

    由于其基于事件驅動設計可以處理異步I/O,Node.js被設計用來處理大量的連接和請求,有很多額外的設置和調整來提高性能,我們將專注與 Node.js的流程。Node是單線程的,即使在多核機器上,也至多只能使用一個核。這意味著,除非特殊設計,你的應用程序不會充分利用服務器的性能。
Node進程集群

    對你的程序作以下修改是可能的,派生出一些進程只接收在相同端口的數據請求,并且在多個cpu核心之間進行負載均衡。Node有一個核心模塊 cluster可以幫助你完成這項工作,盡管如此,它需要你做寫額外工作集成到你的應用中,如果你使用express,eBay開發了一個類似的模塊 cluster2.
上下文切換

    當在服務器上運行多個進程,確保每個cpu核心在任意時刻被單一的線程占用。普遍來說,當可用的cpu核心數量為N,應該開啟N-1個進程,這樣每個進程都可以占用一個核,還有一個核用來執行服務器的其它服務。此外,確保服務器只運行Node.JS服務,這樣才不會競爭cpu資源。我們曾經犯過將兩個 Node.js的服務部署在相同的機器上的錯誤,每一個都是N-1個進程,這些應用的進程競爭cpu資源,導致cpu負載增加很快,即使我們將它部署在8 核的服務器上,我們也因為頻繁的上下文切換代價深刻。上下文切換是指cpu掛起一個任務去執行另外一個,當上下文發生切換的時候,內核必須將一個進程的所以狀態掛起,并加載和執行另外一個任務,當降低了進程的數量,每個進程擁有相同數量的核數之后,負載明顯下降。

 原文地址:Optimising NginX, Node.JS and networking for heavy workloads

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!