【3.工程开发】-nginx架构

一.概述

本文将深刻剖析nginx的架构。php

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

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

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

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

二.nginx现有架构实现

功能介绍

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

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

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

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在逻辑上分为入口层,模块化的功能处理层,系统调用层。入口调用配置模块和核心模块,各核心模块分别调用各自功能模块,系统调用层封装了各个操做系统的功能被功能处理层使用。逻辑架构最明显主要的特征就是高度模块化,全部功能都是模块,每一个模块都统一结构,下面先看下这个统一结构,而后分别介绍各个模块。
逻辑架构web

特征——高度模块化

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

特征

核心模块/配置模块

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

事件模块

负责请求链接的创建,分发等网络事件及定时器事件,其中全部模块封装到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...

相关文章
相关标签/搜索