摘要: 在搭建高吞吐量web應用這個議題上,NginX和Node.js可謂是天生一對。他們都是基於事件驅動模型而設計,能夠輕易突破Apache等傳統web服務器的C10K瓶頸。預設的配置已經能夠得到很

摘要: 在搭建高吞吐量web應用這個議題上,NginX和Node.js可謂是天生一對。他們都是基於事件驅動模型而設計,能夠輕易突破Apache等傳統web服務器的C10K瓶頸。預設的配置已經能夠得到很高的併發,不過,要是你們想在廉價硬件上作到每秒數千以上的請求,仍是有一些工做要作的。html

在搭建高吞吐量web應用這個議題上,NginX和Node.js可謂是天生一對。他們都是基於事件驅動模型而設計,能夠輕易突破Apache等傳統web服務器的C10K瓶頸。預設的配置已經能夠得到很高的併發,不過,要是你們想在廉價硬件上作到每秒數千以上的請求,仍是有一些工做要作的。node

這篇文章假定讀者們使用NginX的HttpProxyModule來爲上游的node.js服務器充當反向代理。咱們將介紹Ubuntu 10.04以上系統sysctl的調優,以及node.js應用與NginX的調優。固然,若是你們用的是Debian系統,也能達到一樣的目標,只不過調優的方法有所不一樣而已。linux

網絡調優nginx

若是不先對Nginx和Node.js的底層傳輸機制有所瞭解,並進行鍼對性優化,可能對二者再細緻的調優也會徒勞無功。通常狀況下,Nginx經過TCP socket來鏈接客戶端與上游應用。git

咱們的系統對TCP有許多門限值與限制,經過內核參數來設定。這些參數的默認值每每是爲通常的用途而定的,並不能知足web服務器所需的高流量、短生命的要求。github

這裏列出了調優TCP可供候選的一些參數。爲使它們生效,能夠將它們放在/etc/sysctl.conf文件裏,或者放入一個新配置文件,好比/etc/sysctl.d/99-tuning.conf,而後運行sysctl -p,讓內核裝載它們。咱們是用sysctl-cookbook來幹這個體力活。web

須要注意的是,這裏列出來的值是能夠安全使用的,但仍是建議你們研究一下每一個參數的含義,以便根據本身的負荷、硬件和使用狀況選擇一個更加合適的值。express

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'

重點說明其中幾個重要的。api

net.ipv4.ip_local_port_range緩存

爲了替上游的應用服務下游的客戶端,NginX必須打開兩條TCP鏈接,一條鏈接客戶端,一條鏈接應用。在服務器收到不少鏈接時,系統的可用端口將很快被耗盡。經過修改net.ipv4.ip_local_port_range參數,能夠將可用端口的範圍改大。若是在/var/log/syslog中發現有這樣的錯誤: 「possible SYN flooding on port 80. Sending cookies」,即代表系統找不到可用端口。增大net.ipv4.ip_local_port_range參數能夠減小這個錯誤。

net.ipv4.tcp_tw_reuse

當服務器須要在大量TCP鏈接之間切換時,會產生大量處於TIME_WAIT狀態的鏈接。TIME_WAIT意味着鏈接自己是關閉的,但資源尚未釋放。將net_ipv4_tcp_tw_reuse設置爲1是讓內核在安全時儘可能回收鏈接,這比從新創建新鏈接要便宜得多。

net.ipv4.tcp_fin_timeout

這是處於TIME_WAIT狀態的鏈接在回收前必須等待的最小時間。改小它能夠加快回收。

如何檢查鏈接狀態

使用netstat:

netstat -tan | awk ‘{print $6}’ | sort | uniq -c

或使用ss:

ss -s

NginX

隨着web服務器的負載逐漸升高,咱們就會開始遭遇NginX的某些奇怪限制。鏈接被丟棄,內核不停報SYN flood。而這時,平均負荷和CPU使用率都很小,服務器明明是能夠處理更多鏈接的狀態,真使人沮喪。

通過調查,發現有很是多處於TIME_WAIT狀態的鏈接。這是其中一個服務器的輸出:

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能夠看出,它們都是已經關閉的鏈接。這說明,服務器已經消耗了絕大部分可用端口,同時也暗示咱們,服務器是爲每一個鏈接都分配了新端口。調優網絡對這個問題有一點幫助,可是端口仍然不夠用。

通過繼續研究,我找到了一個關於上行鏈接keepalive指令的文檔,它寫道:

設置通往上游服務器的最大空閒保活鏈接數,這些鏈接會被保留在工做進程的緩存中。

有趣。理論上,這個設置是經過在緩存的鏈接上傳遞請求來儘量減小鏈接的浪費。文檔中還提到,咱們應該把proxy_http_version設爲」1.1″,並清除」Connection」頭部。通過進一步的研究,我發現這是一種很好的想法,由於HTTP/1.1相比HTTP1.0,大大優化了TCP鏈接的使用率,而Nginx默認用的是HTTP/1.0。

按文檔的建議修改後,咱們的上行配置文件變成這樣:

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;
}

我還按它的建議修改了server一節的proxy設置。同時,加了一個 p roxy_next_upstream來跳過故障的服務器,調整了客戶端的 keepalive_timeout,並關閉訪問日誌。配置變成這樣:

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;
}

採用新的配置後,我發現服務器們佔用的socket 下降了90%。如今能夠用少得多的鏈接來傳輸請求了。新的輸出以下:

ss -s
 
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進程的集羣化

咱們能夠修改應用,讓它fork多個線程,在同一個端口上接收數據,從而實現負載的跨越多核。Node有一個cluster模塊,提供了實現這個目標所必需的全部工具,但要將它們加入應用中還須要不少體力活。若是你用的是express,eBay有一個叫cluster2的模塊能夠用。

防止上下文切換

當運行多個進程時,應該確保每一個CPU核同一時間只忙於一個進程。通常來講,若是CPU有N個核,咱們應該生成N-1個應用進程。這樣能夠確保每一個進程都能獲得合理的時間片,而剩下的一個核留給內核調度程序運行其它任務。咱們還要確保服務器上基本不執行除Node.js外的其它任務,防止出現CPU的爭用。

咱們曾經犯過一個錯誤,在服務器上部署了兩個node.js應用,而後每一個應用都開了N-1個進程。結果,它們互相之間搶奪CPU,致使系統的負荷急升。雖然咱們的服務器都是8核的機器,但仍然能夠明顯地感受到由上下文切換引發的性能開銷。上下文切換是指CPU爲了執行其它任務而掛起當前任務的現象。在切換時,內核必須掛起當前進程的全部狀態,而後裝載和執行另外一個進程。爲了解決這個問題,咱們減小了每一個應用開啓的進程數,讓它們公平地分享CPU,結果系統負荷就降了下來:

爲重負網絡優化 Nginx 和 Node.js

爲重負網絡優化 Nginx 和 Node.js

請注意上圖,看系統負荷(藍線)是如何降到CPU核數(紅線)如下的。在其它服務器上,咱們也看到了一樣的狀況。既然總的工做量保持不變,那麼上圖中的性能改善只能歸功於上下文切換的減小。

連接和參考

● 10 Vital Aspects of building a Node.JS application

● Using NginX to avoid node.js load

● Commands to analyse system socket usage

● TCP/IP setting reference

● Linux kernel tuning 

原文發佈時間爲:2013-05-13

相關文章
相關標籤/搜索