当前位置 博文首页 > 软件性能测试分析与调优实践之路-Web中间件的性能分析与调优总结
本文主要阐述软件性能测试中的一些调优思想和技术,节选自作者新书《软件性能测试分析与调优实践之路》部分章节归纳。
在国内互联网公司中,Web中间件用的最多的就是Apache和Nginx这两款了,包括很多大型电商网站淘宝、京东、苏宁易购等,都在使用Nginx或者Apache作为Web中间件。而且很多编程语言在做Web开发时,会将Apache或者Nginx作为其绑定的固定组件,比如php语言做Web开发时,就经常和Apache联系在一起,使得apche成为了php在Web开发时的一个标配。而Nginx不管是在作为Web静态资源访问管理或者作为动态的请求代理性能都是非常的高效,当然Nginx或者Apache在性能分析时,有时候也会存在性能瓶颈或者需要进行调优以支持更高的并发处理能力。
1. Nginx的性能分析和调优
1.1 Nginx的负载均衡策略的选择
在一般的时候,Web中间件最大的作用就是负责对请求进行分发,也就是我们常说的起到负载均衡的作用,当然负载均衡只是Nginx的作用之一,Nginx常见的负载均衡策略一般包括轮询、指定权重(weight)、ip_hash、least_conn、fair、url_hash等六种,其中默认执行的策略为轮询,fair、url_hash属于第三方策略,这两种策略不是Nginx自带支持的策略,需要安装第三方的插件来辅助支持。在不同的场景下,每一种策略的选择对系统的整体性能影响都非常大,一般建议根据实际场景和服务器配置来选择对应的负载均衡策略。
upstream applicationServer { server 192.168.1.14; server 192.168.1.15; }
使用轮询策略时,其他非必填的辅助参数如下:(转载请注明出处:来源于博客园,作者:张永清 https://www.cnblogs.com/laoqing/p/14259788.html)
使用轮询策略的辅助参数
参数 |
含义 |
fail_timeout |
该参数需要和max_fails参数结合一起来使用,用于表示在fail_timeout指定的时间内某个server允许重试连接失败的最大次数 |
max_fails |
在fail_timeout参数设置的时间内最大失败次数,如果在这个时间内,所有针对该服务器的请求都失败了,Nginx就判定该服务器挂掉了 |
down |
标记指定的服务器已经挂掉了,Nginx将不会再向标记为down的服务器转发任何的请求 |
upstream applicationServer { server 192.168.1.14 weight=8; server 192.168.1.15 weight=10; }
upstream applicationServer { ip_hash; server 192.168.1.14; server 192.168.1.15; }
upstream applicationServer { least_conn; server 192.168.1.14; server 192.168.1.15; }
upstream applicationServer { server 192.168.1.14; server 192.168.1.15; fair; }
upstream applicationServer { server 192.168.1.14; server 192.168.1.15; hash $request_uri; }
1.2 Nginx进程数的配置优化
nginx服务启动后会包括两个重要的进程:
worker进程的个数可以在配置文件nginx.conf中进行配置,如下所示。
worker_processes 1; # Nginx配置文件中worker_processes指令后面的数值代表了Nginx启动后worker进程的个数。
worker进程的数量一般建议等于CPU的核数或者CPU核数的两倍。通过执行lscpu命令可以获取到CPU的核数,如图所示。
或者通过执行grep processor /proc/cpuinfo|wc –l 命令也可以直接获取到CPU的核数。
[root@localhost conf]#grep processor /proc/cpuinfo|wc -l
在配置完worker进程的数量后,还建议将每一个worker进程绑定到不同的CPU核上,这样可以避免出现CPU的争抢。将worker进程绑定到不同的CPU核时可以通过在nginx.conf中增加worker_cpu_affinity 配置,例如将worker进程分配到4核的CPU上,可以按照如下配置进行配置。
worker_processes 4; worker_cpu_affinity 0001 0010 0100 1000;
1.3 Nginx事件处理模型的优化
为了性能得到最优处理,Nginx的连接处理机制在不同的操作系统中一般会采用不同的I/O事件模型,在Linux操作系统中一般使用epoll的I/O多路复用模型,在freebsd操作系统中使用kqueue的I/O多路复用模型,在solaris操作系统中使用/dev/pool方式的I/O多路复用模型,在windows操作系统中使用的icop模型。在实际使用Nginx时,我们也是需要根据不同的操作系统来选择事件处理模型,很多事件模型都只能在对应的操作系统上得到支持。比如我们在Linux操作系统中,可以使用如下配置来使用epoll事件处理模型。
events { worker_connections 1024; use epoll; }
关于I/O多路复用做个说明:在Nginx中可以配置让一个进程处理多个I/O事件和多个调用请求,这种处理方式就类似Redis中的单线程处理模式一样,Redis缓存读写处理时采用的虽然是单线程,但是性能和效率却是非常的高,这就是因为Redis采用了异步非阻塞I/O多路复用的策略导致资源的开销很小,不需要重复的去创建和释放资源,而是共用一个处理线程。Nginx中也同样采用异步非阻塞I/O策略,每个worker进程会同时启动一个固定的线程来利用epoll监听各种需要处理的事件,当有事件需要处理时会将事件注册到epoll模型中去进行处理,异步非阻塞I/O策略在处理时线程可以不用因为某个I/O的处理耗时很长而一直导致线程阻塞等待,线程可以不用等待响应,也不必等待响应,而是可以继续去处理其他的I/O事件。当I/O事件处理完成后,操作系统内核会通知I/O事件已经处理完成,这时线程才会去获取处理好的结果。
下面表列出了Nginx常用事件处理模型的详细介绍。
Nginx常用事件处理模型
处理模型 |
说明 |
select |
各个版本的Linux和Windows操作系统都支持的基本事件驱动模型。select模型处理事件的步骤: (1)、创建事件的描述符集合,包括读、写、异常发送三类事件描述符分别用来收集读事件的描述符、写事件的描述符和异常事件的描述符。 (2)、调用select模型底层提供的select函数等待事件发生。 (3)、轮询所有事件描述符集合中的每一个事件描述符,检查是否有相应的事件发生,如果有,select模型就进行相关的处理。 |
poll |
Linux操作系统上的基本事件驱动模型,此模型无法在windows操作系统上使用。poll与select的共同点是都是先创建一个关注事件的描述符集合,再去等待这些事件发生,然后再轮询描述符集合检查有没有事件发生,如果有就进行处理。不同点是select库需要为读事件、写事件、异常事件分别创建一个描述符集合,因此在最后轮询的时候,需要分别轮询这三个集合。而poll库只需要创建一个集合,在每个描述符对应的结构上分别设置读事件、写事件、异常事件,最后轮询的时候,可以同时检查这三种事件是否发生 |
epoll |
epoll库是Nginx服务器支持的高性能事件驱动模型之一,epoll属于poll模型的一个变种,和poll主要区别在于epoll不需要使用轮询的模式去检查有没有对应的事件发生,而是交给操作系统内核去负责处理,一旦有某种对应的事件发生时内核会把发生事件的描述符列表通知给进程,这样就避免了轮询整个描述符列表。epoll库在Linux操作系统上是非常高效的,epoll可以同时处理的I/O事件数是操作系统可以打开文件的最大数目,而且epoll库的I/O效率不随描述符数目增加而线性下降,因为它只会对操作系统内核反馈的待处理的事件描述符进行操作 |
rtsig |
rtsig模型不是一种经常用到的事件处理模型,rtsig模型在工作时会通过系统内核建立一个rtsig队列用于存放标记事件发生的信号,每个事件发生时,系统内核就会产生一个信号存放到rtsig队列中等待Nginx工作进程的处理 |
kqueue |
Kqueue模型也是poll模型的一个变种,和上面介绍的epoll模型的处理方式几乎一样,都是通过避免轮询操作提供效率。该模型支持在BSD系列操作系统(例如FreeBSD 、OpenBSD 、NetBSD 等)上使用 |
/dev/poll |
/dev/poll模型一般用于solaris操作系统或者其他的unix衍生操作系统上,该模型最早是Sun公司在开发Solaris系列平台时提出的用于完成事件驱动机制的方案,它使用了虚拟的/dev/poll设备,开发人员可以将要监视的文件描述符加入这个设备,然后通过调用ioctl()函数来获取事件通知 |
eventport |
该模型也是Sun公司在开发Solaris系列平台时提出的、用于完成事件驱动机制的方案,它可以有效防止内核崩溃情况的发生,Nginx在此基础上提供了事件处理支持,但是由于Solaris自身后来的没落,所以该模型现在也很少使用。 |
1.4 Nginx客户端连接数的优化
在高并发的请求调用中,连接数有时候很容易成为性能的一个瓶颈,Nginx中可以通过如下方式来调整Nginx的连接数。
events #可以设置Nginx的工作模式以及连接数上限 { worker_connections 1024; }
worker_processes 2; worker_rlimit_nofile 2048; # 设置worker进程可以打开的文件数
1.5 Nginx中文件传输的优化
Nginx中文件传输一般需要优化的是如表中所示的几个参数。
Nginx文件传输需要优化的参数
Nginx参数 |
说明 |
tcp_nopush |
tcp_nopush和tcp_nodelay是互斥的,开启tcp_nopush后会设置调用tcp_cork方法,让数据包不会马上传送出去,等到数据包累积到最大时,一次性的传输出去,这样有助于解决网络堵塞,从而提供网络传输的性能,默认为off。设置为on时的配置如下: tcp_nopush on; |
tcp_nodelay |
和tcp_nopush的处理方式刚好相反,在开启了tcp_nodelay后意味着无论数据包是多么的小,都立即发送出去,默认值为on,设置为off时为关闭,配置的方式如下: tcp_nodelay off; |
sendfile |
sendfile 一般和tcp_nopush选项搭配在一起使用。Nginx中提供 sendfile 选项用来提高服务器性能,sendfile实际上是 Linux2.0版本以后推出的一个系统调用。在网络文件传输过程中一般是:从硬盘读写 → 写入操作系统内核 buffer → 读入用户模式 buffer-> 写入内核socket buffer →协议栈开始传输,在开启了sendfile后,之前的传输步骤就简化成了:从硬盘读写 → 写入内核 buffer (数据可以快速拷贝到内核 socket buffer) →协议栈开始传输,这就是常说的零拷贝方式。开启sendfile的配置如下: sendfile on; |
nginx gzip压缩相关参数: gzip on gzip_min_length gzip_buffers gzip_http_version gzip_comp_level gzip_types gzip_vary gzip_proxied off |
gzip on:开启gzip 压缩模式 gzip_min_length :设置允许压缩的页面最小宇节数,字节数大小 从HTTP请求的header头部的Content-Length中获取。默认值是0,表示不管页面多大都进行压缩。一般建议设置成gzip_min_length 1K,因为如果小于1K可能会越压越大。 gzip_buffers:表示压缩缓冲区大小,例如设置gzip_buffers 4 16k; 表示申请4个单位为16K的内存作为压缩结果数据流的缓存,默认值是申请与原始数据大小相同的内存空间来存储gzip压缩结果。 gzip_http_version:表示压缩的HTTP协议版本,默认为1.1,常用的浏览器几乎都支持gzip解压,一般使用默认设置即可。 gzip_comp_level:表示gzip的压缩比率,该参数用来指定gzip压缩比,可以设置为1-9之间的数字。1表示压缩比最小但是压缩处理速度最快,9表示压缩比最大并且传输速度快,但处理时耗时最长也比较消耗CPU资源。该参数一般建议根据实际场景中传输的大部分文件大小来设置,在压缩处理速度和压缩比率大小之间做到均衡。 gzip_types:用来设置指定压缩的类型(就是HTTP协议中的Content-Type属性中的媒体类型), 支持的常见类型包括 text/plain、application/x-javascript 、text/css、 application/xml、application/json等。 gzip_vary:表示启用response header头部属性“Vary:Accept-Encoding”的压缩模式。[z1] zip_proxied off:默认为off,一般在Nginx作为反向代理的时候启用,表示对代理的结果数据进行压缩,zip_proxied可以在后面增加参数来判断HTTP的header头部中符合指定条件后才进行数据压缩。 loff :关闭所有的代理结果数据的压缩。 lexpired :启用压缩,如果header头部中包含 "Expires" 头信息。 lno-cache:启用压缩,如果header头部中包含 "Cache-Control:no-cache" 头信息。 lno-store:启用压缩,如果header头部中包含 "Cache-Control:no-store" 头信息。 lprivate:启用压缩,如果header头部中包含"Cache-Control:private" 头信息。 lno_last_modified:启用压缩,如果header头部中不包含 "Last-Modified" 头信息。 lno_etag:启用压缩,如果header头部中不包含 "ETag" 头信息。 lauth:启用压缩,如果header头部中包含 "Authorization" 头信息。 lany:表示总是启用压缩,表示会对返回的代理数据都进行压缩。 |
[z1]
在nginx.conf配置文件中开启sendfile参数的方式配置示例如下:
sendfile on ;#默认情况下,sendfile是off。
在nginx.conf配置文件中开启tcp_nopush参数的方式配置示例如下:
tcp_nopush on ;#默认情况下tcp_nopush是off
在nginx.conf配置文件中开启tcp_nodelay参数的方式配置示例如下:
tcp_nodelay on;#默认情况下tcp_nodelay是on
1.6 Nginx中FastCGI配置的优化
FastCGI是在CGI基础上的优化升级,CGI是Web服务器与CGI程序间传输数据的一种标准,运行在服务器上的CGI程序按照这个协议标准提供了传输接口,具体介绍如下。
Nginx本身并不支持对外部动态程序的直接调用或者解析,所有的外部编程语言编写的程序(比如Python、PHP)必须通过FastCGI接口才能调用。FastCGI相关参数说明如表所示。
FastCGI相关参数说明
Nginx FastCGI相关参数 |
说明 |
fastcgi_connect_timeout |
用于设置Nginx服务器和后端FastCGI程序连接的超时时间,默认值值为60秒,一般建议不要超过75秒,时间太长会导致高并发调用下建立连接过多而不能及时释放,建立的连接越多消耗的资源就会越多 |
fastcgi_send_timeout |
用于设置Nginx发送CGI请求到FastCGI程序的超时时间,这个超时时间不是整个请求的超时时间,而是两个成功请求的之间间隔时间为超时时间,如果这个时间内FastCGI服务没有收到任何信息连接将被关闭 |
fastcgi_read_timeout |
用于设置Nginx从FastCGI服务器读取响应信息的超时时间,连接建立成功后 Nginx等待后端FastCGI程序的响应时间,实际上是读取FastCGI响应成功消息的间隔时间,如果这个时间内Nginx没有再次从FastCGI读取到响应消息连接就会被关闭 |
fastcgi_buffer_size |
用于设置Nginx FastCGI的缓冲区的大小,缓冲区大小表示的是读取从FastCGI程序端收到的第一部分响应信息的缓冲区大小,这里的第一部分通常会包含一个小的响应头部消息,默认情况下这个参数的大小等价于操作系统的1个内存页单位的大小,一般是4k或者8k, 在不同的操作系统上默认大小可能不同 |
fastcgi_buffers |
用于设置Nginx用多少和多大的缓冲区读取从FastCGI程序收到的响应信息,默认值为fastcgi_buffer 8 4k|8k;可以在nginx.conf配置文件的的http、server、location这三个字段中使用。 一般用于指定Web服务器本地需要用多少和多大的缓冲区来缓冲FastCGI的应答请求,比如如果一个fastcgi程序的响应消息大小为12k,那么配置为fastcgi_buffers 6 4k时将分配3个4k的buffer |
fastcgi_busy_buffers_size |
用于设置服务器很忙时可以使用的fastcgi_buffers大小,一般推荐的大小为fastcgi_buffers*2;默认值为 fastcgi_busy_buffers_size 8k|16k |
fastcgi_temp_file_write_size |
用于设置FastCGI临时文件的大小,一般建议可以设置为128~256KB ,默认大小为fastcgi_temp_file_write_size 8k|16k; |
fastcgi_cache myboy_nginx |
用于设置开启FastCGI缓存功能并为其指定一个名称(比如指定为myboy_nginx)。开启缓存可以有效降低CPU的负载并且可以防止HTTP请求的502错误(HTTP服务器端的一个错误码,一般是指上游服务器接收到无效的响应)的发生,而且合理设置该参数可以有效的提高请求的并发数和tps |
fastcgi_cache_path |
用于设置fastcgi_cache的缓存路径。 例如可以设置为:fastcgi_cache_path /opt/data/nginx/cache levels = 2:2 keys_zone = nginx_ fastcgi_cache:256m inactive = 2d max_size=80g use_temp_path=on;fastcgi_cache的缓存路径可以设置目录前列层级,比如2:2会生成256*256 个子目录,keys_zone是设置这个缓存空间的名字以及使用多少物理内存(一般经常被访问的热点数据Nginx会直接放入内存以提高访问速度),nginx_ fastcgi _cache:256m表示缓存空间的名字为nginx_ fastcgi_cache,大小为256m。inactive表示默认失效时间,max_size表示最多用多少硬盘空间来存储缓存,特别要注意的是fastcgi_cache缓存是先写在fastcgi_temp_path,等到达一定大小后再移到fastcgi_cache_path下去的,所以这个两个目录最好在同一个磁盘分区下以提高I/O读写速度, 如果设置use_temp_path=off则 nginx 会将缓存文件直接写入指定的 cache 文件中 |
fastcgi_temp_path |
用于设置fastcgi_cache的临时目录路径,例如可以设置为fastcgi_temp_path /usr/local/nginx/fastcgi_temp |
fastcgi_cache_valid |
用于对不同的HTTP 请求响应状态码设置缓存的时长。 例如:fastcgi_cache_valid 200 302 lh; 表示将HTTP 请求响应状态码为200和302的应答缓存1个小时。 再例如:fastcgi_cache_valid 301 2d; 表示将HTTP 请求响应状态码为301的应答缓存2天 |
fastcgi_cache_min_uses |
用于设置同一请求达到几次之后晌应消息将被缓存。 例如:fastcgi_cache_min_uses 1;1表示一次即被缓存 |
fastcgi_cache_use_stale |
用于设置哪些状态下使用过期缓存。 例如:fastcgi_cache_use_stale error timeout invalid_header http_500 表示在发生error错误、响应超时、HTTP的请求头无效和HTTP请求返回状态码为500时使用过期缓存 |
fastcgi_cache_key |
用于设定fastcgi_cache中的key值。 例如:fastcgi_cache_key scheme$request_method$host$request_uri 表示以请求的URI作为缓存的key,Nginx会取这个key的md5作为缓存文件,如果设置了缓存的目录,Nginx会从后往前取对应的位数作为目录。建议一定要加上$request_method变量一起作为cache key,防止如果先请求的为head 类型(HTTP请求的一种类型,类似于get请求,只不过返回的响应中没有具体的响应内容,仅用于获取响应报文的头部信息),后面的GET请求将返回为空 |
1.7 Nginx的监控
Nginx自带了监控模块,但是需要在Nginx编译安装时指定安装监控模块,默认情况下是不会安装该监控模块的,需要指定的编译参数为--with-http_stub_status_module。
编译安装完成后,Nginx的配置文件nginx.conf中还是不会开启监控,需要在配置文件中增加如下配置,其中allow 192.168.1.102代表允许访问监控页面的ip地址,如图所示。
location = /nginx_status { stub_status on; access_log off; allow 192.168.1.102; deny all; }
修改完配置文件后,通过执行nginx –s reload来重新加载配置信息,然后通过访问http://nginx服务器ip地址:端口号/nginx_status 就可以进入监控页面了,如图所示。
从图中可以看到当前已经建立的连接数、服务器已经接收的请求数、请求的处理情况等监控信息。
2、Apache的性能分析和调优
在Web中间件中除了Nginx外另一个用的最多的中间件就是Apache了,Apache几乎可以运行在所有的操作系统中,支持HTTP、SSL、Socket、FastCGI、SSO、负载均衡、服务器代理等很多功能模块,在性能测试分析中如果对Apache使用不当,那么Apache有时候也可能会成为高并发访问的瓶颈。
2.1 Apache的工作模式选择和进程数调优
Apache的工作模式主要是指Apache在运行时内存分配、CPU、进程以及线程的使用管理和请求任务的调度等。Apache比较稳定的工作模式有prefork模式、worker模式、event模式,这三种模式也是Apache经常使用的模式。Apache默认使用的是prefork模式,一般可以在编译安装Apache时通过参数--with-mpm来指定安装后使用的工作模式。
可以通过执行httpd –V命令来查看Apache当前使用的工作模式,如图所示可以看到当前的工作模式为默认的prefork模式。
1. prefork模式
prefork是Apache的默认工作模式,采用非线程型的预派生方式来处理请求,在工作时使用多进程,每个进程在同一个固定的时间只单独处理一个连接,这种方式效率高但由于是多进程的方式所以内存使用比较大。如图所示,可以看到prefork模式下启动了多个进程。
prefork工作模式在收到请求后的处理过程如图所示,从图中可以看到处理过程是单进程和单线程的方式,由于不存在线程安全问题所以这种模式非常适合于没有线程安全库,需要避免线程安全性问题的系统。虽然解决了线程安全问题,但是也必然会导致无法处理高并发请求的场景,prefork模式会将请求放进队列中,一直等到有可用子进程请求才会被处理,也很容易导致请求队列积压。