精华内容
下载资源
问答
  • nginx架构

    千次阅读 2018-12-02 06:36:44
    第一部分介绍nginx现有框架,用典型的4+1视图阐述,包括逻辑架构,开发架构,运行架构,物理架构,功能用例,nginx为单机服务,不考虑物理架构。其中功能用例概述nginx功能;逻辑架构主要介绍nginx高度模块化中各个...

    一.概述

    本文将深入剖析nginx的架构。

    第一部分介绍nginx现有框架,用典型的4+1视图阐述,包括逻辑架构,开发架构,运行架构,物理架构,功能用例,nginx为单机服务,不考虑物理架构。其中功能用例概述nginx功能;逻辑架构主要介绍nginx高度模块化中各个模块的分层和依赖关系;开发架构主要描述nginx的代码结构和代码内容简介;重点是运行架构,nginx一主多从的进程模型架构和通信,高并发进程和IO并发的选型等。

    第二部分对比nginx运行架构和其他开源运行架构,总结nginx为何要这样选型;介绍nginx逻辑架构中的优点。

    本文适合阅读对象:1)已经看过nginx代码,本文帮你高度抽象总结了nginx的架构和与我自己设计相比较,nginx哪里设计的优点,试着从架构层来重新看下代码;2)研究各种系统架构的人,本文从统一的架构视图介绍,无需知道nginx的代码细节,列出了与其他架构比,nginx架构的亮点。3)还未看过nginx的代码,关注第二章,可以看四个视图忽略对特性的分析和架构的思考,帮助了解nginx有什么功能、如何组织代码、如何运行。

    关键词:Nginx架构,nginx功能,nginx逻辑架构,nginx代码结构,nginx运行架构,nginx高性能实现

    二.nginx现有架构实现

    功能介绍

    nginx最核心的功能是web服务器和反向代理服务器,web服务器完成对 http请求协议的解析 和 以http协议格式响应请求、缓存、日志处理这些 基本web服务器 功能,反向代理服务器完成对请求的转发、负载均衡、鉴权、限流、缓存、日志处理等代理常用功能。nginx在这方面提供了丰富的功能,包括对http2,ssl等等的支持。除了http外,nginx还支持mail服务和普通的tcp,udp协议的反向代理功能。一下列出了常用功能,详细所有功能见参考1
    ### http服务器/反向代理服务器

    • 静态文件,fastcgi,uwsgi,scgi,memcached 服务
    • 缓存
    • 负载均衡
    • SSL/TLS
    • HTTP2
    • 鉴权/限流
    • 虚拟servers

    功能用例举例:web服务器和反向代理服务器的功能(第一个locatiion为服务器,后面是反向代理)。在nginx中配置如图配置,启动nginx加载配置后,发起http请求,获取服务器响应。

    server{
        listen 8091;
        root /home/xiaoju/yyl/;
        index index.php;
        location /
        {
            root html;
            index index.html index.htm;
        }
        location  ~ \.php$
        {
            rewrite /(.*)$ /index.php/$1 break;
            fastcgi_index index.php;
            fastcgi_pass 127.0.0.1:9000;
            include fastcgi.conf;
        }
    }
    curl localhost:8091 -v
    * About to connect() to localhost port 8091 (#0)
    *   Trying 127.0.0.1... connected
    * Connected to localhost (127.0.0.1) port 8091 (#0)
    > GET / HTTP/1.1
    > User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.27.1 zlib/1.2.3 libidn/1.18 libssh2/1.4.2
    > Host: localhost:8091
    > Accept: */*
    >
    < HTTP/1.1 200 OK
    < Server: nginx/1.13.11
    < Date: Sun, 02 Dec 2018 06:29:01 GMT
    < Content-Type: text/html; charset=utf-8
    < Content-Length: 612
    < Last-Modified: Tue, 10 Apr 2018 15:32:02 GMT
    < Connection: keep-alive
    < ETag: "5accd8f2-264"
    < Accept-Ranges: bytes
    <
    <!DOCTYPE html>
    <html>
    <head>
    <title>Welcome to nginx!</title>
    ...
    </html>
    * Connection #0 to host localhost left intact
    * Closing connection #0

    mail反向代理

    • mail反向代理
    • SSL; STARTTLS/STLS

    TCP/UDP反向代理 socket,websocket

    • SSL/TLS, 负载均衡, 鉴权/限流等

    2.逻辑架构

    nginx在逻辑上分为入口层,模块化的功能处理层,系统调用层。入口调用配置模块和核心模块,各核心模块分别调用各自功能模块,系统调用层封装了各个操作系统的功能被功能处理层使用。逻辑架构最明显主要的特征就是高度模块化,所有功能都是模块,每个模块都统一结构,下面先看下这个统一结构,然后分别介绍各个模块。
    逻辑架构

    特征——高度模块化

    nginx除了main等少量代码,其他全都是模块,所有模块都是Ngx_module_t的抽象,只有初始化,退出,对配置项的处理;每个模块内部也都有自己模块ngx_xx_module_t的抽象;配置也高度抽象统一的结构ngx_command_t。如图:

    特征

    核心模块/配置模块

    核心流程会只会调用核心模块和配置模块。
    核心模块调用各个其他模块的core_module完成各自模块的加载工作。配置模块为其他模块的基础,负责解析配置文件。

    事件模块

    负责请求连接的建立,分发等网络事件及定时器事件,其中所有模块封装到ngx_events_module_t接口中供其他模块直接调用。

    http/stream/mail模块

    对应nginx用户功能的三个主体。以Http模块为例,初始化,退出,对配置项的处理等工作也统一封装在ngx_http_module_t中。http请求的处理过程各模块可插拔,为固定的11个阶段,模块想介入,只需在ngx_http_module_t中定义回调函数,http协议内容多,对结果的处理也高度模块化,根据配置项将模块选择性插入到输出过滤链中。

    系统调用

    nginx对各种操作系统的调用做了一层封装,使模块代码无需区分。

    依赖关系

    http,stream,mail依赖事件模块,事件模块依赖核心模块和配置模块,上层模块依赖底层的系统调用。

    3. 开发架构

    开发架构主要关注现有的代码结构和开发代码时如何扩展。先介绍代码结构然后列举一下如何新增模块

    代码结构

    包含core,event,http,mail,misc,os,stream这几个文件夹

    • core

    为nginx的核心源代码,包括main函数/整体控制,基本数据结构封装,配置管理,内存管理,日志管理,文件读写网络套接字功能,系统参数资源通用管理等

    • event

    module子目录实现了Nginx支持的事件驱动模型:AIOepollkqueueselectpoll等,其他提供了事件驱动模型相关数据结构的定义、初始化、事件接收传递管理功能以及时间驱动模型调用功能

    • http

    module文件实现了http模块的功能,perl为对perl的支持,v2为对http2.0的支持,其他提供结构定义、初始化、网络连接建立、管理、关闭、数据报解析、upstream等通用功能

    • mail 邮件功能的实现
    • misc
    • os 根据操作系统对系统调用的封装
    • stream 为对TCP/UDP反向代理的支持

    开发扩展模块

    编辑新的模块步骤:

    1. 在自定义文件夹下,创建conf文件,说明名字和目录。
    2. 编写代码。
    3. 编译入nginx:
      在configure脚本执行时加入参数--add-module=PATH,执行后会生成objs/Makefile,objs/ngx_modules.c(也可以直接修改)。会执行conf文件,生成的ngx_modules.c包含nginx启动时加载的所有模块,nginx核心代码中全局modules从这里获取。这个模块就被编译到nginx程序中了。

    4.运行架构

    首先给出nginx的整体架构图,然后介绍运行架构中关注的运行模式,通信方式,IO处理选型。再总结下nginx运行架构的特性:事件驱动+碎片化+异步处理

    架构图

    运行架构
    说明:因为精力有限,只看了epoll的运行时架构,以下所有分析均只考虑linux并使用epoll的情况。
    启动后,有一个主进程,多个worker进行,两个cache相关进程。多个worker共同监听事件并处理,反向代理会把请求转发给后端服务。

    一主+多worker+cache manager+cache loader进程

    • master: 管理worker等子进程实现重启服务,平滑升级,更换日志文件,配置文件实时生效等
    • worker: 简单的负载均衡(高负载等待),抢锁,监听处理事件,接收master命令
    • cache: nginx开启缓存功能,会创建cache的两个进程,cache loader在nginx启动后将磁盘上次缓存的对象加载到内存后自动退出,cache管理进程清理超时缓存文件,限制缓存文件总大小,这个过程反反复复,直到Nginx整个进程退出。

    进程间通信

    nginx的进程间通信,在不同应用场景下采取不同的形式:

    • linux与master/worker/cache进程通信:信号

      master启动时先把感兴趣的信号注册;
      在主进程fork子进程之前要把所有信号调用sigprocmask阻塞住,等待fork成功后再将阻塞信号清除;
      主进程之后就挂起在sigsuspend中,等待信号;

    • 主进程与子进程通信:socketpair

      这里每个子进程和父进程之间使用的是socketpair系统调用建立起来的全双工的socket channel[]在父子进程中各有一套,channel[0]为写端,channel[1]为读端
      父进程关闭socket[0], 子进程关闭socket[1],父进程从sockets[1]中读写,子进程从sockets[0]中读写,还是全双工形态

      子进程也会监督部分信号,是master通过socketpair发送过去的。linux关闭worker后,worker也会通过socketpair把信号发送给主进程

    • 其他进程间共享数据:共享内存

      nginx中所有共享内存都是以list链表的形式组织在全局变量cf->cycle->shared_memory下,在创建新的共享内存之前会先对该链表进行遍历查找以及冲突检测,对于已经存在且不存在冲突的共享内存可直接返回引用。

      函数ngx_shm_alloc()时共享内存的实际分配,针对当前系统可提供的接口,可以是mmap,shmget等
      应用于进程(如子进程之间,在进程重启时新旧主进程需要抢锁等)间需要共享的数据,比如连接数/互斥锁等,另外提下锁有多重互斥方式,在操作系统支持的情况下用优先用原子操作。

    IO处理

    这部分为了并发需要考虑多进程,多线程,IO阻塞,IO非阻塞,每个进程处理一个还是多个事件 等典型的IO网络选型中的这几个问题。

    nginx在操作系统支持的情况下(不支持根据不同操作系统和配置,事件模型中选择不同IO处理方式)采取多进程,每个进程可以同时接收多个请求,IO多路复用非阻塞的方式。详细的运行架构如图。简单抽象过程:主进程创建监听socket后所有worker子进程继承共同监听,通过抢锁的方式决定同一时刻哪个worker是请求的acceptor方,accept请求后在本子进程中处理。

    运行架构

    • 多进程

    为了并发和利用多核处理,首先启用多进程的模式,在主进程创建所有监听的socket,为了所有worker都可以继承并监听该socket的fd。

    • 多acceptor,多handler 采取多acceptor,多handler的模式,每个进程在自己内部acceptor后分配给自己内部的handler处理。为了防止多acceptor同时accept的惊群现象,只有抢到锁的才把事件加入到监听,唤醒只会唤醒当前进程accept事件(新版的nginx采取reuseport可以一个端口被多个进程监听,支持的4.3的accept相关特征也不需要抢锁)
    • 进程accept多个 每个进程可以accept多个连接的模式,每次只处理accept,为三次握手完成的请求建立连接后就将其他事件放入延迟队列,释放锁后才处理这部分队列,以便其他进程可以继续抢锁

    worker监听处理的过程如图所示,在master启动的时候,为每个端口创建监听套接字listen socked(以下简称lss),然后fork出worker进程,所有worker进程继承同一份lss,为每个ls创建连接和事件结构,每个空闲worker抢锁获取这些lss的处理权。持有锁就将ls的读事件加入到epoll中等待,把接收事件分为两类:优先处理accept,延时处理非建立连接事件。accept后就释放锁。

    worker会有三种情况,一种是空闲但没有抢到锁,就等待事件后继续抢锁。另一种是在处理队列中请求,空闲后去抢锁。第三种是空闲并且抢到锁,则持有锁并监听和分配给hander后释放锁,处理队列请求

    事件驱动+碎片化+异步处理

    异步特征
    nginx所有需要等待的全都尽可能的碎片化,并加入到事件中,当事件ready后根据回调调用消费者处理,在Nginx里,Listen后是不需要循环等待accept,把他加入到epoll中,统一在epoll_wait中处理,当有返回直接调用accept。包括后续与客户端建立的主动连接(非Listen的)的所有事件也都统一在epoll_wait中等待,有事件直接调用事件的消费回调函数。在调用epoll_wait时也是一直循环等待事件没有退出,所以就要把事件拆分成特别细小的单元,这些单元都是可以异步执行的,有了epoll这个模型可以把任何涉及到磁盘读写的小粒度事件加入到监控中,比如读过了头第一行就去处理headline把其他的再加入到epoll中。

    三.nginx架构的思考分析

    考虑如果没有nginx,自己实现,是如何实现。

    1. 先聊运行架构

      要实现的简化功能概述:服务器要持续启动,监听8000端口,收到请求后解析http协议,若是静态请求,获取文件内容,封装为Http的响应协议格式,发送;若为动态请求,转化为fastcgi协议,转发给fastcgi程序,发送到响应的端口,获取数据后再转化为Http响应协议格式发送。
      为了实现高并发,当然要开启多个处理进程,因为要监听同一个端口,需要一个监听进程负责监听端口(在不考虑新的技术支持多个进程同时监听一个端口的情况),accept后分配给处理进程。对于单个监听端口最好设计是单acceptor多进程的形式,然而,这基本上是不可能实现的,因为多个进程处理完的数据如何返回给监听进程,大量的数据再进程间通信是不现实的。因此对于单个监听端口只能是单进程。或者改用线程,然而多线程不稳定。

      我可以对多个监听端口开启多个进程,每个监听不同的端口,但端口间流量分配不均匀时,进程负载不均衡。

      =》从监听处理进程个数上,nginx比我自己设计聪明的地方体现出来了,特殊的多reactor多进程结构。

      再说说每个进程里的高并发,网络连接肯定会有IO等待,此时若可以继续做其他的,会更快。每次接收一个请求的情况下,可以读完请求后,一边请求异步磁盘一边写返回头等;在有子请求和接受多个请求的情况下,可以一边为其他请求建立连接,一边处理本请求的事件,多个请求同时处理。因此设计为单进程可以同时accept多个,每个可以并行的操作都拆为单独事件异步处理。思想和nginx一样。

    2. 再聊聊开发架构,代码架构可以依照开发架构

      nginx因为支持反向代理,支持多平台,支持自定义配置,所以有配置模块,统一的事件模块,有抽象的配置结构。自己开发web服务器,可能不会考虑这么多,主要考虑http的处理过程,http固定的读取头,解析头,真实ip,权限,处理,输出协议的转换,写日志做成固定的顺序,直接调用固定函数。nginx再此之上,每个过程有自己的回调,整体阶段清晰,每个模块可以在把回调加入到各个子阶段,更灵活。协议按顺序也先固定输出链,若没有该协议直接跳过,对于这种运行前不知道会有哪些输出过滤的情况,自己写可能就在运行中判断有就调用了,nginx是固定走,没有直接跳过,这两种根据不同应用各有应用吧。

    高可扩展性

    1. 模块化

      无论配置,初始化,代码结构都是模块化的,各个模块要介入到主流程,根本不需要修改主流程代码,通过在hook位置增加回调。

    2. 高度抽象

      正常很难想到所有的模块和配置全部都一个抽象结构,各个子模块也都统一抽象结构,新增加功能简单,可读性高

    3. 输出统一过滤链,功能可插拔

      扩展容易,代码简洁,可读性高

    高性能

    1. 多reactor多进程结构

      经过上述分析,Nginx在并发选型上要么是单reactor单进程结构,要么是单reactor多线程结构,但多线程只要一个操作共享区域,会影响其他线程,所以在不需要共享数据的情况下,最好用多进程。nginx巧妙的虽然同一时刻只能单reactor,但在accept后立刻释放锁,也达到多reactor的性能,此架构不常见,可以参考。memcache,mysql等因为要共享数据都是多reactor多线程;apache旧版是一个进程处理一个请求,类似phpfpm,本质上是单reactor单进程,后来一个进程中有多个线程,单reactor多线程,但每个线程处理一个请求;后面也加入了IO多路复用,每个线程中处理多个请求。

    2. 后续preactor+线程

    preactor

    本质上epoll还是等待的,还是需要进程去询问,利用内核异步IO,可以做到事件自动处理,处理后通知,不需要询问,其架构如下:

    单linux的AIO还不完善,到目前为止,nginx实现了AIO+线程的模型,但还未应用。

    1. 内存池,连接池

      为了省去每次申请,减少内存碎片,统一释放等,提前准备好内存池和连接池 。

    四.总结

    nginx作为一个高性能高可用高可扩展的 http服务器和多协议反向代理服务器,其运行架构采用特殊的监听同一端口却多reactor多进程的模型,值得借鉴;高度抽象和模块化的逻辑架构使得功能庞大代码却清晰易懂,开发和扩展代价低。

    • 参考

    nginx功能 http://nginx.org/en/
    nginx代码结构 https://www.kancloud.cn/diges...

    展开全文
  • Nginx架构

    千次阅读 2021-07-09 14:53:51
    Nginx架构 > master进程主要用来管理worker进程,具体包括如下4个主要功能: > 1)接受来自外界的信号。其中master循环中的各项标志位就对应着各种信号,如:ngx_quit代表QUIT信号,表示优雅的关闭整个服务...

    这张图真的很nice

    Nginx架构 

    > master进程主要用来管理worker进程,具体包括如下4个主要功能:

    > 1)接受来自外界的信号。其中master循环中的各项标志位就对应着各种信号,如:ngx_quit代表QUIT信号,表示优雅的关闭整个服务。

    > 2)向各个worker进程发送信。比如ngx_noaccept代表WINCH信号,表示所有子进程不再接受处理新的连接,由master向所有的子进程发送QUIT信号量。

    > 3)监控worker进程的运行状态。比如ngx_reap代表CHILD信号,表示有子进程意外结束,这时需要监控所有子进程的运行状态,主要由ngx_reap_children完成。

    > 4)当woker进程退出后(异常情况下),会自动重新启动新的woker进程。主要也是在ngx_reap_children。

    > Nginx热更配置,具体流程:master使用新配置启动新的worker,旧的worker不再接受新的请求。

    > Nginx热更配置,具体流程:新旧master和worker共存。向老master发送WINCH信号,关闭旧worker进程,观察新worker进程工作情况。若升级成功,则向老master进程发送QUIT信号,关闭老master进程

    > epoll利用红黑树高效的增删查效率来管理连接,利用一个双向链表来维护活跃连接

    > 多个worker来出来accept事件冲突:设置ngx_accept_mutex锁,只有获得锁的进程,才可以处理连接事件

    > 为什么不采用多线程模型管理连接/处理逻辑业务? 无状态服务,无需共享进程内存;一个进程异常崩溃,其他进程的服务不会中断,提升了架构的可靠性;作为接入层,基本上都是数据转发业务,网络IO任务的等待耗时部分,已经被处理为非阻塞/全异步/事件驱动模式,在没有更多CPU的情况下,再利用多线程处理,意义不大

    展开全文
  • Nginx架构浅析

    千次阅读 2021-04-02 22:39:56
    文章目录Nginx架构浅析1.Nginx基础架构2.Master进程2.1核心逻辑2.2热更2.2.1热重载-配置热更2.2.2热升级-程序热更3.Worker进程3.1核心逻辑3.2事件驱动-epoll3.3惊群3.4负载均衡4.思考4.1为什么不采用多线程模型管理...

    Nginx架构浅析

    最近项目中使用了基于Nginx的OpenResty的框架,于是有了想学习了解Nginx相关内容的想法,现将一些理解撰写成文,和大家讨论一下。

    1.Nginx基础架构

    nginx启动后以daemon形式在后台运行,后台进程包含一个master进程和多个worker进程。如下所示:nginx1-master与worker
    nginx是由一个master管理进程,多个worker进程处理工作的多进程模型。基础架构设计,如下所示:
    nginx2-基础架构设计
    master负责管理worker进程,worker进程负责处理网络事件。整个框架被设计为一种依赖事件驱动、异步、非阻塞的模式。

    设计优点:

    • 1.可以充分利用多核机器,增强并发处理能力。
    • 2.多worker间可以实现负载均衡。
    • 3.Master监控并统一管理worker行为。在worker异常后,可以主动拉起worker进程,从而提升了系统的可靠性。并且由Master进程控制服务运行中的程序升级、配置项修改等操作,从而增强了整体的动态可扩展与热更的能力。

    2.Master进程

    2.1核心逻辑

    master进程的主逻辑在ngx_master_process_cycle,核心关注源码:

    ngx_master_process_cycle(ngx_cycle_t *cycle)
    {
        ...
        ngx_start_worker_processes(cycle, ccf->worker_processes,
                                            NGX_PROCESS_RESPAWN);
        ...
    
        
        for ( ;; ) {
            if (delay) {...}
    
            ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "sigsuspend");
    
            sigsuspend(&set);
    
            ngx_time_update();
    
            ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                                 "wake up, sigio %i", sigio);
    
            if (ngx_reap) {
                ngx_reap = 0;
                ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "reap children");
                live = ngx_reap_children(cycle);
            }
    
            if (!live && (ngx_terminate || ngx_quit)) {...}
    
            if (ngx_terminate) {...}
    
            if (ngx_quit) {...}
    
            if (ngx_reconfigure) {...}
    
            if (ngx_restart) {...}
    
            if (ngx_reopen) {...}
    
            if (ngx_change_binary) {...}
    
            if (ngx_noaccept) {
                ngx_noaccept = 0;
                ngx_noaccepting = 1;
                ngx_signal_worker_processes(cycle,
                                                      ngx_signal_value(NGX_SHUTDOWN_SIGNAL));
            }
        }
     }
    

    由上述代码,可以理解,master进程主要用来管理worker进程,具体包括如下4个主要功能:

    • 1.接受来自外界的信号。其中master循环中的各项标志位就对应着各种信号,如:ngx_quit代表QUIT信号,表示优雅的关闭整个服务。
    • 2.向各个worker进程发送信。比如ngx_noaccept代表WINCH信号,表示所有子进程不再接受处理新的连接,由master向所有的子进程发送QUIT信号量。
    • 3.监控worker进程的运行状态。比如ngx_reap代表CHILD信号,表示有子进程意外结束,这时需要监控所有子进程的运行状态,主要由ngx_reap_children完成。
    • 4.当woker进程退出后(异常情况下),会自动重新启动新的woker进程。主要也是在ngx_reap_children

    2.2热更

    2.2.1热重载-配置热更

    nginx3-热重载

    nginx热更配置时,可以保持运行中平滑更新配置,具体流程如下:

    • 1.更新nginx.conf配置文件,向master发送SIGHUP信号或执行nginx -s reload
    • 2.master进程使用新配置,启动新的worker进程
    • 3.使用旧配置的worker进程,不再接受新的连接请求,并在完成已存在的连接后退出

    2.2.2热升级-程序热更

    nginx4-热升级

    nginx热升级过程如下:

    • 1.将旧Nginx文件换成新Nginx文件(注意备份)
    • 2.向master进程发送USR2信号(平滑升级到新版本的Nginx程序)
    • 3.master进程修改pid文件号,加后缀.oldbin
    • 4.master进程用新Nginx文件启动新master进程,此时新老master/worker同时存在。
    • 5.向老master发送WINCH信号,关闭旧worker进程,观察新worker进程工作情况。若升级成功,则向老master进程发送QUIT信号,关闭老master进程;若升级失败,则需要回滚,向老master发送HUP信号(重读配置文件),向新master发送QUIT信号,关闭新master及worker。

    3.Worker进程

    3.1核心逻辑

    worker进程的主逻辑在ngx_worker_process_cycle,核心关注源码:

    ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data)
    {
        ngx_int_t worker = (intptr_t) data;
    
        ngx_process = NGX_PROCESS_WORKER;
        ngx_worker = worker;
    
        ngx_worker_process_init(cycle, worker);
    
        ngx_setproctitle("worker process");
    
        for ( ;; ) {
    
            if (ngx_exiting) {...}
    
            ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "worker cycle");
    
            ngx_process_events_and_timers(cycle);
    
            if (ngx_terminate) {...}
    
            if (ngx_quit) {...}
    
            if (ngx_reopen) {...}
        }
    }
    

    由上述代码,可以理解,worker进程主要在处理网络事件,通过ngx_process_events_and_timers方法实现,其中事件主要包括:网络事件、定时器事件。

    3.2事件驱动-epoll

    worker进程在处理网络事件时,依靠epoll模型,来管理并发连接,实现了事件驱动、异步、非阻塞等特性。如下图所示:
    nginx5-infographic-Inside-NGINX_nonblocking
    通常海量并发连接过程中,每一时刻(相对较短的一段时间),往往只需要处理一小部分有事件的连接即活跃连接。基于以上现象,epoll通过将连接管理活跃连接管理进行分离,实现了高效、稳定的网络IO处理能力。
    nginx6-网络模型对比
    其中,epoll利用红黑树高效的增删查效率来管理连接,利用一个双向链表来维护活跃连接
    nginx7-epoll数据结构

    3.3惊群

    由于worker都是由master进程fork产生,所以worker都会监听相同端口。这样多个子进程在accept建立连接时会发生争抢,带来著名的“惊群”问题。
    worker核心处理逻辑ngx_process_events_and_timers核心代码如下:

    void ngx_process_events_and_timers(ngx_cycle_t *cycle){
        //这里面会对监听socket处理
        ...
        
        if (ngx_accept_disabled > 0) {
                ngx_accept_disabled--;
        } else {
            //获得锁则加入wait集合,
            if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
                return;
            }
            ...
            //设置网络读写事件延迟处理标志,即在释放锁后处理
            if (ngx_accept_mutex_held) {
                flags |= NGX_POST_EVENTS;
            }
        }
        ...
        //这里面epollwait等待网络事件
        //网络连接事件,放入ngx_posted_accept_events队列
        //网络读写事件,放入ngx_posted_events队列
        (void) ngx_process_events(cycle, timer, flags);
        ...
        //先处理网络连接事件,只有获取到锁,这里才会有连接事件
        ngx_event_process_posted(cycle, &ngx_posted_accept_events);
        //释放锁,让其他进程也能够拿到
        if (ngx_accept_mutex_held) {
            ngx_shmtx_unlock(&ngx_accept_mutex);
        }
        //处理网络读写事件
        ngx_event_process_posted(cycle, &ngx_posted_events);
    }
    

    由上述代码可知,Nginx解决惊群的方法:

    • 1.将连接事件与读写事件进行分离。连接事件存放为ngx_posted_accept_events,读写事件存放为ngx_posted_events
    • 2.设置ngx_accept_mutex锁,只有获得锁的进程,才可以处理连接事件。

    3.4负载均衡

    worker间的负载关键在于各自接入了多少连接,其中接入连接抢锁的前置条件是ngx_accept_disabled > 0,所以ngx_accept_disabled就是负载均衡机制实现的关键阈值。

    ngx_int_t             ngx_accept_disabled;
    ngx_accept_disabled = ngx_cycle->connection_n / 8 - ngx_cycle->free_connection_n;
    

    因此,在nginx启动时,ngx_accept_disabled的值就是一个负数,其值为连接总数的7/8。当该进程的连接数达到总连接数的7/8时,该进程就不会再处理新的连接了,同时每次调用’ngx_process_events_and_timers’时,将ngx_accept_disabled减1,直到其值低于阈值时,才试图重新处理新的连接。因此,nginx各worker子进程间的负载均衡仅在某个worker进程处理的连接数达到它最大处理总数的7/8时才会触发,其负载均衡并不是在任意条件都满足。如下图所示:
    nginx8-实际工作情况

    其中’pid’为1211的进程为master进程,其余为worker进程

    4.思考

    4.1为什么不采用多线程模型管理连接?

    • 1.无状态服务,无需共享进程内存
    • 2.采用独立的进程,可以让互相之间不会影响。一个进程异常崩溃,其他进程的服务不会中断,提升了架构的可靠性。
    • 3.进程之间不共享资源,不需要加锁,所以省掉了锁带来的开销。

    4.2为什么不采用多线程处理逻辑业务?

    • 1.进程数已经等于核心数,再新建线程处理任务,只会抢占现有进程,增加切换代价。
    • 2.作为接入层,基本上都是数据转发业务,网络IO任务的等待耗时部分,已经被处理为非阻塞/全异步/事件驱动模式,在没有更多CPU的情况下,再利用多线程处理,意义不大。并且如果进程中有阻塞的处理逻辑,应该由各个业务进行解决,比如openResty中利用了Lua协程,对阻塞业务进行了优化。

    PPT下载

    Nginx架构浅析PPT

    展开全文
  • Nginx】初探 Nginx 架构

    千次阅读 2019-12-27 17:33:29
    初探nginx架构02. 参考 01. 初探nginx架构 众所周知,nginx性能高,而nginx的高性能与其架构是分不开的。那么nginx究竟是怎么样的呢?这一节我们先来初识一下nginx框架吧。 nginx在启动后,在unix系统中会以daemon...

    00. 目录

    01. 初探nginx架构

    众所周知,nginx性能高,而nginx的高性能与其架构是分不开的。那么nginx究竟是怎么样的呢?这一节我们先来初识一下nginx框架吧。

    nginx在启动后,在unix系统中会以daemon的方式在后台运行,后台进程包含一个master进程和多个worker进程。我们也可以手动地关掉后台模式,让nginx在前台运行,并且通过配置让nginx取消master进程,从而可以使nginx以单进程方式运行。很显然,生产环境下我们肯定不会这么做,所以关闭后台模式,一般是用来调试用的,在后面的章节里面,我们会详细地讲解如何调试nginx。所以,我们可以看到,nginx是以多进程的方式来工作的,当然nginx也是支持多线程的方式的,只是我们主流的方式还是多进程的方式,也是nginx的默认方式。nginx采用多进程的方式有诸多好处,所以我就主要讲解nginx的多进程模式吧。

    刚才讲到,nginx在启动后,会有一个master进程和多个worker进程。master进程主要用来管理worker进程,包含:接收来自外界的信号,向各worker进程发送信号,监控worker进程的运行状态,当worker进程退出后(异常情况下),会自动重新启动新的worker进程。而基本的网络事件,则是放在worker进程中来处理了。多个worker进程之间是对等的,他们同等竞争来自客户端的请求,各进程互相之间是独立的。一个请求,只可能在一个worker进程中处理,一个worker进程,不可能处理其它进程的请求。worker进程的个数是可以设置的,一般我们会设置与机器cpu核数一致,这里面的原因与nginx的进程模型以及事件处理模型是分不开的。nginx的进程模型,可以由下图来表示:

    在这里插入图片描述

    在nginx启动后,如果我们要操作nginx,要怎么做呢?从上文中我们可以看到,master来管理worker进程,所以我们只需要与master进程通信就行了。master进程会接收来自外界发来的信号,再根据信号做不同的事情。所以我们要控制nginx,只需要通过kill向master进程发送信号就行了。比如kill -HUP pid,则是告诉nginx,从容地重启nginx,我们一般用这个信号来重启nginx,或重新加载配置,因为是从容地重启,因此服务是不中断的。master进程在接收到HUP信号后是怎么做的呢?首先master进程在接到信号后,会先重新加载配置文件,然后再启动新的worker进程,并向所有老的worker进程发送信号,告诉他们可以光荣退休了。新的worker在启动后,就开始接收新的请求,而老的worker在收到来自master的信号后,就不再接收新的请求,并且在当前进程中的所有未处理完的请求处理完成后,再退出。当然,直接给master进程发送信号,这是比较老的操作方式,nginx在0.8版本之后,引入了一系列命令行参数,来方便我们管理。比如,./nginx -s reload,就是来重启nginx,./nginx -s stop,就是来停止nginx的运行。如何做到的呢?我们还是拿reload来说,我们看到,执行命令时,我们是启动一个新的nginx进程,而新的nginx进程在解析到reload参数后,就知道我们的目的是控制nginx来重新加载配置文件了,它会向master进程发送信号,然后接下来的动作,就和我们直接向master进程发送信号一样了。

    现在,我们知道了当我们在操作nginx的时候,nginx内部做了些什么事情,那么,worker进程又是如何处理请求的呢?我们前面有提到,worker进程之间是平等的,每个进程,处理请求的机会也是一样的。当我们提供80端口的http服务时,一个连接请求过来,每个进程都有可能处理这个连接,怎么做到的呢?首先,每个worker进程都是从master进程fork过来,在master进程里面,先建立好需要listen的socket(listenfd)之后,然后再fork出多个worker进程。所有worker进程的listenfd会在新连接到来时变得可读,为保证只有一个进程处理该连接,所有worker进程在注册listenfd读事件前抢accept_mutex,抢到互斥锁的那个进程注册listenfd读事件,在读事件里调用accept接受该连接。当一个worker进程在accept这个连接之后,就开始读取请求,解析请求,处理请求,产生数据后,再返回给客户端,最后才断开连接,这样一个完整的请求就是这样的了。我们可以看到,一个请求,完全由worker进程来处理,而且只在一个worker进程中处理。

    那么,nginx采用这种进程模型有什么好处呢?当然,好处肯定会很多了。首先,对于每个worker进程来说,独立的进程,不需要加锁,所以省掉了锁带来的开销,同时在编程以及问题查找时,也会方便很多。其次,采用独立的进程,可以让互相之间不会影响,一个进程退出后,其它进程还在工作,服务不会中断,master进程则很快启动新的worker进程。当然,worker进程的异常退出,肯定是程序有bug了,异常退出,会导致当前worker上的所有请求失败,不过不会影响到所有请求,所以降低了风险。当然,好处还有很多,大家可以慢慢体会。

    上面讲了很多关于nginx的进程模型,接下来,我们来看看nginx是如何处理事件的。

    有人可能要问了,nginx采用多worker的方式来处理请求,每个worker里面只有一个主线程,那能够处理的并发数很有限啊,多少个worker就能处理多少个并发,何来高并发呢?非也,这就是nginx的高明之处,nginx采用了异步非阻塞的方式来处理请求,也就是说,nginx是可以同时处理成千上万个请求的。想想apache的常用工作方式(apache也有异步非阻塞版本,但因其与自带某些模块冲突,所以不常用),每个请求会独占一个工作线程,当并发数上到几千时,就同时有几千的线程在处理请求了。这对操作系统来说,是个不小的挑战,线程带来的内存占用非常大,线程的上下文切换带来的cpu开销很大,自然性能就上不去了,而这些开销完全是没有意义的。

    为什么nginx可以采用异步非阻塞的方式来处理呢,或者异步非阻塞到底是怎么回事呢?我们先回到原点,看看一个请求的完整过程。首先,请求过来,要建立连接,然后再接收数据,接收数据后,再发送数据。具体到系统底层,就是读写事件,而当读写事件没有准备好时,必然不可操作,如果不用非阻塞的方式来调用,那就得阻塞调用了,事件没有准备好,那就只能等了,等事件准备好了,你再继续吧。阻塞调用会进入内核等待,cpu就会让出去给别人用了,对单线程的worker来说,显然不合适,当网络事件越多时,大家都在等待呢,cpu空闲下来没人用,cpu利用率自然上不去了,更别谈高并发了。好吧,你说加进程数,这跟apache的线程模型有什么区别,注意,别增加无谓的上下文切换。所以,在nginx里面,最忌讳阻塞的系统调用了。不要阻塞,那就非阻塞喽。非阻塞就是,事件没有准备好,马上返回EAGAIN,告诉你,事件还没准备好呢,你慌什么,过会再来吧。好吧,你过一会,再来检查一下事件,直到事件准备好了为止,在这期间,你就可以先去做其它事情,然后再来看看事件好了没。虽然不阻塞了,但你得不时地过来检查一下事件的状态,你可以做更多的事情了,但带来的开销也是不小的。所以,才会有了异步非阻塞的事件处理机制,具体到系统调用就是像select/poll/epoll/kqueue这样的系统调用。它们提供了一种机制,让你可以同时监控多个事件,调用他们是阻塞的,但可以设置超时时间,在超时时间之内,如果有事件准备好了,就返回。这种机制正好解决了我们上面的两个问题,拿epoll为例(在后面的例子中,我们多以epoll为例子,以代表这一类函数),当事件没准备好时,放到epoll里面,事件准备好了,我们就去读写,当读写返回EAGAIN时,我们将它再次加入到epoll里面。这样,只要有事件准备好了,我们就去处理它,只有当所有事件都没准备好时,才在epoll里面等着。这样,我们就可以并发处理大量的并发了,当然,这里的并发请求,是指未处理完的请求,线程只有一个,所以同时能处理的请求当然只有一个了,只是在请求间进行不断地切换而已,切换也是因为异步事件未准备好,而主动让出的。这里的切换是没有任何代价,你可以理解为循环处理多个准备好的事件,事实上就是这样的。与多线程相比,这种事件处理方式是有很大的优势的,不需要创建线程,每个请求占用的内存也很少,没有上下文切换,事件处理非常的轻量级。并发数再多也不会导致无谓的资源浪费(上下文切换)。更多的并发数,只是会占用更多的内存而已。 我之前有对连接数进行过测试,在24G内存的机器上,处理的并发请求数达到过200万。现在的网络服务器基本都采用这种方式,这也是nginx性能高效的主要原因。

    我们之前说过,推荐设置worker的个数为cpu的核数,在这里就很容易理解了,更多的worker数,只会导致进程来竞争cpu资源了,从而带来不必要的上下文切换。而且,nginx为了更好的利用多核特性,提供了cpu亲缘性的绑定选项,我们可以将某一个进程绑定在某一个核上,这样就不会因为进程的切换带来cache的失效。像这种小的优化在nginx中非常常见,同时也说明了nginx作者的苦心孤诣。比如,nginx在做4个字节的字符串比较时,会将4个字符转换成一个int型,再作比较,以减少cpu的指令数等等。

    现在,知道了nginx为什么会选择这样的进程模型与事件模型了。对于一个基本的web服务器来说,事件通常有三种类型,网络事件、信号、定时器。从上面的讲解中知道,网络事件通过异步非阻塞可以很好的解决掉。如何处理信号与定时器?

    首先,信号的处理。对nginx来说,有一些特定的信号,代表着特定的意义。信号会中断掉程序当前的运行,在改变状态后,继续执行。如果是系统调用,则可能会导致系统调用的失败,需要重入。关于信号的处理,大家可以学习一些专业书籍,这里不多说。对于nginx来说,如果nginx正在等待事件(epoll_wait时),如果程序收到信号,在信号处理函数处理完后,epoll_wait会返回错误,然后程序可再次进入epoll_wait调用。

    另外,再来看看定时器。由于epoll_wait等函数在调用的时候是可以设置一个超时时间的,所以nginx借助这个超时时间来实现定时器。nginx里面的定时器事件是放在一颗维护定时器的红黑树里面,每次在进入epoll_wait前,先从该红黑树里面拿到所有定时器事件的最小时间,在计算出epoll_wait的超时时间后进入epoll_wait。所以,当没有事件产生,也没有中断信号时,epoll_wait会超时,也就是说,定时器事件到了。这时,nginx会检查所有的超时事件,将他们的状态设置为超时,然后再去处理网络事件。由此可以看出,当我们写nginx代码时,在处理网络事件的回调函数时,通常做的第一个事情就是判断超时,然后再去处理网络事件。

    我们可以用一段伪代码来总结一下nginx的事件处理模型:

    
    while (true) {
        for t in run_tasks:
            t.handler();
        update_time(&now);
        timeout = ETERNITY;
        for t in wait_tasks: /* sorted already */
            if (t.time <= now) {
                t.timeout_handler();
            } else {
                timeout = t.time - now;
                break;
            }
        nevents = poll_function(events, timeout);
        for i in nevents:
            task t;
            if (events[i].type == READ) {
                t.handler = read_handler;
            } else { /* events[i].type == WRITE */
                t.handler = write_handler;
            }
            run_tasks_add(t);
    }
    

    本节我们讲了进程模型,事件模型,包括网络事件,信号,定时器事件。

    02. 参考

    1. W3CSchool: https://www.w3cschool.cn/nginx/sd361pdz.html
    展开全文
  • Nginx 架构原理

    千次阅读 2019-08-15 17:06:12
    Nginx 架构 1简介 Nginx 专注于高性能、高并发、低内存使用率。 功能:load balance,web server,caching,访问和带宽控制等等。 2高并发性为什么这么重要? 系统的并发情形在过去就一直存在,同时系统也会...
  • Nginx架构说明

    2019-03-23 02:20:08
    Nginx架构 Nginx 架构图: 架构说明: 1)nginx启动时,会生成两种类型的进程,一个是主进程(Master),一个(windows版本的目前只有一个)和多个工作进程(Worker)。主进程并不处理网络请求,主要负责调度工作...
  • Nginx架构设计

    千次阅读 2018-01-29 11:07:49
    Nginx架构设计 这里先说下Nginx设计时重视的几个关键点: 性能:包括网络性能,单词请求的延迟性,网络效率;(名词就不解释了~)可伸缩性:可通过添加组件来提升服务,或者允许组件之间具有交互功能...
  • 高度模块化的设计是Nginx架构基础。Nginx服务器被分解为多个模块,每个模块就是一个功能模块,只负责自身的功能,模块之间严格遵循“高内聚,低耦合”的原则。核心模块 核心模块是Nginx服务器正常运行必不可少的...
  • nginx架构与实现

    2015-09-06 23:20:51
    nginx架构与实现分析
  • nginx架构解析

    2018-11-30 14:57:05
    Nginx 可以在大多数 UnixLinux OS 上编译运行,并有 Windows 移植版。 Nginx 的1.4.0稳定版已经于2013年4月24日发布,一般情况下,对于新建站点,建议使用最新稳定版作为生产版本,已有站点的升级急迫性不高。 Nginx...
  • grpc+nginx架构部署指导

    万次阅读 2019-08-21 17:18:08
    本文介绍使用nginx管理grpc流量、部署grpc+nginx架构的方法。 1. 环境信息 本节介绍本文示例使用的环境信息,如下: 软件名称 版本信息 操作系统 CentOS Linux release 7.2 gRPC 1.10.1-pre1 ...
  • nginx架构与基础概念

    千次阅读 2016-06-11 20:44:32
    趁着端午假期,了解了一些关于nginx web服务器的知识,顺便摘抄整理一些要点。 ... 1 Nginx架构 ...Nginx架构: nginx运行时,在unix系统中以daemon形式在后台运行,后台进程包含一个master进程和多个worker进程
  • 视频讲解:Nginx架构,地址重写、反向代理、负载均衡等内容。Nginx:取代Apache的高性能Web服务器;Nginx是当前最流行,也是最热门的WEB服务器,近几年呈快速增加的趋势。Nginx的并发能力,以及自带的反向代理和...
  • 一种基于Python+Nginx架构的双向CA系统设计与实现.pdf
  • Nginx架构分析

    2016-04-27 14:37:32
    传统的web服务器和应用服务器架构设计上采用多进程或线程作为其处理业务的基本单位,而Nginx更多的使用了事件驱动的架构。正是这种架构使得Nginx可以轻松支持数十万的并发链接。【译注:Nginx相比其他的web服务器...
  • Nginx架构的企业级应用
  • Nginx高性能Web服务器之Nginx架构

    千次阅读 2019-10-09 19:19:58
    Nginx服务器架构初探 模块化设计:没有统一的定义,各模块之间低耦合。 “功能块”是对模块的描述,一个模块就是一个功能块,应该只负责一个功能,在设计模式理论中类似于“单一职责原则” “模块化”,即对程序...
  • nginx架构初探

    2016-01-11 14:11:57
    注:个人学习的一些知识点笔记参考:《Nginx开发从入门到精通》,《深入理解Nginx》1.nginx可以后台运行,也可以前台运行(一般用于调试);2.nginx支持多线程,只是我们主流的方式还是多进程的方式,也是nginx的...
  • Nginx学习(11)—Nginx架构设计(1)

    千次阅读 2014-04-28 12:00:22
    Nginx架构设计 这里先说下Nginx设计时重视的几个关键点: 性能:包括网络性能,单词请求的延迟性,网络效率;(名词就不解释了~)可伸缩性:可通过添加组件来提升服务,或者允许组件之间具有交互功能;简单性...
  • 一、Nginx和tomcat的安装 1、Nginx安装见: https://blog.csdn.net/weixin_44571270/article/details/102887048 2、tomcat安装如下: (1)下载tomcat wget ...
  • nginx架构分析

    千次阅读 2016-02-13 02:37:57
    并且具有多种web服务器功能特性:负载均衡,缓存,访问控制,带宽控制,以及高效整合各种应用的能力,这些特性使nginx很适合于现代网站架构。目前,nginx已经是互联网上第二流行的开源web服务器软件。 1
  • Nginx架构

    2019-12-08 23:02:04
  • Nginx架构之模块化结构体系

    千次阅读 2020-01-09 10:03:17
    备注:随着版本的不同Nginx的版本也会有所不同,比如STREAM模块的出现。具体情况可以参考《Nginx源码初探 -之数据结构 - 模块...在架构Nginx受到各种操作系统中高级事件机制处理模式的启发,Nginx实现了整体基于m...
  • Nginx安装(yum) yum安装Nginx,需要使用到epel-release源。这个可以先通过yum安装源。不过这个源安装的Nginx可能版本比较老。 你也可以自己弄一个Nginx源 写一个文件: [root@shuai-01 ~]# vim /etc/yum.repos...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 132,394
精华内容 52,957
关键字:

nginx架构