/ 中存储网

nginx使用线程池提升9倍性能

2015-07-15 11:13:33 来源:中存储网

众所周知nginx使用异步,事件驱动方法处理连接。这意味着nginx使用一个worker进程处理多个连接和请求,而不是每一个请求有一个专门的进程或着线程处理(像传统架构的服务器那样,例如apache)。为了实现这个目的,nginx使用非阻塞模式的socket和高效的方法epoll和kqueue。

因为高负荷进程的数量少且相对不变(通常1个cpu核心配1个进程),它内存消耗少,cpu时间没有浪费在任务切换上。这种处理请求的方式的优势也因为nginx而被大家所熟知。nginx能够成功处理数百万并发请求同时扩展性非常好。
Traditional-Server-and-NGINX-Worker.gif

不过异步,事件驱动方法还是有一个问题。或者像我想的那样有一个“敌人”。这个敌人的名字是:阻塞。不幸的是,许多第三方的模块使用阻塞调用,同时用户(甚至作为模块的开发者)也没意识到缺点。阻塞操作能毁掉nginx的性能,需要不惜一切代价避免阻塞。

甚至当前nginx的官方代码中也没有完全避免阻塞操作在所有的例子中,为了解决这个问题,新的“thread pools”机制在nginx1.7.11中被实现了。线程池是什么以及它如何使用,一会我们就讲到。

问题

首先,为了更好的理解问题,再简单介绍一下nginx的原理。

一般,nginx是一个事件handler,一个controller接收来自内核的信息关于所有连接上发生的事件和给操作系统发送命令告诉它做什么。事实上,nginx做了所有困难的工作通过组织操作系统,而操作系统只做读和写字节的常规工作。所以对于nginx来说快速响应式非常重要的。
NGINX-Event-Loop2-e1434744201287

事件会超时,sockets的读和写通知,发生错误的通知。nginx收到许多事件然后一个一个处理它们。因此所有事情在一个简单的循环在一个队列上通过一个线程处理。nginx从队列上取出一个消息事件然后做出反应通过写或读一个socket。在大多数情况下,这是相当快的(或许只需要几个cpu时钟去拷贝数据到内存)同时nginx继续立刻处理队列里的其他事件。
Events-Queue-Processing-Cycle1

但是如果某个长和重的操作出现的时候会发生什么呢?整个事件处理循环将会被卡住等待这个操作的完成。

所以,一个阻塞的操作是指任何能够停止事件处理循环许多时间的操作。操作被阻塞有很多原因。例如,nginx可能忙于处理长时的cpu密集操作,或者需要等待访问一个资源(如一个硬盘,一个锁,一个同步方式的方法调用返回访问数据库),主要的问题是当处理这些操作的时候,nginx的worker进程不能够做其它的事情,也不能处理事件,即使有许多系统资源仍然可用,那些队列里的事件可以用这些资源来处理。

想象一个商店的销售面前排了很长的队伍,队首的人想要一个不在商店但在仓库里的东西。销售人员去库房取东西。那么整个队列会等几个小时来取这个东西,队列里的所有人都会不高兴。你能想象这些人的反应吗?队列每个人的等待时间都增加了几个小时,但是它们需要的东西就在店里,很快就能完成购买。

Faraway-Warehouse

几乎会出现同样的事情当nginx想要读一个不在内存中缓存的大文件,需要从磁盘中读取的时候,硬盘很慢,然后队列中等待的其它请求并不一定需要访问硬盘,但他们被强制等待。结果等待时间增加,系统资源没有完全使用。
Blocking-Operation-e1434743587684

某些操作系统提供了异步的接口来发送文件,nginx可以使用这些接口。一个好的例子是FreeBSD。不幸的是,linux不能这样。虽然linux提供了异步接口来读文件,它有许多缺点。第一就是访问文件和缓存的对齐需求,nginx能够处理它。第二个就更糟糕一些,异步接口需要O_DIRECT标识被设置在文件描述符上,这意味着任何对文件的访问将会越过内存中的缓存,会增加磁盘的负载。所以并不是最优的在许多场景下。

为了解决这个问题,线程池在nginx1.7.11中被引进,它默认没有被加入到nginx plus中。