如果你以为Nginx只是单线程的异步大师,那你就错过了它真正强大的武器库。
从Nginx的“敌人”说起:阻塞操作
大家都知道,Nginx采用了异步、事件驱动的方法来处理连接。这种处理方式无需为每个请求创建额外的专用进程或线程,而是在一个工作进程中处理多个连接和请求。
为此,NGINX工作在非阻塞的socket模式下,并使用了epoll和kqueue这样有效的方法。
这种架构的优势显而易见:满负载进程的数量很少(通常每核CPU只有一个)而且恒定,任务切换只消耗很少的内存,而且不会浪费CPU周期。NGINX可以非常好地处理百万级规模的并发请求。
但异步、事件驱动方法仍然存在问题。这个问题的名字叫阻塞(blocking)。很多第三方模块使用了阻塞调用,然而用户(有时甚至是模块的开发者)并不知道阻塞的缺点。阻塞操作可以毁掉NGINX的性能,我们必须不惜一切代价避免使用阻塞。
即使在当前官方的NGINX代码中,依然无法在全部场景中避免使用阻塞。想象一下,当Nginx需要读取一个没有缓存在内存中的文件时,它不得不从磁盘读取。从磁盘(特别是旋转式的磁盘)读取是很慢的。而当队列中等待的其他请求可能不需要访问磁盘时,它们也得被迫等待。
这就像商店的营业员要接待一排长队:队伍中的第一位顾客想要的商品在仓库中,营业员跑去仓库取货,整个队伍就得等待数小时。结果是延迟增加并且系统资源没有得到充分利用。
一个阻塞操作足以显著地延缓所有接下来的操作。
线程池:Nginx的救星
为了解决这个问题,在NGINX 1.7.11中引入了线程池机制。那么,线程池到底是什么呢?
继续我们之前的比喻,现在那个营业员变聪明了,他雇用了一个配货服务团队。当任何人要买的东西在仓库时,他不再亲自去仓库,只需要将订单丢给配货服务。配货服务将处理订单,同时营业员可以继续为其他顾客服务。
对NGINX而言,线程池执行的就是配货服务的功能。它由一个任务队列和一组处理这个队列的线程组成。当工作进程需要执行一个潜在的长操作时,工作进程不再自己执行这个操作,而是将任务放到线程池队列中,任何空闲的线程都可以从队列中获取并执行这个任务。
工作进程将阻塞操作卸给线程池。
这样一来,只有访问文件的请求需要等待,而磁盘不再延误其他事件的处理。
目前,卸载到线程池中执行的两个基本操作是大多数操作系统中的read()系统调用和Linux中的sendfile()。在未来的版本中,如果有明显的优势,可能会卸载其他操作到线程池中。
pthread系统调用:线程池的基石
要理解Nginx的线程池实现,我们首先需要了解pthread系统调用的基础知识。
线程基础
线程是任务执行的基本单位,而进程则是线程执行的资源容器。线程共享相同的地址空间,共享相同的代码、数据区和堆区域。它们还共享相同的操作系统资源(如打开的文件)——一个线程读取文件会推进所有线程的文件偏移量。
每个线程有自己的执行状态:自己的用户栈、PC(程序计数器)、SP(栈指针)和寄存器。
关键的pthread函数
Nginx线程池的实现依赖于几个关键的pthread函数:

最低0.47元/天 解锁文章

被折叠的 条评论
为什么被折叠?



