Nginx是一个轻量级Http服务器。使用Master管理进程+Worker工作进程的Multi-Reactor多进程模型设计。整体架构图大致如下:
-
Master+Worker
Matster管理Worker,Worker处理请求。
-
后台服务
Nginx可以代理后台服务,真正的服务可以是Web服务,应用服务,数据库服务等。
-
缓存
这里的cachemanager其实分为:proxy cache、cacheloader和cache manager。
-
cache manager
定期清理过期缓存,基本策略LRU。
-
cache loader
将缓存的文件映射到内存中。刚启动的时候就要把缓存文件加载到内存中。
-
进程模型
Nginx分为单工作进程模型(1个Master1个Worker)和多进程模型(1个Master多个Worker)。 Nginx共4个进程:Master、Worker、CacheLoader、CacheManager。
Master进程
-
Master进程充当整个进程组与用户交互的接口
不具体处理网络事件、不负责业务的执行,只是管理worker进程来实现服务重启、平滑升级、更换日志文件、配置文件实时生效。
- 加载配置文件
- 启动工作进程
- 非停升级平滑升级
-
对Worker进程进行监护
向各个Worker进程发送信号或管道方式进行控制,监控worker进程的运行状态。 如果有Worker意外退出,会自动启动新的worker进程。
Worker进程
- 基本网络事件由Worker进程处理
- 不同Worker平等、相互独立,一个请求只能交给一个Worker处理
- 一般Worker进程数设置为CPU核心数
worker_processes CPU核心数 - Worker之间通过共享内存,原子操作等实现负载均衡
- 关闭时,没有连接的Worker会直接关闭,其他Worker如果还有连接会等连接释放后才关闭。这也是多进程的好处,进程之间是相互独立的。
每个Worker进程都可以处理数以千计的网络请求,Worker进程可以同时处理的请求数只受限于内存大小。
Worker处理Web请求,与浏览器保持连接,处理响应。具体操作有读取请求、解析请求、处理请求、产生数据后,再返回给客户端,最后断开连接。
连接池
每个Worker进程有独立的ngx_cycle_t数据结构
进程管理:信号
上文提到Worker进程之间的通信会通过信号,并且主要通过信号。
- Master进程
- 监控Worker进程有没有发送CHLD信号(子进程终止会向父进程发送CHLD信号)
- 管理Worker进程,接收TERM、INT(立刻停止);QUIT(优雅停止);HUP(重载配置文件);USER1(重新打开日志文件 做日志文件切割);USER2(关于热部署 找到Master PID发送);WINCH(关于热部署 找到Master PID发送);信号
- Worker进程
- 接收TERM、INT(立刻停止);QUIT(优雅停止);USER1(重新打开日志文件 做日志文件切割);WINCH(关于热部署 找到Master PID发送);信号
- nginx命令行
- reload:HUP
- reopen:USER1
- stop:TERM
- quit:QUIT
Worker实现连接——抢占机制
一个连接过来后有多个Worker,如何决定要连接哪个Worker呢,这就涉及到Worker的抢占机制了。 Master监听指定端口,并fork出了多个Worker进程。当一个请求从客户端过来的时候,Worker需要抢占互斥锁/accept_mutex,抢占成功的Worker进行处理。
Worker默认的并发能力可通过worker_connections 1024设置。默认IO模型为epoll。
events{
use epoll;
worker_connections 1024
}
connection
Nginx中的connection其实就是对tcp连接的封装,包括socket以及读写事件。
Nginx连接_作为服务端
- Nginx刚启动时,解析配置文件,得到需要监听的IP地址和端口
- 在Master进程初始化socket
- Master进程fork多个worker进程,同时Worker进程会竞争accept新连接
完成以上三步后,客户端就可以向Nginx发起连接了。当进行TCP三次提手之后,Nginx的某一个Worker进程会accept成功,然后就根据这个socket,创建Nginx对socket的封装,即ngx_connection_t结构体。
Nginx连接_作为客户端
创建连接会封装在ngx_connection_t结构体,然后创建socket并设置属性,添加读写事件。
最大连接数
Nginx中进程的最大连接数上限和系统的FD连接数上限不同。Nginx中进程的最大连接数上限是前面提到的worker_connections设置的数值,操作系统进程的最大连接数是ulimit -n显示的FD最大数。如果操作系统的最大连接数小于Nginx的最大连接数也没关系,因为通过连接池来管理。
每个Worker进程会有1个连接池、1个链表
-
连接池
连接池大小是长度为
worker_connections,数据结构为ngx_connection_t的数组。 -
链表
链表
free_connections保存空闲ngx_connection_t。每获取一个连接,就从空闲链表中获取一个,用完放回链表。
如果Nginx作为应用服务器最大连接数为工作进程数worker_processes \* worker_connections。
如果作为反向代理服务器为工作进程数worker_processes \* worker_connections/2,因为每个并发都会建立与客户端以及后端服务的连接。
模块设计
核心模块
- ngx_http_module 定义HTTP模块
- ngx_events_module 定义事件模块
- ngx_mail_module 定义Mail模块
- ngx_openssl_module 加载模块支持HTTPS请求
- ngx_errlog_module 错误日志处理
- ngx_core_module nginx启动后负责加载的第一个模块 主要用来保存全局对象。
HTTP模块
-
标准HTTP模块
提供HTTP协议解析相关的功能,比如:端口配置、网页编码设置、HTTP响应头设置等等。
-
可选HTTP模块
扩展标准的 HTTP 功能,让Nginx能处理一些特殊的服务,比如: Flash多媒体传输、解析GeoIP 请求、网络传输压缩、安全协议SSL支持等。
Event模块
负责事件的注册、分发处理、销毁。支持各类操作系统的事件驱动模型,例如epoll、poll、select、kqueue、eventport等。
Mail模块
邮件服务模块
配置模块
其他模块基础。
接收连接请求流程
结合以上对connection的分析和和http模块的简要介绍。HTTP模块处理前,nginx需要先建立连接,接收客户端发来的http请求。根据Header信息决定需要哪些模块,让HTTP模块知道怎样处理请求。
-
- 建立连接
当用户发来SYN时,由内核回应SYN+ACK。
-
- 选中Worker进程 当操作系统内核收到最后一次握手消息ACK后,会通过负载均衡算法,操作系统选中Worker进程。
-
- epoll_wait
Worker中的方法epoll_wait会返回建立好连接的句柄。
-
- accept
监听到ACK读事件,会调用accept分配连接内存池。连接内存池分配大小
connection_pool_size: 512内存池是可以扩展的。
-
- ngx_http_init_connection
分配好内存池之后,accept分配连接内存池后会调用HTTP模块中的回调方法ngx_http_init_connection,http模块就会从事件模块接手请求,新建立的读事件会通过epoll_ctl加入epoll中,并添加超时定时器。
client_header_timeout:xxs代表xxs后没有收到请求就超时了,完成这步后,事件模块就切到其他FD去处理了。 -
- 当用户传来真正的GET/POST请求
操作系统会回复ACK。事件模块epoll_wait拿到请求,调用上一步设置好的在HTTP模块中的回调方法ngx_http_wait_request_handler,这个方法要把内核的数据读到用户态中,因为要读所以首先要分配内存。这个内存要分配多大?要从分配连接内存池
connection_pool_size:xxx中分client_header_buffer_size:1k。这里的大小代表,一旦用户有消息过来就会分配该大小如1k。
接收连接请求收到nginx中就行了,但是处理请求还需要分析协议header。
接收请求方法流程
请求上下文一般会将请求内存池设置为4K。
HTTP请求11个阶段
图中11个阶段依次执行。
性能优势
Nginx轻量级,拥有性能优势的原因有
异步非阻塞
CPU绑定
Worker绑定固定内核上,避免频繁切换上下文,提高缓存命中率。
负载均衡
Nginx使用全局互斥锁accept_mutex实现了一套负载均衡算法,避免Worker之间负载的不均衡;另外也避免了所有Worker争抢接收Accept连接,全部被唤醒导致“惊群效应”。