使用Node.js配合Nginx实现高负载网络


Posted in Javascript onJune 28, 2015

 在搭建高吞吐量web应用这个议题上,NginX和Node.js可谓是天生一对。他们都是基于事件驱动模型而设计,可以轻易突破Apache等传统web服务器的C10K瓶颈。预设的配置已经可以获得很高的并发,不过,要是大家想在廉价硬件上做到每秒数千以上的请求,还是有一些工作要做的。

这篇文章假定读者们使用NginX的HttpProxyModule来为上游的node.js服务器充当反向代理。我们将介绍Ubuntu 10.04以上系统sysctl的调优,以及node.js应用与NginX的调优。当然,如果大家用的是Debian系统,也能达到同样的目标,只不过调优的方法有所不同而已。

网络调优

如果不先对Nginx和Node.js的底层传输机制有所了解,并进行针对性优化,可能对两者再细致的调优也会徒劳无功。一般情况下,Nginx通过TCP socket来连接客户端与上游应用。

我们的系统对TCP有许多门限值与限制,通过内核参数来设定。这些参数的默认值往往是为一般的用途而定的,并不能满足web服务器所需的高流量、短生命的要求。

这里列出了调优TCP可供候选的一些参数。为使它们生效,可以将它们放在/etc/sysctl.conf文件里,或者放入一个新配置文件,比如/etc/sysctl.d/99-tuning.conf,然后运行sysctl -p,让内核装载它们。我们是用sysctl-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

为了替上游的应用服务下游的客户端,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,结果系统负荷就降了下来:

使用Node.js配合Nginx实现高负载网络

请注意上图,看系统负荷(蓝线)是如何降到CPU核数(红线)以下的。在其它服务器上,我们也看到了同样的情况。既然总的工作量保持不变,那么上图中的性能改善只能归功于上下文切换的减少。

Javascript 相关文章推荐
保证JavaScript和Asp、Php等后端程序间传值编码统一
Apr 17 Javascript
javascript中callee与caller的用法和应用场景
Dec 08 Javascript
JQuery通过Ajax提交表单并返回结果
Jul 31 Javascript
Js,alert出现乱码问题的解决方法
Jun 19 Javascript
JS实现可缩放、拖动、关闭和最小化的浮动窗口完整实例
Mar 04 Javascript
avalonjs制作响应式瀑布流特效
May 06 Javascript
vue.js通过自定义指令实现数据拉取更新的实现方法
Oct 18 Javascript
JavaScript创建对象_动力节点Java学院整理
Jun 27 Javascript
从vue源码解析Vue.set()和this.$set()
Aug 30 Javascript
解决VUE项目使用Element-ui 下拉组件的验证失效问题
Nov 07 Javascript
vue中使用mockjs配置和使用方式
Apr 06 Vue.js
JavaScript声明变量和数据类型的转换
Apr 12 Javascript
jQuery模拟黑客帝国矩阵效果实例
Jun 28 #Javascript
jQuery往textarea中光标所在位置插入文本的方法
Jun 26 #Javascript
javascript实现相同事件名称,不同命名空间的调用方法
Jun 26 #Javascript
JavaScript实现数组随机排序的方法
Jun 26 #Javascript
JavaScript让Textarea支持tab按键的方法
Jun 26 #Javascript
javascript实现textarea中tab键的缩排处理方法
Jun 26 #Javascript
jQuery根据用户电脑是mac还是pc加载对应样式的方法
Jun 26 #Javascript
You might like
PHP.MVC的模板标签系统(五)
2006/09/05 PHP
一个查看session内容的函数
2006/10/09 PHP
安装PHP可能遇到的问题“无法载入mysql扩展” 的解决方法
2007/04/16 PHP
php将日期格式转换成xx天前的格式
2015/04/16 PHP
php去掉文件前几行的方法
2015/07/29 PHP
php+ajax简单实现全选删除的方法
2016/12/06 PHP
SCP远程VPS快速搬家和WDCP升级php5.3安装memcached和eaccelerator教程
2017/07/27 PHP
innerHTML 和 getElementsByName 在IE下面的bug 的解决
2010/04/09 Javascript
JQuery获取当前屏幕的高度宽度的实现代码
2011/07/12 Javascript
Jquery的Tabs内容轮换效果实现代码,几行搞定
2014/02/12 Javascript
模拟一个类似百度google的模糊搜索下拉列表
2014/04/15 Javascript
JS根据年月获得当月天数的实现代码
2014/07/03 Javascript
深入理解javascript严格模式(Strict Mode)
2014/11/28 Javascript
JS实现网页上随机产生超链接地址的方法
2015/11/09 Javascript
jquery插件jquery.dragscale.js实现拖拽改变元素大小的方法(附demo源码下载)
2016/02/25 Javascript
JQuery之proxy实现绑定代理方法
2016/08/01 Javascript
利用jQuery的动画函数animate实现豌豆发射效果
2016/08/28 Javascript
Bootstrap时间选择器datetimepicker和daterangepicker使用实例解析
2016/09/17 Javascript
Node.js 数据加密传输浅析
2016/11/16 Javascript
Vue.js中用webpack合并打包多个组件并实现按需加载
2017/02/17 Javascript
js仿新浪微博消息发布功能
2017/02/17 Javascript
微信小程序Getuserinfo解决方案图解
2018/08/24 Javascript
react MPA 多页配置详解
2019/10/18 Javascript
[02:38]2018年度DOTA2最佳劣单位选手-完美盛典
2018/12/17 DOTA
[35:39]完美世界DOTA2联赛PWL S2 FTD.C vs Rebirth 第二场 11.22
2020/11/24 DOTA
浅谈Python中的bs4基础
2018/10/21 Python
自定义Django Form中choicefield下拉菜单选取数据库内容实例
2020/03/13 Python
css3使用animation属性实现炫酷效果(推荐)
2020/02/04 HTML / CSS
HTML5实现动画效果的方式汇总
2016/02/29 HTML / CSS
简述进程的启动、终止的方式以及如何进行进程的查看
2013/07/12 面试题
建筑自我鉴定
2013/10/19 职场文书
车祸赔偿收入证明
2014/01/09 职场文书
环境保护标语
2014/06/20 职场文书
2015年清明节活动总结
2015/02/09 职场文书
幼儿园心得体会范文
2016/01/21 职场文书
七年级作文之环保作文
2019/10/17 职场文书