高负载下Nginx,Node.JS和网络的优化

jopen 10年前

  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