摘要: 在搭建高吞吐量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

相关文章
相关标签/搜索