Nginx服务器性能优化的三大方面

Nginx服务器非常快,但是Nginx的默认设置并没有针对具体的硬件进行调优。在这篇文章中,我们要把Nginx的性能发挥到极限。Nginx的配置分为三大部分:worker进程配置、I/O配置、TCP配置。我们将分别对这三大配置展开讨论,并在最后给出综合性的配置。

Nginx的worker进程配置

worker_processes

worker_processes directive指定nginx worker进程的数量。它是一个全局性配置,不属于events模块,也不属于http或location模块。

worker_processes 1;

默认的值是1,意味着nignx只打开一个worker进程。最优的设置是worker进程数量要与CPU的核数相等。我们可以用lscpu命令来找出CPU的核数。

 lscpu

也可以用

cat /proc/cpuinfo | grep 'processor' | wc -l

另外,我们也可以将worker_processes的值设为auto,这样nginx会自动检测CPU核数并打开相同数量的worker进程。

当nginx添加了SSL证书时,最好要打开多个worker进程。SSL握手会进行硬盘I/O操作。 所以打开多个worker进程有利于性能的提升。

accept_mutex

当我们为nginx打开了多个worker进程后,我们需要配置如何选择worker进程来完成相应的请求处理。在events模块中,我们可以设置

events {
 accept_mutex on;
 }

accept_mutex会轮流来选择worker进程。Nginx默认开启了accept_mutex。

如果accept_mutex的值被设为off,那么当有请求需要处理时,所有的worker进程都会从waiting状态中唤醒,但是只有一个worker进程能处理请求,这造成了thundering herd现象,这个现象每一秒钟会发生多次。它使服务器的性能下降,因为所有被唤醒的worker进程在重新进入waiting状态前会占用一段CPU时间。

accept_mutex_delay

当accept_mutex功能启用后,只有一个持有mutex锁的worker进程会接受并处理请求,其他worker进程等待。accept_mutex_delay指定的时间就是这些worker进程的等待时间,过了等待时间下一个worker进程便取得mutex锁,处理请求。accept_mutex_delay在events模块中指定,默认的值为500ms。

events {
 accept_mutex_delay 500ms;
 }

worker_connections

worker_connections的默认值是512,它在events模块中。它指定了一个worker进程在同一时间可以处理的最大请求数。

events {
 worker_connections 512;
 }

将它的值增加到1024左右。

web服务器同时处理的请求数并不等于它同时服务的客户端数量。一个浏览器会打开多个并发连接来下载网页的各个部分,如图片、脚本等等。而且不同的浏览器对同一个网页打开的并发连接数量也会有所不同。

worker_rlimit_nofile

由于每一个socket都会打开一个文件描述符,所以服务器可以同时处理连接数量受到系统文件描述符数量的限制。如果nginx打开的socket数量超过了文件描述符的数量,那么在error.log文件中会出现too many opened files错误。我们可以用下面的命令来查看文件描述符的数量:

$ ulimit -n

Nginx worker进程默认的用户名是www-data,用户www-data所拥有的文件描述符的数量要大于worker进程数量与worker_connections之乘积。 nginx有一个worker_rlimit_nofile directive,可以用来设置系统可用的文件描述符。这与ulimit设置可用文件描述符的作用是一样的。如果它们都设置了可用文件描述符,那么worker_rlimit_nofile会覆盖ulimit的设置。

worker_rlimit_nofile 20960;

查看操作系统对一个进程施加的限制,我们可以用命令读取/etc/$pid/limits文件,$pid是进程的pid。

multi_accept

multi_accept可以让nginx worker进程尽可能多地接受请求。它的作用是让worker进程一次性地接受监听队列里的所有请求,然后处理。如果multi_accept的值设为off,那么worker进程必须一个一个地接受监听队列里的请求。

events {
 multi_accept on;
 }

默认Nginx没有开启multi_accept。

如果web服务器面对的是一个持续的请求流,那么启用multi_accept可能会造成worker进程一次接受的请求大于worker_connections指定可以接受的请求数。这就是overflow,这个overflow会造成性能损失,overflow这部分的请求不会受到处理。

use

Nginx处理请求的方法有很多种,每一个方法都允许Nginx Worker进程监测多个socket文件描述符。这些方法都分别依赖于特定的平台,用于生成Nginx二进制文件的configure命令会选择适合当前平台的最有效的方法。如果要使用另外的方法,那么我们必须先启用这些方法。

我们可以用use这个directive来选择另外的处理请求的方法。use directive属于events模块。

events {
 use select;
 }

Nginx支持以下请求处理方法:

  • select: 这是一种标准的请求处理方法。如果一个平台上缺少相应的更加有效的方法,那么Nginx会自动使用select方法。
  • poll: 这是一种标准的请求处理方法。如果一个平台上缺少相应的更加有效的方法,那么Nginx会自动使用poll方法。
  • kqueue: 这是BSD家族操作系统上可用的一种高效的请求处理方法。可用于FreeBSD, OpenBSD, NetBSD和OS X。kqueue方法会忽略multi_accept。
  • epoll: 这是Linux系统上可用的一种高效的请求处理方法,类似于kqueue。它有一个额外的directive,那就是epoll_events。epoll_events指定了Nginx可以向内核传递的事件数量。默认的值是512。

Nginx的I/O配置

sendfile

当一个程序需要传输文件时,Linux内核首先将文件数据缓冲,然后将文件数据传送给程序缓冲,最后程序将文件数据传输到目的地。Sendfile方法是一种数据传输的更高效的方法,数据在内核中的文件描述符之间传输,而不需要将数据传输给程序缓冲。这种方法的结果是改善了对操作系统资源的利用。

我们可以用sendfile directive来启用sendfile方法,在http,server,location三个模块都可以定义。

http {
 sendfile on ;
 }

默认情况下,sendfile 的值是on。

Direct I/O

通常,Linux内核会尝试优化和缓存读写请求。数据缓存在内核里,将来任何的读取相同数据的请求会变得更快,因为不需要从缓慢的硬盘中读取数据。

Direct I/O是文件系统的一个功能,它允许程序绕过内核缓存,直接在硬盘上读写数据,这可以充分利用CPU频数,改善缓存的有效性。Directo I/O适用于访问率少的数据。这些数据不需要缓存在任何位置。我们可以用directio directive来启用这一功能,在http, server和location当中定义。

location /video/ {
 directio 4m;
 }

在上面的设置中,任何大于4M的文件都将以Direct I/O的形式直接从硬盘读取。默认directio没有启用。

如果某个请求是通过directo I/O,那么这个请求不能使用sendfile功能。

Direct I/O取决于硬盘的块大小。Nginx可以使用directio_alignment directive来设置块大小,在http, server, location中定义

location /video/ {
 directio 4m;
 directio_alignment 512;
 }

512字节适用于大多数情况。如果Linux文件系统是XFS,那么块大小应该为4KB。

异步I/O

异步I/O允许一个进程在不阻塞和等待的情况下进行I/O操作。

aio在http, server和location中定义。Linux2.6.22+和FreeBSD4.3支持aio。

location /data {
 aio on;
 }

默认情况下,aio是关闭的。在Linux发行版上,要先启用directio后才能启用aio。在FreeBSD上,必须先关闭sendfile才能让aio生效。

如果Nginx没有–with-file-aio这个模块,那么启用aio会导致unknown directive aio错误。

aio可以指定线程数,这个多线程功能只在Linux发行版上才可用,并且只能在epoll, kqueue 或 eventport方法下可用。

为了使用多线程,在编译Nginx时要添加–with-threads选项。然后在/etc/nignx/nignx.conf文件中使用一个全局设置。

thread_pool io_pool threads=16;
 http {
 ....
 location /data {
 sendfile on;
 aio threads=io_pool;
 }
 }

综合I/O设置

这三个directive可以综合起来实现不同的目标。在下面的配置中,如果文件的大小小于directio指定的大小,那么使用sendfile功能。以异步I/O的方式读取directio服务的文件。

location /archived-data/ {
 send file on;
 aio on;
 directio 4m;
 }

Nginx的TCP配置

HTTP是一个应用层协议,在传输层使用TCP协议。在TCP协议中,数据是以一块一块的TCP数据包传输的。Nginx提供了多个directive,可以用来调整TCP栈。

TCP_NODELAY

TCP/IP网络有一个“小数据包”的问题,如果一条信息中只有一个字符,那么网络可能会堵塞。这样的数据包大小为41字节,其中TCP信头40字节,内容为1字节。像这种小数据包,它们的开销是4000%。大量的小数据包可以迅速让网络饱和。

John Nagle发明了Nagle算法,它在一定的时间段,将小数据包暂存,将这些小数据包集合起来,整合为一个数据包发送,在下一个时间段又是如此。这改善了网络传输的效率。时间段通常为200ms。

但值得注意的是,小数据包的问题在telnet的通信过程中仍然存在。在telnet中,每一次敲键都分别在网络发送。不过这跟web服务器没有关联。web服务器发送的文件大多是大数据包,所以它们会被立即发送,而不需要等待200ms。

TCP_NODELAY可以用来关闭Nagle的缓冲算法,将数据尽快地发送。Nginx可以在http, server, location当中定义

 http {
 tcp_nodelay on;
 }

Nginx默认启用了tcp_nodelay。Nginx在keep-alive模式下会使用tcp_nodelay。

TCP_CORK

除了Nagle算法外,Linux内核提供了一个TCP_CORK选项,也可以解决小数据包问题。TCP_CORK告诉TCP栈将数据包附加在另外一个数据包,当数据包饱和时或程序明确地指示发送时再发送。在FreeBSD和Mac OS系统上,TCP_NOPUSH选项相当于TCP_CORK。

Nginx可以在http, server和location模块中定义tcp_nopush。

http {
 tcp_nopush on;
 }

Nginx默认启用了tcp_nopush

上面的两个directives负责不同的任务。tcp_nodelay可以降低网络延迟,而tcp_nopush可以优化发送的数据包。

同时启用tcp_nopush和sendfile可以确保在传输文件时,内核总是生成最大量的完整TCP数据包以供传输,而最后一个TCP数据包可以是不完整的,这个不完整的数据包

大综合

下面的配置综合了前面所讨论的worker进程配置、I/O配置、TCP配置。

worker_processes 1;     #假设CPU为单核
worker_rlimit_nofile 8000;

events {
 multi_accept on;
 use epoll;
 worker_connections 1024;
 }

http {
 sendfile on;
 aio on;
 directio 4m;
 tcp_nopush on;
 tcp_nodelay on;
 }

做完上述配置后,重启nginx

sudo service nginx restart

性能测试对比

做上述修改前,主页负载测试结果如下,平均花费4814毫秒加载完主页。

Nginx服务器性能优化

做修改后,主页负载测试结果如下,平均花费3851毫秒加载完主页,节约1秒钟。不过从下图中可以看到虽然样本时间下降了,延迟却有所上升。当连接数少时,延迟在500ms左右,而当连接数多时,延迟却升到了2000多。

Nginx服务器性能优化

为这篇文章评分
[Total: 6 Average: 3.2]

发表评论

电子邮件地址不会被公开。 必填项已用*标注