Nginx Aio

Nginx的AIO
http://nightsailer.com/2010/12/16/827.html

Nginx支持Linux Native AIO,因此我考虑是否使用AIO能够大大降低IO-Wait? 性能应该有比较明显的提升?
网上有一些资料,吹嘘的Nginx AIO性能提升,神奇云云。我有点将信将疑,因为都没有任何的测试数据比较,
均是人云亦云。另外,多数配置都是或多或少有问题的。

我使用的CentOS, Nginx AIO要使用,必须是CentOS 5.5以上。因为只有5.5的kernel才有AIO的backport,nginx并没有使用libaio。
此外,Nginx的AIO本来是为FreeBSD开发,Linux固然可以使用,不过受到了Linux AIO的很多限制。
1. 必须使用Direct IO. 这样一来,导致无法使用vm的disk cache.
2. 文件只有大小和directio_alignment定义block size整数倍的数据才可以使用AIO,当文件整数据块之前和之后,那些不能取整的部分则是blocking方式读取的,这也是为什么需要output-buffer。directio_alignment大小取决于你使用的文件系统,默认是512,而对于XFS,注意,如果你没有修改XFS bsize, 需要调整为XFS默认的4k.

我使用的配置如下:
location /archive {
internal;
aio on;
directio 4k;
directio_alignment 4k;
output_buffers 1 128k;
}

当启用AIO后,可以看到vmstat中,cache的内存消耗迅速降低,这是因为使用AIO必须使用directio,这就绕过了vm的diskcache。

实际性能如何,AIO一定很快么? 这点即便是Igor也不确定。

从我们自己的实际效果看,AIO并没有明显的性能提升,相反,偶尔会轻微增加了IO-Wait,这是因为无法利用diskcache,
而如果文件多数又和directio_alignment有偏差(尤其是断点续传的时候,多数文件读取位置在directio_alignment数据边界外),这部分的数据必须使用blocking io读取,又没有disk cache,增加IO-Wait也可以理解。

最终,结论是,与其使用不那么靠谱的Nginx AIO, 不如多开一些Nginx的worker,重复利用vm disk cache, 当内存100%利用率的时候,nginx的静态文件分发效率是高于AIO模式的。

BTW,这个实际用例也重新印证了我的一个观点,不要轻信网上那些毫无测试数据的忽悠,多数都是copy & paste的传说, 各个说好,其实多数都没实际印证过。

Nginx线程池

NGINX引入线程池 性能提升9倍
作者 Valentin Bartenev ,译者 韩陆
1. 引言正如我们所知,NGINX采用了异步、事件驱动的方法来处理连接。这种处理方式无需(像使用传统架构的服务器一样)为每个请求创建额外的专用进程或者线程,而是在一个工作进程中处理多个连接和请求。为此,NGINX工作在非阻塞的socket模式下,并使用了epoll 和 kqueue这样有效的方法。
因为满负载进程的数量很少(通常每核CPU只有一个)而且恒定,所以任务切换只消耗很少的内存,而且不会浪费CPU周期。通过NGINX本身的实例,这种方法的优点已经为众人所知。NGINX可以非常好地处理百万级规模的并发请求。

每个进程都消耗额外的内存,而且每次进程间的切换都会消耗CPU周期并丢弃CPU高速缓存中的数据。
但是,异步、事件驱动方法仍然存在问题。或者,我喜欢将这一问题称为“敌兵”,这个敌兵的名字叫阻塞(blocking)。不幸的是,很多第三方模块使用了阻塞调用,然而用户(有时甚至是模块的开发者)并不知道阻塞的缺点。阻塞操作可以毁掉NGINX的性能,我们必须不惜一切代价避免使用阻塞。
即使在当前官方的NGINX代码中,依然无法在全部场景中避免使用阻塞,NGINX1.7.11中实现的线程池机制解决了这个问题。我们将在后面讲述这个线程池是什么以及该如何使用。现在,让我们先和我们的“敌兵”进行一次面对面的碰撞。
相关厂商内容
提高工程效率的各种最佳实践和典型思路 知道创宇技术副总裁余弦将担任QCon北京2016出品人 QCon北京2016大会,4月21-23日,与您相约北京国际会议中心,现在报名享8折优惠! 相关赞助商

QCon北京2016大会,4月21-23日,北京·国际会议中心,精彩内容邀您参与!
2. 问题首先,为了更好地理解这一问题,我们用几句话说明下NGINX是如何工作的。
通常情况下,NGINX是一个事件处理器,即一个接收来自内核的所有连接事件的信息,然后向操作系统发出做什么指令的控制器。实际上,NGINX干了编排操作系统的全部脏活累活,而操作系统做的是读取和发送字节这样的日常工作。所以,对于NGINX来说,快速和及时的响应是非常重要的。

工作进程监听并处理来自内核的事件
事件可以是超时、socket读写就绪的通知,或者发生错误的通知。NGINX接收大量的事件,然后一个接一个地处理它们,并执行必要的操作。因此,所有的处理过程是通过一个线程中的队列,在一个简单循环中完成的。NGINX从队列中取出一个事件并对其做出响应,比如读写socket。在多数情况下,这种方式是非常快的(也许只需要几个CPU周期,将一些数据复制到内存中),NGINX可以在一瞬间处理掉队列中的所有事件。

所有处理过程是在一个简单的循环中,由一个线程完成
但是,如果NGINX要处理的操作是一些又长又重的操作,又会发生什么呢?整个事件处理循环将会卡住,等待这个操作执行完毕。
因此,所谓“阻塞操作”是指任何导致事件处理循环显著停止一段时间的操作。操作可以由于各种原因成为阻塞操作。例如,NGINX可能因长时间、CPU密集型处理,或者可能等待访问某个资源(比如硬盘,或者一个互斥体,亦或要从处于同步方式的数据库获得相应的库函数调用等)而繁忙。关键是在处理这样的操作期间,工作进程无法做其他事情或者处理其他事件,即使有更多的可用系统资源可以被队列中的一些事件所利用。
我们来打个比方,一个商店的营业员要接待他面前排起的一长队顾客。队伍中的第一位顾客想要的某件商品不在店里而在仓库中。这位营业员跑去仓库把东西拿来。现在整个队伍必须为这样的配货方式等待数个小时,队伍中的每个人都很不爽。你可以想见人们的反应吧?队伍中每个人的等待时间都要增加这些时间,除非他们要买的东西就在店里。

队伍中的每个人不得不等待第一个人的购买
在NGINX中会发生几乎同样的情况,比如当读取一个文件的时候,如果该文件没有缓存在内存中,就要从磁盘上读取。从磁盘(特别是旋转式的磁盘)读取是很慢的,而当队列中等待的其他请求可能不需要访问磁盘时,它们也得被迫等待。导致的结果是,延迟增加并且系统资源没有得到充分利用。

一个阻塞操作足以显著地延缓所有接下来的操作
一些操作系统为读写文件提供了异步接口,NGINX可以使用这样的接口(见AIO指令)。FreeBSD就是个很好的例子。不幸的是,我们不能在Linux上得到相同的福利。虽然Linux为读取文件提供了一种异步接口,但是存在明显的缺点。其中之一是要求文件访问和缓冲要对齐,但NGINX很好地处理了这个问题。但是,另一个缺点更糟糕。异步接口要求文件描述符中要设置O_DIRECT标记,就是说任何对文件的访问都将绕过内存中的缓存,这增加了磁盘的负载。在很多场景中,这都绝对不是最佳选择。
为了有针对性地解决这一问题,在NGINX 1.7.11中引入了线程池。默认情况下,NGINX+还没有包含线程池,但是如果你想试试的话,可以联系销售人员,NGINX+ R6是一个已经启用了线程池的构建版本。
现在,让我们走进线程池,看看它是什么以及如何工作的。
3. 线程池让我们回到那个可怜的,要从大老远的仓库去配货的售货员那儿。这回,他已经变聪明了(或者也许是在一群愤怒的顾客教训了一番之后,他才变得聪明的?),雇用了一个配货服务团队。现在,当任何人要买的东西在大老远的仓库时,他不再亲自去仓库了,只需要将订单丢给配货服务,他们将处理订单,同时,我们的售货员依然可以继续为其他顾客服务。因此,只有那些要买仓库里东西的顾客需要等待配货,其他顾客可以得到即时服务。

传递订单给配货服务不会阻塞队伍
对NGINX而言,线程池执行的就是配货服务的功能。它由一个任务队列和一组处理这个队列的线程组成。
当工作进程需要执行一个潜在的长操作时,工作进程不再自己执行这个操作,而是将任务放到线程池队列中,任何空闲的线程都可以从队列中获取并执行这个任务。

工作进程将阻塞操作卸给线程池
那么,这就像我们有了另外一个队列。是这样的,但是在这个场景中,队列受限于特殊的资源。磁盘的读取速度不能比磁盘产生数据的速度快。不管怎么说,至少现在磁盘不再延误其他事件,只有访问文件的请求需要等待。
“从磁盘读取”这个操作通常是阻塞操作最常见的示例,但是实际上,NGINX中实现的线程池可用于处理任何不适合在主循环中执行的任务。
目前,卸载到线程池中执行的两个基本操作是大多数操作系统中的read()系统调用和Linux中的sendfile()。接下来,我们将对线程池进行测试(test)和基准测试(benchmark),在未来的版本中,如果有明显的优势,我们可能会卸载其他操作到线程池中。
4. 基准测试现在让我们从理论过度到实践。我们将进行一次模拟基准测试(synthetic benchmark),模拟在阻塞操作和非阻塞操作的最差混合条件下,使用线程池的效果。
另外,我们需要一个内存肯定放不下的数据集。在一台48GB内存的机器上,我们已经产生了每文件大小为4MB的随机数据,总共256GB,然后配置NGINX,版本为1.9.0。
配置很简单:
worker_processes 16;

events {
accept_mutex off;
}

http {
include mime.types;
default_type application/octet-stream;

access_log off;
sendfile on;
sendfile_max_chunk 512k;

server {
listen 8000;

location / {
root /storage;
}
}
}如上所示,为了达到更好的性能,我们调整了几个参数:禁用了logging和accept_mutex,同时,启用了sendfile并设置了sendfile_max_chunk的大小。最后一个指令可以减少阻塞调用sendfile()所花费的最长时间,因为NGINX不会尝试一次将整个文件发送出去,而是每次发送大小为512KB的块数据。
这台测试服务器有2个Intel Xeon E5645处理器(共计:12核、24超线程)和10-Gbps的网络接口。磁盘子系统是由4块西部数据WD1003FBYX 磁盘组成的RAID10阵列。所有这些硬件由Ubuntu服务器14.04.1 LTS供电。

为基准测试配置负载生成器和NGINX
客户端有2台服务器,它们的规格相同。在其中一台上,在wrk中使用Lua脚本创建了负载程序。脚本使用200个并行连接向服务器请求文件,每个请求都可能未命中缓存而从磁盘阻塞读取。我们将这种负载称作随机负载。
在另一台客户端机器上,我们将运行wrk的另一个副本,使用50个并行连接多次请求同一个文件。因为这个文件将被频繁地访问,所以它会一直驻留在内存中。在正常情况下,NGINX能够非常快速地服务这些请求,但是如果工作进程被其他请求阻塞的话,性能将会下降。我们将这种负载称作恒定负载。
性能将由服务器上ifstat监测的吞吐率(throughput)和从第二台客户端获取的wrk结果来度量。
现在,没有使用线程池的第一次运行将不会带给我们非常振奋的结果:
% ifstat -bi eth2
eth2
Kbps in Kbps out
5531.24 1.03e+06
4855.23 812922.7
5994.66 1.07e+06
5476.27 981529.3
6353.62 1.12e+06
5166.17 892770.3
5522.81 978540.8
6208.10 985466.7
6370.79 1.12e+06
6123.33 1.07e+06如上所示,使用这种配置,服务器产生的总流量约为1Gbps。从下面所示的top输出,我们可以看到,工作进程的大部分时间花在阻塞I/O上(它们处于top的D状态):
top - 10:40:47 up 11 days, 1:32, 1 user, load average: 49.61, 45.77 62.89
Tasks: 375 total, 2 running, 373 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.0 us, 0.3 sy, 0.0 ni, 67.7 id, 31.9 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem: 49453440 total, 49149308 used, 304132 free, 98780 buffers
KiB Swap: 10474236 total, 20124 used, 10454112 free, 46903412 cached Mem

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
4639 vbart 20 0 47180 28152 496 D 0.7 0.1 0:00.17 nginx
4632 vbart 20 0 47180 28196 536 D 0.3 0.1 0:00.11 nginx
4633 vbart 20 0 47180 28324 540 D 0.3 0.1 0:00.11 nginx
4635 vbart 20 0 47180 28136 480 D 0.3 0.1 0:00.12 nginx
4636 vbart 20 0 47180 28208 536 D 0.3 0.1 0:00.14 nginx
4637 vbart 20 0 47180 28208 536 D 0.3 0.1 0:00.10 nginx
4638 vbart 20 0 47180 28204 536 D 0.3 0.1 0:00.12 nginx
4640 vbart 20 0 47180 28324 540 D 0.3 0.1 0:00.13 nginx
4641 vbart 20 0 47180 28324 540 D 0.3 0.1 0:00.13 nginx
4642 vbart 20 0 47180 28208 536 D 0.3 0.1 0:00.11 nginx
4643 vbart 20 0 47180 28276 536 D 0.3 0.1 0:00.29 nginx
4644 vbart 20 0 47180 28204 536 D 0.3 0.1 0:00.11 nginx
4645 vbart 20 0 47180 28204 536 D 0.3 0.1 0:00.17 nginx
4646 vbart 20 0 47180 28204 536 D 0.3 0.1 0:00.12 nginx
4647 vbart 20 0 47180 28208 532 D 0.3 0.1 0:00.17 nginx
4631 vbart 20 0 47180 756 252 S 0.0 0.1 0:00.00 nginx
4634 vbart 20 0 47180 28208 536 D 0.0 0.1 0:00.11 nginx
4648 vbart 20 0 25232 1956 1160 R 0.0 0.0 0:00.08 top
25921 vbart 20 0 121956 2232 1056 S 0.0 0.0 0:01.97 sshd
25923 vbart 20 0 40304 4160 2208 S 0.0 0.0 0:00.53 zsh在这种情况下,吞吐率受限于磁盘子系统,而CPU在大部分时间里是空闲的。从wrk获得的结果也非常低:
Running 1m test @ http://192.0.2.1:8000/1/1/1
12 threads and 50 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 7.42s 5.31s 24.41s 74.73%
Req/Sec 0.15 0.36 1.00 84.62%
488 requests in 1.01m, 2.01GB read
Requests/sec: 8.08
Transfer/sec: 34.07MB请记住,文件是从内存送达的!第一个客户端的200个连接创建的随机负载,使服务器端的全部的工作进程忙于从磁盘读取文件,因此产生了过大的延迟,并且无法在合理的时间内处理我们的请求。
现在,我们的线程池要登场了。为此,我们只需在location块中添加aio threads指令:
location / {
root /storage;
aio threads;
}接着,执行NGINX reload重新加载配置。
然后,我们重复上述的测试:
% ifstat -bi eth2
eth2
Kbps in Kbps out
60915.19 9.51e+06
59978.89 9.51e+06
60122.38 9.51e+06
61179.06 9.51e+06
61798.40 9.51e+06
57072.97 9.50e+06
56072.61 9.51e+06
61279.63 9.51e+06
61243.54 9.51e+06
59632.50 9.50e+06现在,我们的服务器产生的流量是9.5Gbps,相比之下,没有使用线程池时只有约1Gbps!
理论上还可以产生更多的流量,但是这已经达到了机器的最大网络吞吐能力,所以在这次NGINX的测试中,NGINX受限于网络接口。工作进程的大部分时间只是休眠和等待新的事件(它们处于top的S状态):
top - 10:43:17 up 11 days, 1:35, 1 user, load average: 172.71, 93.84, 77.90
Tasks: 376 total, 1 running, 375 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.2 us, 1.2 sy, 0.0 ni, 34.8 id, 61.5 wa, 0.0 hi, 2.3 si, 0.0 st
KiB Mem: 49453440 total, 49096836 used, 356604 free, 97236 buffers
KiB Swap: 10474236 total, 22860 used, 10451376 free, 46836580 cached Mem

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
4654 vbart 20 0 309708 28844 596 S 9.0 0.1 0:08.65 nginx
4660 vbart 20 0 309748 28920 596 S 6.6 0.1 0:14.82 nginx
4658 vbart 20 0 309452 28424 520 S 4.3 0.1 0:01.40 nginx
4663 vbart 20 0 309452 28476 572 S 4.3 0.1 0:01.32 nginx
4667 vbart 20 0 309584 28712 588 S 3.7 0.1 0:05.19 nginx
4656 vbart 20 0 309452 28476 572 S 3.3 0.1 0:01.84 nginx
4664 vbart 20 0 309452 28428 524 S 3.3 0.1 0:01.29 nginx
4652 vbart 20 0 309452 28476 572 S 3.0 0.1 0:01.46 nginx
4662 vbart 20 0 309552 28700 596 S 2.7 0.1 0:05.92 nginx
4661 vbart 20 0 309464 28636 596 S 2.3 0.1 0:01.59 nginx
4653 vbart 20 0 309452 28476 572 S 1.7 0.1 0:01.70 nginx
4666 vbart 20 0 309452 28428 524 S 1.3 0.1 0:01.63 nginx
4657 vbart 20 0 309584 28696 592 S 1.0 0.1 0:00.64 nginx
4655 vbart 20 0 30958 28476 572 S 0.7 0.1 0:02.81 nginx
4659 vbart 20 0 309452 28468 564 S 0.3 0.1 0:01.20 nginx
4665 vbart 20 0 309452 28476 572 S 0.3 0.1 0:00.71 nginx
5180 vbart 20 0 25232 1952 1156 R 0.0 0.0 0:00.45 top
4651 vbart 20 0 20032 752 252 S 0.0 0.0 0:00.00 nginx
25921 vbart 20 0 121956 2176 1000 S 0.0 0.0 0:01.98 sshd
25923 vbart 20 0 40304 3840 2208 S 0.0 0.0 0:00.54 zsh如上所示,基准测试中还有大量的CPU资源剩余。
wrk的结果如下:
Running 1m test @ http://192.0.2.1:8000/1/1/1
12 threads and 50 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 226.32ms 392.76ms 1.72s 93.48%
Req/Sec 20.02 10.84 59.00 65.91%
15045 requests in 1.00m, 58.86GB read
Requests/sec: 250.57
Transfer/sec: 0.98GB服务器处理4MB文件的平均时间从7.42秒降到226.32毫秒(减少了33倍),每秒请求处理数提升了31倍(250 vs 8)!
对此,我们的解释是请求不再因为工作进程被阻塞在读文件,而滞留在事件队列中,等待处理,它们可以被空闲的进程处理掉。只要磁盘子系统能做到最好,就能服务好第一个客户端上的随机负载,NGINX可以使用剩余的CPU资源和网络容量,从内存中读取,以服务于上述的第二个客户端的请求。
5. 依然没有银弹在抛出我们对阻塞操作的担忧并给出一些令人振奋的结果后,可能大部分人已经打算在你的服务器上配置线程池了。先别着急。
实际上,最幸运的情况是,读取和发送文件操作不去处理缓慢的硬盘驱动器。如果我们有足够多的内存来存储数据集,那么操作系统将会足够聪明地在被称作“页面缓存”的地方,缓存频繁使用的文件。
“页面缓存”的效果很好,可以让NGINX在几乎所有常见的用例中展示优异的性能。从页面缓存中读取比较快,没有人会说这种操作是“阻塞”。而另一方面,卸载任务到一个线程池是有一定开销的。
因此,如果内存有合理的大小并且待处理的数据集不是很大的话,那么无需使用线程池,NGINX已经工作在最优化的方式下。
卸载读操作到线程池是一种适用于非常特殊任务的技术。只有当经常请求的内容的大小,不适合操作系统的虚拟机缓存时,这种技术才是最有用的。至于可能适用的场景,比如,基于NGINX的高负载流媒体服务器。这正是我们已经模拟的基准测试的场景。
我们如果可以改进卸载读操作到线程池,将会非常有意义。我们只需要知道所需的文件数据是否在内存中,只有不在内存中时,读操作才应该卸载到一个单独的线程中。
再回到售货员那个比喻的场景中,这回,售货员不知道要买的商品是否在店里,他必须要么总是将所有的订单提交给配货服务,要么总是亲自处理它们。
人艰不拆,操作系统缺少这样的功能。第一次尝试是在2010年,人们试图将这一功能添加到Linux作为fincore()系统调用,但是没有成功。后来还有一些尝试,是使用RWF_NONBLOCK标记作为preadv2()系统调用来实现这一功能(详情见LWN.net上的非阻塞缓冲文件读取操作和异步缓冲读操作)。但所有这些补丁的命运目前还不明朗。悲催的是,这些补丁尚没有被内核接受的主要原因,貌似是因为旷日持久的撕逼大战(bikeshedding)。
另一方面,FreeBSD的用户完全不必担心。FreeBSD已经具备足够好的异步读取文件接口,我们应该用这个接口而不是线程池。
6. 配置线程池所以,如果你确信在你的场景中使用线程池可以带来好处,那么现在是时候深入了解线程池的配置了。
线程池的配置非常简单、灵活。首先,获取NGINX 1.7.11或更高版本的源代码,使用—with-threads配置参数编译。在最简单的场景中,配置看起来很朴实。我们只需要在http、 server,或者location上下文中包含aio threads指令即可:
aio threads;这是线程池的最简配置。实际上的精简版本示例如下:
thread_pool default threads=32 max_queue=65536;
aio threads=default;这里定义了一个名为“default”,包含32个线程,任务队列最多支持65536个请求的线程池。如果任务队列过载,NGINX将输出如下错误日志并拒绝请求:
thread pool "NAME" queue overflow: N tasks waiting错误输出意味着线程处理作业的速度有可能低于任务入队的速度了。你可以尝试增加队列的最大值,但是如果这无济于事,那么这说明你的系统没有能力处理如此多的请求了。
正如你已经注意到的,你可以使用thread_pool指令,配置线程的数量、队列的最大值,以及线程池的名称。最后要说明的是,可以配置多个独立的线程池,将它们置于不同的配置文件中,用做不同的目的:
http {
thread_pool one threads=128 max_queue=0;
thread_pool two threads=32;

server {
location /one {
aio threads=one;
}

location /two {
aio threads=two;
}
}

}如果没有指定max_queue参数的值,默认使用的值是65536。如上所示,可以设置max_queue为0。在这种情况下,线程池将使用配置中全部数量的线程,尽可能地同时处理多个任务;队列中不会有等待的任务。
现在,假设我们有一台服务器,挂了3块硬盘,我们希望把该服务器用作“缓存代理”,缓存后端服务器的全部响应信息。预期的缓存数据量远大于可用的内存。它实际上是我们个人CDN的一个缓存节点。毫无疑问,在这种情况下,最重要的事情是发挥硬盘的最大性能。
我们的选择之一是配置一个RAID阵列。这种方法毁誉参半,现在,有了NGINX,我们可以有其他的选择:

  1. 我们假设每块硬盘挂载在相应的目录中:/mnt/disk1、/mnt/disk2、/mnt/disk3

proxy_cache_path /mnt/disk1 levels=1:2 keys_zone=cache_1:256m max_size=1024G
use_temp_path=off;
proxy_cache_path /mnt/disk2 levels=1:2 keys_zone=cache_2:256m max_size=1024G
use_temp_path=off;
proxy_cache_path /mnt/disk3 levels=1:2 keys_zone=cache_3:256m max_size=1024G
use_temp_path=off;

thread_pool pool_1 threads=16;
thread_pool pool_2 threads=16;
thread_pool pool_3 threads=16;

split_clients $request_uri $disk {
33.3% 1;
33.3% 2;
* 3;
}

location / {
proxy_pass http://backend;
proxy_cache_key $request_uri;
proxy_cache cache_$disk;
aio threads=pool_$disk;
sendfile on;
}在这份配置中,使用了3个独立的缓存,每个缓存专用一块硬盘,另外,3个独立的线程池也各自专用一块硬盘。
缓存之间(其结果就是磁盘之间)的负载均衡使用split_clients模块,split_clients非常适用于这个任务。
在 proxy_cache_path指令中设置use_temp_path=off,表示NGINX会将临时文件保存在缓存数据的同一目录中。这是为了避免在更新缓存时,磁盘之间互相复制响应数据。
这些调优将带给我们磁盘子系统的最大性能,因为NGINX通过单独的线程池并行且独立地与每块磁盘交互。每块磁盘由16个独立线程和读取和发送文件专用任务队列提供服务。
我敢打赌,你的客户喜欢这种量身定制的方法。请确保你的磁盘也持有同样的观点。
这个示例很好地证明了NGINX可以为硬件专门调优的灵活性。这就像你给NGINX下了一道命令,让机器和数据用最佳姿势来搞基。而且,通过NGINX在用户空间中细粒度的调优,我们可以确保软件、操作系统和硬件工作在最优模式下,尽可能有效地利用系统资源。
7. 总结综上所述,线程池是一个伟大的功能,将NGINX推向了新的性能水平,除掉了一个众所周知的长期危害——阻塞——尤其是当我们真正面对大量内容的时候。
甚至,还有更多的惊喜。正如前面提到的,这个全新的接口,有可能没有任何性能损失地卸载任何长期阻塞操作。NGINX在拥有大量的新模块和新功能方面,开辟了一方新天地。许多流行的库仍然没有提供异步非阻塞接口,此前,这使得它们无法与NGINX兼容。我们可以花大量的时间和资源,去开发我们自己的无阻塞原型库,但这么做始终都是值得的吗?现在,有了线程池,我们可以相对容易地使用这些库,而不会影响这些模块的性能。
查看英文原文:Thread Pools in NGINX Boost Performance 9x!

http://www.infoq.com/cn/articles/thread-pools-boost-performance-9x

Nginx配置解说

绝对详细!Nginx基本配置、性能优化指南

大多数的Nginx安装指南告诉你如下基础知识——通过apt-get安装,修改这里或那里的几行配置,好了,你已经有了一个Web服务器了!而且,在大多数情况下,一个常规安装的nginx对你的网站来说已经能很好地工作了。然而,如果你真的想挤压出nginx的性能,你必须更深入一些。在本指南中,我将解释Nginx的那些设置可以微调,以优化处理大量客户端时的性能。需要注意一点,这不是一个全面的微调指南。这是一个简单的预览——那些可以通过微调来提高性能设置的概述。你的情况可能不同。

Nginx配置 Nginx优化 Nginx安装

基本的 (优化过的)配置

我们将修改的唯一文件是nginx.conf,其中包含Nginx不同模块的所有设置。你应该能够在服务器的/etc/nginx目录中找到nginx.conf。首先,我们将谈论一些全局设置,然后按文件中的模块挨个来,谈一下哪些设置能够让你在大量客户端访问时拥有良好的性能,为什么它们会提高性能。本文的结尾有一个完整的配置文件。

高层的配置

nginx.conf文件中,Nginx中有少数的几个高级配置在模块部分之上。

user www-data;
pid /var/run/nginx.pid;
worker_processes auto;
worker_rlimit_nofile 100000;
user和pid应该按默认设置 – 我们不会更改这些内容,因为更改与否没有什么不同。

worker_processes 定义了nginx对外提供web服务时的worder进程数。最优值取决于许多因素,包括(但不限于)CPU核的数量、存储数据的硬盘数量及负载模式。不能确定的时候,将其设置为可用的CPU内核数将是一个好的开始(设置为“auto”将尝试自动检测它)。

worker_rlimit_nofile 更改worker进程的最大打开文件数限制。如果没设置的话,这个值为操作系统的限制。设置后你的操作系统和Nginx可以处理比“ulimit -a”更多的文件,所以把这个值设高,这样nginx就不会有“too many open files”问题了。

Events模块

events模块中包含nginx中所有处理连接的设置。

events {
worker_connections 2048;
multi_accept on;
use epoll;
}
worker_connections设置可由一个worker进程同时打开的最大连接数。如果设置了上面提到的worker_rlimit_nofile,我们可以将这个值设得很高。

记住,最大客户数也由系统的可用socket连接数限制(~ 64K),所以设置不切实际的高没什么好处。

multi_accept 告诉nginx收到一个新连接通知后接受尽可能多的连接。

use 设置用于复用客户端线程的轮询方法。如果你使用Linux 2.6+,你应该使用epoll。如果你使用*BSD,你应该使用kqueue。想知道更多有关事件轮询?看下维基百科吧(注意,想了解一切的话可能需要neckbeard和操作系统的课程基础)

(值得注意的是如果你不知道Nginx该使用哪种轮询方法的话,它会选择一个最适合你操作系统的)。

HTTP 模块

HTTP模块控制着nginx http处理的所有核心特性。因为这里只有很少的配置,所以我们只节选配置的一小部分。所有这些设置都应该在http模块中,甚至你不会特别的注意到这段设置。

http {
server_tokens off;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
}
server_tokens 并不会让nginx执行的速度更快,但它可以关闭在错误页面中的nginx版本数字,这样对于安全性是有好处的。

sendfile可以让sendfile()发挥作用。sendfile()可以在磁盘和TCP socket之间互相拷贝数据(或任意两个文件描述符)。Pre-sendfile是传送数据之前在用户空间申请数据缓冲区。之后用read()将数据从文件拷贝到这个缓冲区,write()将缓冲区数据写入网络。sendfile()是立即将数据从磁盘读到OS缓存。因为这种拷贝是在内核完成的,sendfile()要比组合read()和write()以及打开关闭丢弃缓冲更加有效(更多有关于sendfile)

tcp_nopush 告诉nginx在一个数据包里发送所有头文件,而不一个接一个的发送

tcp_nodelay 告诉nginx不要缓存数据,而是一段一段的发送–当需要及时发送数据时,就应该给应用设置这个属性,这样发送一小块数据信息时就不能立即得到返回值。

access_log off;
error_log /var/log/nginx/error.log crit;
access_log设置nginx是否将存储访问日志。关闭这个选项可以让读取磁盘IO操作更快(aka,YOLO)。

error_log 告诉nginx只能记录严重的错误。

keepalive_timeout 10;
client_header_timeout 10;
client_body_timeout 10;
reset_timedout_connection on;
send_timeout 10;
keepalive_timeout 给客户端分配keep-alive链接超时时间。服务器将在这个超时时间过后关闭链接。我们将它设置低些可以让ngnix持续工作的时间更长。

client_header_timeout 和client_body_timeout 设置请求头和请求体(各自)的超时时间。我们也可以把这个设置低些。

reset_timeout_connection告诉nginx关闭不响应的客户端连接。这将会释放那个客户端所占有的内存空间。

send_timeout 指定客户端的响应超时时间。这个设置不会用于整个转发器,而是在两次客户端读取操作之间。如果在这段时间内,客户端没有读取任何数据,nginx就会关闭连接。

limit_conn_zone $binary_remote_addr zone=addr:5m;
limit_conn addr 100;
limit_conn为给定的key设置最大连接数。这里key是addr,我们设置的值是100,也就是说我们允许每一个IP地址最多同时打开有100个连接。

limit_conn_zone设置用于保存各种key(比如当前连接数)的共享内存的参数。5m就是5兆字节,这个值应该被设置的足够大以存储(32K*5)32byte状态或者(16K*5)64byte状态。

include /etc/nginx/mime.types;
default_type text/html;
charset UTF-8;
include只是一个在当前文件中包含另一个文件内容的指令。这里我们使用它来加载稍后会用到的一系列的MIME类型。

default_type设置文件使用的默认的MIME-type。

charset设置我们的头文件中的默认的字符集。

以下两点对于性能的提升在伟大的WebMasters StackExchange中有解释。

gzip_disable "msie6";

  1. gzip_static on;

gzip_proxied any;
gzip_min_length 1000;
gzip_comp_level 4;

gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
gzip是告诉nginx采用gzip压缩的形式发送数据。这将会减少我们发送的数据量。

gzip_disable为指定的客户端禁用gzip功能。我们设置成IE6或者更低版本以使我们的方案能够广泛兼容。

gzip_static告诉nginx在压缩资源之前,先查找是否有预先gzip处理过的资源。这要求你预先压缩你的文件(在这个例子中被注释掉了),从而允许你使用最高压缩比,这样nginx就不用再压缩这些文件了(想要更详尽的gzip_static的信息,请点击这里)。

gzip_proxied允许或者禁止压缩基于请求和响应的响应流。我们设置为any,意味着将会压缩所有的请求。

gzip_min_length设置对数据启用压缩的最少字节数。如果一个请求小于1000字节,我们最好不要压缩它,因为压缩这些小的数据会降低处理此请求的所有进程的速度。

gzip_comp_level设置数据的压缩等级。这个等级可以是1-9之间的任意数值,9是最慢但是压缩比最大的。我们设置为4,这是一个比较折中的设置。

gzip_type设置需要压缩的数据格式。上面例子中已经有一些了,你也可以再添加更多的格式。

  1. cache informations about file descriptors, frequently accessed files
  2. can boost performance, but you need to test those values

open_file_cache max=100000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
##

  1. Virtual Host Configs
  2. aka our settings for specific servers

##
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
open_file_cache打开缓存的同时也指定了缓存最大数目,以及缓存的时间。我们可以设置一个相对高的最大时间,这样我们可以在它们不活动超过20秒后清除掉。

open_file_cache_valid 在open_file_cache中指定检测正确信息的间隔时间。

open_file_cache_min_uses 定义了open_file_cache中指令参数不活动时间期间里最小的文件数。

open_file_cache_errors指定了当搜索一个文件时是否缓存错误信息,也包括再次给配置中添加文件。我们也包括了服务器模块,这些是在不同文件中定义的。如果你的服务器模块不在这些位置,你就得修改这一行来指定正确的位置。

一个完整的配置

user www-data;
pid /var/run/nginx.pid;
worker_processes auto;
worker_rlimit_nofile 100000;

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

http {
server_tokens off;
sendfile on;
tcp_nopush on;
tcp_nodelay on;

access_log off;
error_log /var/log/nginx/error.log crit;

keepalive_timeout 10;
client_header_timeout 10;
client_body_timeout 10;
reset_timedout_connection on;
send_timeout 10;

limit_conn_zone $binary_remote_addr zone=addr:5m;
limit_conn addr 100;

include /etc/nginx/mime.types;
default_type text/html;
charset UTF-8;

gzip on;
gzip_disable "msie6";
gzip_proxied any;
gzip_min_length 1000;
gzip_comp_level 6;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;

open_file_cache max=100000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;

include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
编辑完配置后,确认重启nginx使设置生效。

sudo service nginx restart
后记

就这样!你的Web服务器现在已经就绪,之前困扰你的众多访问者的问题来吧。这并不是加速网站的唯一途径,很快我会写更多介绍其他加速网站方法的文章的。

http://www.chinaz.com/web/2015/0424/401323.shtml