您的位置 首页 golang

Nginx多线程原理

一、问题

一般情况下, nginx 是一个事件处理器,一个从内核获取连接事件并告诉系统如何处理的控制器。实际上,在操作系统做读写数据调度的时候,nginx是协同系统工作的,所以nginx能越快响应越好。

nginx处理的事件可以是 超时通知、 socket 可读写的通知 或 错误通知。nginx 接收到这些消息后,会逐一进行处理。但是所有处理过程都是在一个简单的 线程 循环中完成的。nginx 从消息队列中取出一条event后执行,例如 读写socket的event。在大多数情况下这很快,Nginx瞬间就处理完了。

如果有耗时长的操作发生怎么办?整个消息处理的循环都必须等待这个耗时长的操作完成,才能继续处理其他消息。所以,我们说的“阻塞操作”其实意思是长时间占用消息循环的操作。操作系统可能被各种各样的原因阻塞,或者等待资源的访问,例如硬盘、互斥锁、数据库同步操作等。

例如,当nginx 想要读取没有缓存在内存中的文件时,则要从磁盘读取。但磁盘是比较缓慢的,即使是其他后续的事件不需要访问磁盘,他们也得等待本次事件的访问磁盘结束。结果就是延迟增加和系统资源没有被充分利用。

有些操作系统提供了异步读写文件接口,在nginx中可以使用这些接口(#aio)。例如FreeBSD就是一个较好的例子,但不幸的是,linux提供的一系列异步读文件接口有不少缺陷。其中一个问题是:文件访问和缓冲需要队列,但是Nginx已经很好解决了。但是还有一个更严重的问题:使用异步接口需要对文件描述符设置O_DIRECT标识,这意味着任何对这个文件的访问会跳过缓存直接访问磁盘上的文件。在大多数情况下,这不是访问文件的最佳方法。

二、 线程池

为了解决这个问题,Nginx 1.7.11 引入了线程池概念。现在让我们了解一下线程池是怎样工作的。

在nginx中,线程池执行的是分发服务,他由一个任务队列和一些执行任务的线程组成。当一个工作线程在执行一个可能会存在潜在长时间操作的任务时,这个任务会被”卸下“并重新放到任务队列中去,这个被”卸下“的任务可能会被其他线程再执行。

现在,只有2个基础操作会造成“卸下任务”到任务队列:

  • 在大多操作系统上的read()系统调用
  • linux系统的sendfile()
    如果这个机制被证实是有益于nginx的,我们以后还会添加其他的操作。

三、线程池并非灵丹妙药

大多数读写文件操作都需要通过缓慢的磁盘。如果有充足的内存来存储数据,那么操作系统会缓存频繁使用的文件,也就是“页面缓存”(page cache)机制。

由于页面缓存机制,nginx几乎在所有情况下都能体现非常好的性能。通过页面缓存读取数据非常快,并且不会阻塞。另一方面,卸下任务到任务池是有瓶颈的。所以在内存充足并且使用的数据不是非常大的时候,nginx即使不使用线程池也是几乎工作在最佳状态。

卸下写操作到任务池中,是一个适用于特殊场景的处理方案,适用于大量无法使用VM缓存的请求操作。例如一个高负荷的基于Nginx的视频流服务器。另外FreeBSD的用户不需要担心这些,因为FreeBSD已经有很好的异步读操作接口,无需使用线程池。

配置线程池

Nginx是由Igor Sysoev为俄罗斯的Rambler.ru站点开发的一个高性能的HTTP和反向代理服务器,也是现在中国互联网公司使用多的代理软件,利用Nginx与各个模块的整合可以实现高效的WEB处理能力

要想合理的配置线程池,就必须首先分析任务特性,可以从以下几个角度来进行分析:

1.任务的性质:

CPU密集型任务,IO密集型任务和混合型任务。

2.任务的优先级:

高,中和低。

3.任务的执行时间:

长,中和短。

4.任务的依赖性:

是否依赖其他系统资源,如数据库连接。

任务性质不同的任务可以用不同规模的线程池分开处理。CPU密集型任务配置尽可能少的线程数量,如配置Ncpu+1个线程的线程池。IO密集型任务则由于需要等待IO操作,线程并不是一直在执行任务,则配置尽可能多的线程,如2*Ncpu。混合型的任务,如果可以拆分,则将其拆分成一个CPU密集型任务和一个IO密集型任务,只要这两个任务执行的时间相差不是太大,那么分解后执行的吞吐率要高于串行执行的吞吐率,如果这两个任务执行时间相差太大,则没必要进行分解。我们可以通过Runtime.getRuntime().availableProcessors()方法获得当前设备的CPU个数。

优先级不同的任务可以使用优先级队列PriorityBlockingQueue来处理。它可以让优先级高的任务先得到执行,需要注意的是如果一直有优先级高的任务提交到队列里,那么优先级低的任务可能永远不能执行。

执行时间不同的任务可以交给不同规模的线程池来处理,或者也可以使用优先级队列,让执行时间短的任务先执行。

依赖数据库连接池的任务,因为线程提交SQL后需要等待数据库返回结果,如果等待的时间越长CPU空闲时间就越长,那么线程数应该设置越大,这样才能更好的利用CPU。

一般总结哦,有其他更好的方式,希望各位留言,谢谢。

CPU密集型时,任务可以少配置线程数,大概和机器的cpu核数相当,这样可以使得每个线程都在执行任务

IO密集型时,大部分线程都阻塞,故需要多配置线程数,2*cpu核数

操作系统之名称解释:

某些进程花费了绝大多数时间在计算上,而其他则在等待I/O上花费了大多是时间,

前者称为计算密集型(CPU密集型)computer-bound,后者称为I/O密集型,I/O-bound。

结论

线程池机制是一个非常好的机制,通过解决大量数据情况下导致的阻塞问题,使得nginx的性能达到一个新的高度。如之前提到的,接下来会有新的接口可能会实现在不损耗性能的情况下实现”卸下“任务机制。

资料推荐;内容包括:C/C++,Linux,golang,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,嵌入式 等。。。后台私信;资料;两个字可以免费领取

文章来源:智云一二三科技

文章标题:Nginx多线程原理

文章地址:https://www.zhihuclub.com/98430.shtml

关于作者: 智云科技

热门文章

网站地图