免费网页在线代理服务器(最近和 Nginx 杠上了)

[51CTO.com原创稿] Nginx(engine x)是一个高性能的HTTP和反向代理Web服务器,它还提供IMAP/POP3/SMTP服务。



免费网页在线代理

图片来自趵突山。com

Nginx以其高性能和高可用性受到程序员的青睐。今天我们就从Nginx的整体架构入手,介绍Nginx的进程结构,进程之间的关系以及如何控制和管理进程。

今天,您将学习以下内容:

  • Nginx整体架构
  • Nginx过程定义
  • Nginx启动过程
  • 主启动过程
  • 进程间的信号模式
  • 进程协助处理网络请求。
  • Nginx 总体架构

    对于传统的HTTP和反向代理服务器,在处理并发请求时,会使用单进程或线程处理的模式,同时停止网络或输入/输出操作。

    这种方式会消耗大量的内存和CPU资源。因为每生成一个单独的进程或线程,都需要准备一个新的运行时环境,包括分配堆和栈内存,创建新的执行上下文。

    可以想象,处理多个请求时会产生相应数量的线程或进程,导致线程在不断的上下文切换中消耗大量资源。

    基于以上原因,Nginx在设计之初就采用了模块化、事件驱动、异步处理和非阻塞的架构。



    图1:1:Nginx的整体架构

    我们来上图了解一下Nginx的概要架构,如图1所示:

    ①NGINX启动时,不会立即处理网络请求,负责调度工作进程。包括负载配置、启动工作程序和不间断升级。

    所以Nginx启动后,在操作系统中会看到Master和Worker两种进程。图顶部的主进程负责加载分析配置文件、启动/管理工作进程以及平滑升级。

    一个主进程可以管理多个工作进程,工作进程负责处理和响应用户请求,也就是图左边的HTTP/HTTPS请求。

    ②由于网络请求属于IO请求,为了处理高并发的Nginx,采用了kevent/epoll/ select模式的复用技术。由于这项技术,每个工作进程可以同时处理数千个网络请求。

    ③为了处理网络请求,Worker会包含模块,分为核心模块和功能模块。

    核心模块负责维护运行循环,并在网络请求处理的不同阶段执行模块功能,如网络读写、存储读写、内容传输、出站过滤、向上游服务器发送请求等。

    围绕核心模块,会有一些功能模块,用来实现具体的请求处理功能。比如有处理http请求的ht_core模块,实现负载均衡的ht_upstream模块,实现FastCGI的HT _ FastCGI模块。

    这些模块将负责与后端服务器进行交互,以满足用户请求。同时,他们可以根据所需功能自由加载模块,甚至扩展第三方模块。

    ④Worker进程可以与本地磁盘通信,支持高级I/O、sendfile机制、AIO机制、mmap机制等。

    通过上面的介绍,发现Nginx不会为每个连接生成一个进程或线程,而是通过Worker进程复用的方式处理多个请求。

    在这里,我们将通过共享侦听套接字来接受新的请求,并在每个工作器中执行一个高效的运行周期,以便每个工作器可以处理数千个连接。

    工作开始后,它会创建一组监听套接字,在处理HTTP请求和响应的过程中不断接受、读写套接字信息。

    运行循环包括全面的内部调用,并严重依赖异步任务处理。

    模块化、事件通知、回调函数和定时器支持异步操作的实现。目的是在高并发请求下实现无阻塞(尽量不阻塞竞争)。

    基于上述机制,Nginx检查网络和存储的状态并初始化新的连接,将它们添加到运行周期中,并异步处理它们,直到它们完成。已处理的连接将被重新分配并从运行周期中删除。因此,Nginx可以在极端工作负载下实现低CPU利用率。

    另外,因为工作会在磁盘上写,为了避免阻塞磁盘I/O上的请求,特别是当磁盘已满时。

    可以设置机制和配置文件指令来减少这种磁盘I/O阻塞的发生。例如,使用发送文件和AIO组合选项来提高磁盘性能。

    Nginx 进程定义

    从架构的介绍中,我们知道Nginx由不同的进程组成,每个进程负责处理高并发下的网络请求。接下来,让我们看看它们的定义和工作原理。

    上面介绍了Nginx的总体架构,其中重点介绍了主进程和工作进程。事实上,还有另外两个过程也在架构中扮演着重要的角色。



    图2:2:Nginx的四种流程

    在这里,我们通过图2一起了解它们:

  • 主进程:作为父进程,会在Nginx初始化时生成并启动。其他流程是其子流程,主流程创建和管理其他流程。
  • 工作进程:它是处理网络请求的主进程的子进程。这里有必要解释一下Nginx为什么采用多进程结构而不是多线程。原因是为了保证高可用性,进程不像线程一样共享地址空,也避免了一个线程中的第三方模块误导而影响其他线程的情况。
  • 缓存管理器和缓存加载器进程:缓存加载器负责缓存加载,缓存管理器负责缓存管理,每个请求使用的缓存仍然由Worker决定,进程间通信通过共享内存实现。
  • 从上图中,你一定注意到了只有一些Worker进程,因为Nginx采用了事件驱动的模型。

    为了提高处理请求的效率,每个工作进程都会找一个CPU核,提高CPU的缓存命中率,将一个工作进程与一个CPU核绑定。

    需要注意的是,我们需要根据具体的应用场景来定义工作进程的数量:

  • CPU密集型请求,如处理大量TCP/IP、执行SSL或压缩,需要工作线程的数量与CPU核心的数量相匹配。
  • IO密集型请求,工作线程数量需要是CPU内核数量的一到两倍。
  • 如上所述,Master通过控制多个Worker进程来处理网络请求。对于Worker的独立进程,在使用资源时不需要考虑锁的问题,省去了锁带来的系统开销。同时,多流程设计使得流程之间互不影响。

    当一个进程退出时,其他进程还在工作,Nginx提供的网络请求服务不会因为其中一个进程的退出而中断。一旦主进程发现一个工作进程存在,它将启动一个新的工作进程。

    这里我们会发现,主进程需要与工作进程进行通信,以便对其进行控制,工作进程也需要与主进程交换信息。

    Nginx 启动过程

    说到Master进程的重要性,我们先来看看Nginx进程的启动过程。



    图3: Nginx启动主进程

    如图3所示,Nginx启动时会根据配置文件进行分析和初始化,同时会从主进程中分叉出一个主进程作为自己的子进程,也就是启动主进程,然后主进程就诞生了。

    Nginx的主进程在fork离开主进程后退出。然后主进程将分叉并启动工作进程,以及缓存管理器和缓存加载器进程,然后主进程将进入主循环。

    需要注意的是,这里使用的fork会复制一个与当前启动进程具有相同代码段、数据段、堆和栈、fd等信息的子进程。

    也就是说,我们所说的四类进程,都是从Nginx启动进程复制过来的子进程。

    Master 启动过程

    接下来,在上述流程主流程被fork后,将执行ngx_master_process_cycle函数。



    图4:主进程执行ngx_master_process_cycle函数

    如图4所示,该函数主要执行以下操作:

  • 设置过程的初始信号屏蔽,屏蔽相关信号。
  • 主fork处理工作进程、缓存管理器、缓存加载器和其他子进程。
  • 进入主循环,通过sigsuspend系统调用等待信号。
  • 信号一旦到达,就会进入信号处理程序ngx_signal_handler。
  • 在执行信号处理程序之后,程序执行过程将判断各种状态位来执行不同的操作。
  • 上面的流程中提到了几个概念,这里进行解释,以便我们更好的理解主流程的执行过程。

    ①信号:过程中用来完成信息传递的介质。在主进程的主循环中,通过等待各种信号事件来处理不同的指令。

    该信号可以传递给主进程,或者从主进程传递给其他进程。

    信号分为标准信号和实时信号,标准信号范围为1-31,实时信号范围为32-64。例如,INT、QUIT和KILL是标准信号。主进程监控的信号也是标准信号。

    标准信号和实时信号的区别在于,标准信号是基于比特的标记。假设在拥塞等待时,有几个相同的信号到达,当拥塞最终解除时,信号只会传输一次,因此等待期间的信号计数无法计算。

    实时信号是通过队列实现的,在等待拥塞的时候会有很多相同的实时信号存储在队列中。一旦拥塞解除,队列中的所有信号都将被传递,结果是接收到许多信号。

    ②信号处理器:信号处理器是指当捕获到指定的信号(传递给进程)时将被调用的函数。它存在于进程中,可以随时中断进程的主程序流。

    ③发送信号:发送信号的操作可以使用shell命令kill来完成。例如,KILL -9 pid表示发送KILL信号。

    Kill -INT pid是发送INT信号。与shell命令类似,您可以使用kill系统调用来通知进程。

    ④信号屏蔽:用于控制信号阻断的编码方式。每个进程都有一个信号掩码,也称为信号掩码字,它指定要屏蔽或阻止传递给进程的信号集。

    对于每一个可能的信号,这个掩码中都有一个比特。对于某个信号,如果其对应的位被置位,则该信号当前被阻止。

    简单地说,信号掩码是一个“位图”,其中每个位对应一个信号。如果位图中的某一位为1,则表示在当前信号集的加工程序执行过程中,相应的信号被暂时“屏蔽”或“阻断”,因此在执行过程中不会以嵌套的方式响应该信号。

    说白了,就是用信号编码来屏蔽比赛信号,告诉其他发送信号的进程说“我在忙事情,你先等着,我一会处理你的信号。”

    进程之间的信号发送方式

    有了上面的基础,我们再回头看看Nginx的过程中信息是如何交换的,主是如何通过信号与工进行交流的。



    图5:5:主人和工人之间的通信

    从图5可以看出,Master可以接受TERM、INT、QUIT、HUP、USR1、USR2和WINCH等信号,这些信号的含义将在后面的表格中解释。

    同时,主进程还可以向工作进程发送信号,因此工作进程可以接收到以下信号:TERM、INT、QUIT、HUP、USR1、WINCH。然后工人响应客户的请求。

    这里,信号的相应命令和含义被列在一个表格中,然后对它们进行解释。



    从上表中可以看出,每个信号都有特定的含义。例如,退出信号表示优雅地关闭服务,并对应于退出命令。

    这里的quit命令是指可以通过命令行命令主进程,从而达到发送信号的效果。当然,主控收到命令后,会转换成信号发送给相应的工作器,达到关闭服务的效果。

    需要注意的是,工作者不会接受命令,但是会通过接受命令来管理所有工作者的行为。

    进程协助处理网络请求

    了解了Master和Worker之间的通信方式之后,我们来看看它们是如何合作完成客户端请求的。



    图6:客户端请求过程

    如图6所示,这里描述了Master创建listen和fork离开Worker的过程,以及client请求和Worker响应请求的过程。

    ①从顶部开始,顺着红色箭头从上到下走。创建主进程后,套接字的IO通道将由socket方法创建。

    然后,执行bind方法将其绑定到监听器listen,然后通过fork方法显示多个工作进程(绿色虚线)。

    ②每个工作进程中的accept方法监听套接字请求。一旦listen侦听到套接字请求,工作进程就可以通过accept接受它。

    ③看底部的客户端模块。当客户端通过connect方法连接Nginx时,所有使用accept方法的工作进程都会收到listen的通知,但是只有一个工作进程可以成功接受,其他进程都会失败。

    这里Nginx提供了一个共享锁accept_mutex,保证在accept中同时只有一个工作进程连接,以解决死机问题。

    ④当Worker进程接受套接字请求时,客户端会通过send方法将请求(绿色虚线)发送给Worker。

    而Worker使用recv方法接受请求,并通过send方法经过解析、处理和生成步骤将返回的响应发送给客户机,而客户机将使用recv方法接受响应。

    最后,Worker调用close方法断开与客户端的连接。

    总结

    本文从Nginx的整体架构出发,介绍了Nginx的主要组件和处理流程。然后,介绍了Nginx的四个进程,以及这些进程在Nginx启动过程中是如何产生的。

    然后重点介绍了主进程启动过程中做了哪些具体的事情,尤其是主进程与Worker、缓存管理器、缓存负载之间的关系。

    在进程间信号传输方式一章中,我们建立了信号、信号传输、信号处理、信号屏蔽等概念,有助于理解进程间的通信。

    最后,趁热打铁,解释Nginx接受网络请求的过程以及进程如何协作处理请求。

    作者:崔豪

    简介:十六年开发和架构经验,曾担任惠普武汉交付中心技术专家、需求分析师、项目经理,后在创业公司担任技术/产品经理。善于学习,乐于分享。目前,他专注于技术架构和研发管理。

    编辑:陶佳龙

    您可以还会对下面的文章感兴趣

    使用微信扫描二维码后

    点击右上角发送给好友