Hi 你好,欢迎访问!登录
当前位置:首页 - Linux - 正文 君子好学,自强不息!

Tengine/Nginx/Openresty性能优化及杂谈(未完待续)

2016-04-10Linux撒加13890°c
A+ A-

谷歌、度娘搜索Nginx优化,能搜索出很多的文章,动不动就几万并发,十万并发,看着好像真是那么回事似的。

从使用Tengine的过程中,对Tengine/Nginx的优化,我个人认为Tengine的优化是脱离不开使用它的环境及部署结构的,单说优化Tengine的意义并不大,况且每家公司的业务各不相同,所以优化不是简单的事情。即便如此,我总结了下,从以下几个方便入手浅谈下Tengine/Nginx的性能优化。本文以Tengine为主,Nginx大部分都适用。

1、网络

带宽

一个需要支持1万并发且平均页面内容在100KB的情况下,就需要大概1G的带宽,如果带宽只有100M,需要支持这样的场景无论如何也是不可能的。更好的带宽可以支持更高的并发

路由

现在的IDC运营商有很多,越小的运营商在路由优化上就不怎么样,例如在对IDC进行测试的时候,大部分运维使用ping来测试机房的网络质量,这种测试其实是点到点的测试,中间大网的情况就测试不到。在使用mtr进行测试的时候,可以看到每一跳的延迟及路由,很多时候会发现浮动路由。这种情况下网络质量一定不好。最好通过mtr对机房提供的IP连续进行一周的测试,路径都比较稳定的时候这样的机房值得选择。

2、硬件

CPU

一般情况下作为Tengine/Nginx的应用,CPU的频率我认为对性能影响还是较小的,但是核心数对于Tengine/Nginx而言确实很有用,更多的核心数外加HT技术,可以让Tengine/Nginx运行更多的worker,从而提升Tengine/Nginx的负载能力

内存

内存的容量直接对Tengine/Nginx可承载的连接数有影响(主要指ESTAB连接)。另外大容量的内存在结合tmpfs进行合理使用的话,可以作为Tengine/Nginx的各种temp目录及各种缓存目录,降低对磁盘IO的消耗

网卡

目前在互联网公司DELL的服务器应该是用的最多的了,而且在用户没有特殊需求的时候,配置的网卡通常是Broadcom BCM57XX的,这种网卡比较便宜所有在处理小包的时候,带宽利用率不高,且流量大的时候会出现大量丢包。在Tengine/Nginx的机器上还是推荐使用Intel X350I QP的网卡,在使用最新的网卡驱动后,可以对网卡做出更多性能控制(X350默认的RSS队列最大为8个)

磁盘

磁盘的IO能力个人认为在硬件里的比重不高,在拿Tengine/Nginx作为专用缓存服务器的时候,使用SATA SSD或者PCIe-SSD卡时可以作为缓存存放的目录路径。一般Intel S3710 800G的大概7000左右,同容量宝存的800G PCIe-SSD大约1.5W。

3、系统

TCP/IP协议栈

CLOSE_WAIT与TIME_WAIT

这两种连接状态是Web服务器中大家最关心的也是最让大家头疼的,虽然很多的博客里说现在服务器内存大,这种连接达到几万、几十万时对内存的消耗不是问题不用去理会,但是在一些场景中可能会对应用造成很大的影响。例如在我们的业务中出现CLOSE-WAIT状态时,是数据库有长事务执行时导致Resin出现了CLOSE-WAIT连接,当这种连接数高于10个时,应用就会打开慢直到应用打不开。所以需要对其进行优化。尽量避免系统中出现过多的这两种连接。

当Tengine/Nginx反向代理后端服务器的时候,Tengine所在的服务器上会出现TIME-WAIT(跟后端服务器断开后产生的状态)以及CLOSE-WAIT(客户端断开后产生的状态)的连接,一般为了减少Tengine/Nginx与后端服务器通信时造成的TIME-WAIT连接,主要需要如下的配置,例如:

配置一

upstream example {
        server 192.168.0.1:8080;
        keepalive 4;
}
server{
        listen 80 backlog=2048;
        server_name www.example.com;
        location = /favicon.ico {
                error_page 404 = 200;
                log_not_found off;
                access_log off;
        }
        location = /robots.txt {
                error_page 404 = 200;
                log_not_found off;
                access_log off;
        }
        location / {
                include proxy_opt.conf;
                proxy_http_version 1.1;
                proxy_set_header Connection "";
                proxy_pass      https://example;
        }
}

配置二

server{
        listen 80 backlog=2048;
        server_name www.example.com;
        location = /favicon.ico {
                error_page 404 = 200;
                log_not_found off;
                access_log off;
        }
        location = /robots.txt {
                error_page 404 = 200;
                log_not_found off;
                access_log off;
        }
        location / {
                include proxy_opt.conf;
                proxy_pass      https://192.168.0.1:8080;
        }
}

两种配置,都可以实现www.example.com的访问,区别在于配置一因为使用upstream且在upstream里配置了keepalive连接,在压力较大的时候Nginx与后端server之间的CLOSE-WAIT以及TIME-WAIT将非常少,因为有长连接。而配置二是直接proxy_pass,在压力大的时候CLOSE-WAIT和TIME-WAIT的数量将比较多,在这种情况下,容易产生孤儿连接,孤儿连接一般占用内存约64K且为不可交换内存(可参考内核文档)。在Tengine中不光可以在upstream中配置与upstream的长连接,还支持长连接超时参数。(此处估计有人说优化TCP/IP协议栈就可以降低CLOSE-WAIT和TIME-WAIT的连接数了,真的吗?你去试试就知道了。)

除了在Nginx上启用upstream来减少CLOSE-WAIT和TIME-WAIT的连接数外,还有2种方式:
a)在/etc/sysctl.conf中将net.ipv4.tcp_max_tw_buckets的值改为0,可以快速释放TIME-WAIT连接(此法比较暴力,但在系统负载比较高时比较好使,有点像Haproxy中的option forceclose

b)编辑kernel中include/net/tcp.h

#define TCP_TIMEWAIT_LEN (60*HZ)
修改为
#define TCP_TIMEWAIT_LEN (5*HZ)
重新编译内核

PS:如果使用FreeBSD,也需要重新编译内核,只不过没有明显标记让你修改TIMEWAIT的数值,只能通过修改MSL的时间来间接达到目的,默认的MSL时间是30S,TIMEWAIT的超时时间默认是2MSL,也就是60S。

4、Tengine/Nginx/Openresty本身

安装方式的选择

对于Tengine的安装,目前貌似官方只提供源码包,只能编译安装。

对于Nginx的安装,可以通过在线的软件源进行在线安装,也可以源码编译安装

在线安装一般使用通用的CPU指令集对Nginx进行编译,这种方式不能很好的利用当前CPU的指令集,且配置文件、可执行文件均不能自定义安装目录。对于大规模进行批量化部署时不方便。

源码编译可以通过指定gcc的参数结合CPU的指令集对Nginx进行编译,可以充分使用CPU的指令集提升性能,且安装目录可以自定义,通过修改可执行文件的rpath还可以被fpm打包成公司专用的rpm包,适合批量部署。

对于GCC可使用的参数,可以参考GCC的官方手册

基本配置的优化

#自动设置Nginx启动的worker数量,默认是CPU的processor数(不是core数,除非在BIOS里关闭了CPU的HT功能)
worker_processes auto;
#自动将Nginx的worker进程绑定到CPU的processor上,1.9.10才支持auto,Tegine默认支持
worker_cpu_affinity auto;
#设置所有worker的open files数,如不设置默认为系统ulimit -n的大小
worker_rlimit_nofile 100000;
#开启pcre jit功能,编译pcre的时候需要开启jit功能
pcre_jit on;
events{
        use epoll;
        worker_connections 8192; 每个worker的最大连接数,这个数字不光光只Nginx与Client间的连接,还包括Nginx与后端Server的连接数,配置的时候须注意worker_rlimit_nofile>worker_connections*workers
        accept_mutex off;在访问量较大的网站上建议关闭accept_mutex机制
}

client_header_buffer_size 8k;
client_header_timeout 10;
client_body_buffer_size 256k;
client_body_timeout 10;
large_client_header_buffers 4 8k;
client_max_body_size 20m;
send_timeout 10;
keepalive_timeout 30;
keepalive_requests 5000;
reset_timedout_connection on;
log_format access

配置文件中的各种buffer

proxy_pass

proxy_connect_timeout   60;
proxy_send_timeout      60;
proxy_read_timeout      60;
proxy_buffer_size       64k;
proxy_buffers           4 64k;
proxy_busy_buffers_size 128k;
proxy_temp_file_write_size 512k;
proxy_next_upstream error timeout invalid_header http_503 http_404 http_502 http_504;
proxy_max_temp_file_size 32m;
proxy_intercept_errors  on;

fastcgi_pass

fastcgi_connect_timeout 30;
fastcgi_send_timeout 15;
fastcgi_read_timeout 15;
fastcgi_buffer_size 64k;
fastcgi_buffers 4 64k;
fastcgi_busy_buffers_size 128k;
fastcgi_temp_file_write_size 512k;

单台Tengine最大连接数估算

合理利用proxy_cache和fastcgi_cache

SO_REUSEPORT && TCP Fast Open

关于SO_REUSEPORT和TCP Fast Open的原理以及它们是做什么的,可以自行谷歌上查找

就这reuseport功能Tengine 2.1.0就支持,Nginx直到1.9.1才支持reuseport

SO_REUSEPORT特性本来是kernel 3.9以上才支持的功能,不过Red Hat在其6.5版本开始就支持该特性了,所以使用CentOS 6系列的童鞋只需要升级内核大于6.5版本的就可以使用了。

TCP Fast Open这个特性RHEL 6中并没有支持,真的需要3.7以上的内核才支持,有需要使用这个特性的童鞋可以升级内核,升级可以安装UEK内核,或者通过ELRepo安装高版本的内核,通知需要重新编译Tengine或者Nginx用以支持TFO,另Haproxy 1.5已经支持TFO功能

PS:Tengine的reuseport在使用过程中还是有问题的,而Nginx和Openresty则没有问题,可参见https://www.nxops.cn/post/86

合理使用tmpfs

在使用Nginx的过程中,总是会考虑使用proxy_cache,fastcgi_cache,同时也会产生各种临时文件,大多数情况下,运维工程师都会使用物理磁盘去承担这部分工作,但这样会产生额外的IO,重要的是响应速度方面磁盘要低于内存,所以在针对各种cache,以及各种temp的情况,有以下建议可参考:

a)用Nginx做缓存服务器
在使用1U DELL R630且内存为128G的前提条件下,如果被缓存的数据小于64G,可考虑直接使用tmpfs来作为proxy_cache或者fastcgi_cache的存储介质,当缓存数量非常大时也可以使用PCIe-SSD来做缓存的存储介质
b)将Nginx产生的临时文件放入tmpfs
本人使用的做法,将mount tmpfs的命令写入Nginx的启动脚本中。读者可自行研究,以下为自用启动脚本
注意tmpfs如果使用echo {1,2,3} > /proc/sys/vm/drop_caches是无法释放tmpfs占用的内存的,除非清空tmpfs

#! /bin/sh
# Description: Startup script for Tengine
# chkconfig: 2345 55 25
PATH=/sbin:/bin:/usr/sbin:/usr/bin
DESC="Tengine daemon"
NAME=tengine
DAEMON=/opt/websuite/tengine/sbin/nginx
CONFIGFILE=/opt/config/tengine/nginx.conf
PIDFILE=/opt/run/tengine/$NAME.pid
SCRIPTNAME=/etc/init.d/rc.tengine
TESTPATH=/opt/websuite
CACHEDIR=/opt/websuite/tengine/cache
TEMPDIR=/opt/websuite/tengine/temp
set -e
[ -x "$DAEMON" ] || exit 0

do_start() {
 $DAEMON -c $CONFIGFILE || echo -n "tengine already running"
}

do_stop() {
 $DAEMON -c $CONFIGFILE -s stop || echo -n "tengine not running"
}

do_reload() {
 $DAEMON -c $CONFIGFILE -s reload || echo -n "tengine can't reload"
}

do_mount_ramdisk() {
 mount -t tmpfs tmpfs $TEMPDIR/client -o defaults,size=32M,uid=websuite,mode=755
 mount -t tmpfs tmpfs $TEMPDIR/proxy -o defaults,size=32M,uid=websuite,mode=755
 mount -t tmpfs tmpfs $TEMPDIR/fastcgi -o defaults,size=32M,uid=websuite,mode=755
 mount -t tmpfs tmpfs $TEMPDIR/hmux -o defaults,size=32M,uid=websuite,mode=755
 mount -t tmpfs tmpfs $CACHEDIR/proxy -o defaults,size=512M,uid=websuite,mode=755
 mount -t tmpfs tmpfs $CACHEDIR/fastcgi -o defaults,size=512M,uid=websuite,mode=755
}

do_umount_ramdisk() {
 umount $TEMPDIR/client
 umount $TEMPDIR/proxy
 umount $TEMPDIR/fastcgi 
 umount $TEMPDIR/hmux
 umount $CACHEDIR/proxy
 umount $CACHEDIR/fastcgi
}

case "$1" in
 start)
 echo -n "Starting $DESC: $NAME"
 if [ $(mount|grep $TESTPATH|wc -l) -eq 0 ];then
        do_mount_ramdisk
 fi
 do_start
 echo "."
 ;;
 stop)
 echo -n "Stopping $DESC: $NAME"
 do_stop
 if [ $(mount|grep $TESTPATH|wc -l) -gt 0 ];then
        do_umount_ramdisk
 fi
 echo "."
 ;;
 reload)
 echo -n "Reloading $DESC configuration..."
 do_reload
 echo "."
 ;;
 restart)
 echo -n "Restarting $DESC: $NAME"
 do_stop
 sleep 1
 do_start
 echo "."
 ;;
 *)
 echo "Usage: $SCRIPTNAME {start|stop|reload|restart}" >&2
 exit 3
 ;;
esac
exit 0

5、Nginx常见部署场景

静态资源网站

Tengine+PHP-FPM

Tengine+Tomcat

Tengine使用hmux构建Resin集群

Tengine结合KVM构建Web集群

Haproxy/LVS+Tengine的配合使用

这种需要在Tengine/Nginx部署负载均衡器实现4层负载的场景,我个人更倾向于使用Haproxy,原因如下:

a)Haproxy支持与后端server的长连接,这里指的是SO_TCPKEEPALIVE,负载高时显著的降低TCP的开销

b)Haproxy支持更加丰富的健康检查机制,例如http-check/tcp-check send/expect

c)Haproxy在多核环境下,可以进行CPU MAP的绑定,可以更加细致的控制每个proxy的CPU使用

d)ACL规则更多

6、监控

Tengine基本状态输出

利用nginx-module-vts输出更多数据

7、其他

降低刷日志对Nginx性能的影响

一般情况下使用Nginx都会对不同的vhost开启access_log和error_log,不管在压力测试时还是高并发时,在开启这两种日志的时候,我们所得到的Nginx性能是真实的吗?答案是否定的,在上述情况下,其实我们得到的是Nginx刷磁盘IO的性能,并不是实际上Nginx的性能,例如在进行Kong的测试时,对upstream中的服务做压测时,开启日志但不对日志做优化,使用wrk测试4K的页面,并发最高800,而优化日志写入后,同等条件下并发可以到1700,妥妥的。对日志的写入优化比较简单。access_log   /path/access_log  access   buffer=2m,表示buffer达到2M时再将日志刷入磁盘,当然可以根据实际情况调整这个buffer,来降低日志刷入造成的Nginx性能不足

access_log过滤不需要的日志

早期的Nginx并不支持过滤指定条件的日志,比如access_log只记录http状态码是200的请求,或者不记录爬虫的访问日志等等,以前使用ngx_log_if来实现,现在可以通过Nginx的map模块实现。

分布式缓存

多台Nginx做缓存服务器的话,每台Nginx的缓存是不能共享的,不管是用内存盘做缓存还是通过更高性能的PCIe-SSD来做都是无法共享的,如果希望缓存的内容可以让所有Nginx服务器都共享的话,可以考虑两个方案:
Nginx+srcache-nginx-module+memc-nginx-module+memcached集群来实现,其中memcached集群可用mcrouter来构建;
Nginx+srcache-nginx-module+redis2-nginx-module+redis集群来实现,其中redis集群可以考虑redis cluster或者redis+twemproxy(使用唯品会的多进程版本)构建

缓解机器人压力

互联网公司的网站最烦的就是短信接口还有其他API接口被一些恶意程序刷,尤其是短信接口,让很多互联网公司苦不堪言,其中P2P金融的受害比较多,所以国内的各种云WAF就被P2P金融公司所使用,我所在公司也被刷过短信接口,从发现到解决用了一周的时间,当然一周的时间里我们使用了两种办法,一种是用Nginx来解决,另一种是客户端JS去处理,后者使我们一劳永逸,巧妙的杜绝了这种刷接口的攻击,当然目前这种攻击还在持续,但是却没有任何效果了。前者运维就可以来做做处理,后者需要研发才能实现,主要是使用scjl这个js加密库。
对于运维可以解决的事情,那就是使用testcookie-nginx-module这个模块来实现了,可以低于技术比较LOW的攻击也可以抵御技术较高的刷接口攻击。至于怎么用我会单独写博客出来

动态更新upstream

有Varnish的架构中Tengine扮演的角色

  选择打赏方式
微信赞助

打赏

QQ钱包

打赏

支付宝赞助

打赏

  选择分享方式
  移步手机端
Tengine/Nginx/Openresty性能优化及杂谈(未完待续)

1、打开你手机的二维码扫描APP
2、扫描左则的二维码
3、点击扫描获得的网址
4、可以在手机端阅读此文章
推荐阅读

发表评论

选填

必填

必填

选填

请拖动滑块解锁
>>


ivmm
1楼  ivmm @Ta
时间:2017-06-28 22:42:53

Tengine 常年不更新否掉
Nginx 如果不需要 lua 特性就用它
OpenResty如果需要 lua 特性就用它

撒加
时间:2017-09-06 10:35:54

@ivmm:Tengine否不否主要看应用场景,如果要用到TFS,Tengine跑不掉。

  用户登录