当前位置: 首页 > 科技观察

浅谈Nginx“线程池模式”;据说性能提升了9倍

时间:2023-03-17 16:10:30 科技观察

与Apache的同步IO模型相比,Nginx由于采用了NIO,在性能上碾压了前者。Nginx轻量级,占用系统资源少,天然支持高并发。今天我们就简单的讨论一下nginx的线程模型。请注意,它不是流程模型。nginx的IO模型大家应该都知道。简单的说就是一个master进程和多个worker进程(进程数由配置决定);master进程负责接受请求并排队,最后转发给worker进程进行请求处理和响应。nginx以多进程模式运行。Nginx在1.7.11版本提供了多线程,但是这种多线程在aio模型中只用在对本地文件的操作上。出发点是使用非阻塞模式来提高文件IO的效率和并发。所以这个多线程不是nginx通过多线程处理代理请求(这部分是通过epoll方式),而是用来处理一些本地的静态文件。这里涉及到几个基本的指令:sendfile、aio、directio,都是和本地文件操作相关的。接下来,我们来看看它们的含义。命令sendfile与系统函数sendfile()具有相同的语义。sendfile的目的是提高通过sockets发送本地文件的效率。官方博客介绍了如何使用nginx线程池aio实现9倍的性能提升。它还有一个比较好记的名字叫零拷贝。读取传统文件和将传统文件发送到网络有什么区别?磁盘、网络驱动器和内存是三种不同的传输介质。如果在本地读取文件并通过socket发送,通常会经过以下步骤:1)磁盘驱动程序根据CPU的调度,从磁盘中读取一定长度(chunk)的字节数据2)复制字节数据到内核??内存3)将内核内存中的数据复制到进程工作区内存4)进程通过socket将数据复制到网络驱动器缓存中,通过相应的传输协议发送出去。可见,发送数据的过程涉及到多个副本,这是受限于计算机系统的设计。sendfile的主要出发点是减少数据拷贝,提高发送效率。sendfile是linux系统级别的调用。socket可以直接通过DMA(directmemoryaccess)访问文件数据,通过传输协议发送,减少了数据的两倍。复制(磁盘到内核,内核到工作区)。sendfile_max_chunk参数用于限制每次sendfile()调用发送的最大数据大小。如果不限制大小,就会独占整个worker进程。默认为“无限制”。这也太霸道了吧。对于nginx来说,代理static本地静态文件资源(一般是小文件)会非常高效。html、图片等一些静态文件建议开启该参数。location/video{sendfileon;sendfile_max_chunk128k;aioon;}directio此命令用于启用O_DIRECT标志(BSD、linux)的使用,对应于directio()系统调用。这个参数是为大文件设置的,而sendfile是为小文件设置的。可以通过directio指定限制大小。对于超过此大小的文件,将使用directio(而不是sendfile)。按照directio的设计初衷,它有sendfile的基本原理,但是不使用内核缓存,而是直接使用DMA,内存缓存(页面对齐部分)用完后也会释放。所以directio通常适合读取大文件,通常读取频率很低。因为对于高频读,并没有提高效率(因为不是每次都复用缓存,而是DMA)。由于性能折衷,此参数默认为关闭。location/video{sendfileon;directio8m;aioon;}aio在讲aio模型时,语义基本一致,就是异步文件IO。nginx默认关闭该功能,需要高版本linux平台(2.6.22+)支持。在Linux上,directio只能读取在512字节边界上对齐的块,文件末尾未对齐的块将以阻塞方式读取。同样,如果文件一开始就没有对齐,那么整个文件都会被阻塞读取。这里所谓的对齐是指文件数据在内存页中的缓存情况。当aio和sendfile都开启时,对于那些大小大于directio设置值的文件,会使用aio机制:即当文件小于directio设置值时,直接使用sendfile(aio不参加)。aio,简单来说就是采用多线程异步方式读取更大的文件,以提高IO效率,但实际上可能并没有什么提升。因为大文件的读取不能使用缓存,而且比较耗时,即使是多线程,请求的等待时间也是不可预测的,尤其是在并发请求很高的时候。但是aio可以提高IO的并发,这个是肯定的。默认情况下,多线程模式是关闭的,我们需要通过--with-threads配置开启它,这个特性只兼容支持epoll和kqueue的平台。对于线程池的设置,我们可以通过thread_pool声明,在aio命令中指定。我们的配置文件进一步丰富了一些。thread_pooldefault_poolthreads=16;##maincontext...location/video{sendfileon;sendfile_max_chunk128k;directio8M;aiothreads=default_pool;}当线程池中的所有线程都处于忙碌状态时,新的任务请求会加入到等待队列中。我们可以使用thread_pool中的max_queue参数来指定队列的大小。默认队列大小为65536。当队列满时,后续请求会抛出错误。ENDnginx官方号称使用多线程模式,在aio读取文件的场景下,性能提升了9倍,但我对这个测试还是有些疑惑。多线程+aio确实可以在一定程度上提高文件IO的读取性能,但是对于大文件来说,这似乎并没有想象中的那么好。这个受制于Linux平台的底层特性,除非nginx对文件缓存做额外的操作。至此,xjjdog还有如下建议(供参考):1)对于小文件的静态代理,我们应该开启sendfile,这样可以显着提升性能。2)对于大文件读取(低频),我们可以尝试开启aio和directio,在提高并发能力的前提下关注请求的实际响应效率;既然官方推荐使用这种方式,那么我们可以抱着尝试的态度来举报。3)对于高频大文件读取,aio和directio的性能提升可能不明显,但应该不会降低性能。作者简介:品味小姐姐(xjjdog),一个不允许程序员走弯路的公众号。专注于基础架构和Linux。十年架构,每天百亿流量,与你探讨高并发世界,给你不一样的滋味。我的个人微信xjjdog0,欢迎加好友进一步交流。