一,简述
Nginx 是俄罗斯人 Igor Sysoev 编写的轻量级 Web 服务器,它不仅是一个高性能的 HTTP 和反向代理服务器,同时也是一个 IMAP/POP3/SMTP 代理服务器。
Nginx 以事件驱动的方式编写,所以有非常好的性能,同时也是一个非常高效的反向代理、负载平衡服务器。在性能上,Nginx 占用很少的系统资源,能支持更多的并发连接,达到更高的访问效率;
在安装配置上,Nginx 安装简单、配置灵活,同时支持热部署,启动速度特别快,还可以在不间断服务的情况下对软件版本或配置进行升级,即使运行数月也无需重新启动。
二,nginx 的多进程模型
nginx 启动后以 daemon 多进程⽅式在后台运⾏,有⼀个 Master 进程和多个 Worker 进程。进程模型的处理方式带来的一些好处就是:进程之间是独立的。也就是一个worker进程出现异常退出,其他worker进程是不会受到影响的;此外,独立进程也会避免一些不需要的锁操作,这样子会提高处理效率,并且开发调试也更容易。多进程模型图如下:
Master 进程:
- 接收外部信号,转发到 Worker;
- 监控 Worker 进程的运行状态,Worker 进程异常终止后,自动重启 Worker 进程。
Worker 进程:
处理网络请求。所有 Worker 进程都是平等的,Worker 数量可以在 nginx.conf 中配置,一般设置为核心数,充分利用 CPU 资源,同时,避免进程数量过多竞争 CPU 资源,增加上下文切换的损耗。
请求是连接到 Nginx,Master 进程负责处理和转发?
如何选定哪个 Worker 进程处理请求?请求的处理结果,是否还要经过 Master 进程?
2.1,Master 工作流程
Master 接收来自外界的信号,先建立好需要 listen 的 socket(listenfd) 之后,然后再 fork 出多个 Worker 进程,然后向各 Worker 进程发送信号,每个进程都有可能来处理这个连接。
2.2,Worker 工作流程
- 新请求到来时,所有 Worker 进程的 listenfd 都变为可读;
- Worker 进程竞争 accept_mutex 锁,获胜的注册 listenfd 的读事件;
- Worker 进程在读事件中,accept 当前连接;
- Worker 进程处理请求,进行相应,最后断开连接;
nginx 中实现了一个 accept_mutex 类似互斥锁,只有获取到这个锁的进程,才可以去注册读事件。
2.3,热部署原理
通常我们用 nginx -s reload
命令重启 nginx,使用这个命令,可以不停止服务始终在处理新的请求的同时把 nginx 的配置文件平滑的把旧的 nginx.conf 配置更新为新的 nginx.conf 配置。
执行这命令过程中 worker 子进程的数量会变多了,这是因为旧配置运行的 worker 进程还没处理完成请求,处理完成后会自动退出。
执行流程:
- 向 master 进程发送 HUP 信号(reload命令);
- master 进程校验配置文件语法是否正确;
- master 进程打开新的监听端口;
- master 进程用新配置启动新的 worker 子进程;
- master 进程向旧 worker 子进程发送退出信号;
- 旧 worker 进程关闭监听句柄,处理完当前连接后结束进程;
三,多路复用
Nginx 拥有高性能并且能够支撑高并发的原因主要是它的多进程模型 + 异步非阻塞方式(IO 多路复用 Epoll)
- 大并发量:只使用一个线程,处理大量的并发请求,降低上下文环境切换损耗,也不需要考虑并发问题,相对可以处理更多的请求。
- 消耗更少的系统资源(不需要线程调度开销)。
- 适用于长连接的情况(多线程模式长连接容易造成线程过多,造成频繁调度)。
下面介绍下相关概念:
3.1,阻塞与非阻塞
阻塞与非阻塞的重点在于进/线程等待消息时候的行为,也就是在等待消息的时候,当前进/线程是挂起状态,还是非挂起状态。
- 阻塞调用在发出去后,在消息返回之前,当前进/线程会被挂起,直到有消息返回,当前进/线程才会被激活 ;
- 非阻塞调用在发出去后,不会阻塞当前进/线程,而会立即返回。
3.2,什么是IO多路复用
单线程或单进程同时监测若干个文件描述符是否可以执行IO操作的能力。一个线程,跟踪多个 Socket 状态,哪个就绪,就读写哪个。
3.3,解决了什么问题
应用程序通常需要处理多条事件流中的事件,而CPU单核在同一时刻只能做一件事情,一种解决办法是对CPU进行时分复用(多个事件流将CPU切割成多个时间片,不同事件流的时间片交替进行)。在计算机系统中,我们用线程或者进程来表示一条执行流,通过不同的线程或进程在操作系统内部的调度,来做到对CPU处理的时分复用。这样多个事件流就可以并发进行,不需要一个等待另一个太久,在用户看起来他们似乎就是并行在做一样。
但凡事都是有成本的。线程/进程也一样,有这么几个方面:
- 线程/进程创建成本;
- CPU切换不同线程/进程成本;
- 多线程的资源竞争;
有没有一种可以在单线程/进程中处理多个事件流的方法呢?一种答案就是 IO多路复用。因此IO多路复用解决的本质问题是在用更少的资源完成更多的事。
3.4,IO多路复方案
IO多路复用在Linux下包括了三种,select、poll、epoll,抽象来看,他们功能是类似的,但具体细节各有不同:首先都会对一组文件描述符进行相关事件的注册,然后阻塞等待某些事件的发生或等待超时。
3.4.1,select
- 查询 fd_set 中,是否有就绪的 fd,可以设定一个超时时间,当有 fd (File descripter) 就绪或超时返回。
- fd_set 是一个位集合,大小是在编译内核时的常量,默认大小为 1024。
- 特点:连接数限制,fd_set 可表示的 fd 数量太小了;线性扫描:判断 fd 是否就绪,需要遍历一边 fd_set;数据复制:用户空间和内核空间,复制连接就绪状态信息。
3.4.2,poll
- 解决了连接数限制:poll 中将 select 中的 fd_set 替换成了一个 pollfd 数组,解决 fd 数量过小的问题。
- 数据复制:用户空间和内核空间,复制连接就绪状态信息。
3.4.3,epoll,event 事件驱动
- 事件机制:避免线性扫描,为每个 fd,注册一个监听事件,fd 变更为就绪时,将 fd 添加到就绪链表。
- fd 数量:无限制(OS 级别的限制,单个进程能打开多少个 fd)。
四,模块化体系结构
Nginx 的内部结构是由核心部分和一系列的功能模块所组成。这样划分是为了使得每个模块的功能相对简单,便于开发,同时也便于对系统进行功能扩展。Nginx 将各功能模块组织成一条链,当有请求到达的时候,请求依次经过这条链上的部分或者全部模块,进行处理,每个模块实现特定的功能。
Nginx 的模块根据其功能基本上可以分为以下几种类型:
- event module: 搭建了独立于操作系统的事件处理机制的框架,及提供了各具体事件的处理。包括 ngx_events_module, ngx_event_core_module和ngx_epoll_module 等。Nginx 具体使用何种事件处理模块,这依赖于具体的操作系统和编译选项。
- phase handler: 此类型的模块也被直接称为 handler 模块。主要负责处理客户端请求并产生待响应内容,比如 ngx_http_static_module 模块,负责客户端的静态页面请求处理并将对应的磁盘文件准备为响应内容输出。
- output filter: 也称为 filter 模块,主要是负责对输出的内容进行处理,可以对输出进行修改。例如,可以实现对输出的所有 html 页面增加预定义的 footbar 一类的工作,或者对输出的图片的 URL 进行替换之类的工作。
- upstream: upstream 模块实现反向代理的功能,将真正的请求转发到后端服务器上,并从后端服务器上读取响应,发回客户端。upstream 模块是一种特殊的 handler,只不过响应内容不是真正由自己产生的,而是从后端服务器上读取的。
- load-balancer: 负载均衡模块,实现特定的算法,在众多的后端服务器中,选择一个服务器出来作为某个请求的转发服务器。
评论区