透过现象看本质 -- 回头再看 Nginx
Nginx 的进程模型
? 使用过 nginx 的朋友都知道 nginx 的性能很高, 而其原因可能少有人知. 首先, nginx 的架构就奠定了其高性能的基础. 那么就先来看看 nginx 的基础架构吧, 如下图所示:(不能完全理清楚所有内容也没关系, 因为本小节讲述的主要内容是 Nginx 的进程模型)
? 本小节先来说说 Nginx 基础架构中的进程模型:
? 所谓进程模型, 即 Nginx 响应请求或服务时程序运行 (机器执行指令集) 的方式, 一般在 nginx 服务启动后, 在 Unix 系统中会以 daemon 的方式在后台运行, 后台进程包含一个 master 进程以及多个 worker 进程.
? 而在我们进行调试时, 可以手动关闭后台模式以及设置 nginx 取消 master 进程, 使其以单进程方式运行, 但是生产环境中肯定不允许这样上线服务, 主流的运行方式还是默认使用多进程.
这里插一下关于进程和程序的区别:
其实这两者完全不是一个概念, 程序只是一堆等待执行的代码和部分待处理的数据, 只有被加到内存中, 由 CPU 处理执行代码, 才可以发挥其作用, 从而形成一个真正的 "活的" 动态的进程. 所以二者的典型区别不言而喻, 程序未被执行时是静态的, 只有运行时才变成动态的进程, 并且进程有始有终(进程的 "生老病死").
? 言归正传, 还是回来说 nginx 的多进程模式. nginx 启动后, 会有如上图所示的一个 master 进程和多个 worker 进程.
? master 进程: 负责管理 worker 进程, 接收, 发送信号, 监控 worker 进程状态; 操作控制 Nginx 只需要通过与 master 进程通信就可以了.
? worker 进程: 负责处理基本网络事件, 每个 worker 进程都是平等且独立的 (记住独立两个字). 一般 worker 进程数会设置为机器(服务器) 的 CPU 核心数(原因下文会讲到).
Nginx 响应连接的工作原理
? 我们从 nginx 启动时的工作流程以及启动后所响应外部的操作来理解 nginx 处理一个连接的工作原理及过程.
启动时:
? Nginx 启动时, 会先解析配置文件, 获取需要监听的 ip 地址与端口号(涉及网络编程以及 TCP/IP 理论), 然后在 master 进程中, 首先初始化好这个监控的 socket, 然后 fork 多个子进程, 子进程竞争(例如互斥锁机制)accept 新的连接. 此时, Nginx 以及启动好, 等待客户端来连接自己.
? fork-- 属于系统编程内容, 原意为叉子, 可能老外使用的是叉子, 而决定使用这个单词来比喻 fork 的作用, fork 函数的功能就是创建 (有时候理解为派生) 出多个子进程
启动后:
? 客户机发起连接请求, 通过 TCP 三次握手与 Nginx 建立一个连接, 此时竞争成功的一个 worker 进程获取到这个建立好连接的 socket, 开始创建 nginx 对连接的封装 (其实说白了就是结构体封装). 随之执行读写事件(本文所述的事件理解为网络事件即可) 函数, 添加读写事件来与客户端进行数据交互. 最后, 其中一方主动断开连接(四次挥手), 到此, 一个连接也就寿终正寝了(该 worker 进程被宣告退休).
Nginx 重启流程
? 当我们执行命令 kill -HUP pid 时, master 进程是如何响应的呢?
? 首先, master 接收到 kill(不要简单理解为杀死, 很多人都保持着这样的理解, 但是这样不准确, 在 Linux 系统中, kill 表示的是用于向进程发送信号的, 可以使用 kill -l 查看可以携带的信号)的信号时, 首先会重载配置文件, 然后启动新的 worker 进程, 并且向旧的 worker 进程发送信号, 提示他们做完当前事件之后就可以光荣退休了. 新的 worker 进程启动后, 开始接收新的请求. 这种方式就是直接给 mater 进程发送信号.
? 而在新的版本中, 可以使用其他方式例如:./nginx -s reload 就是重启服务,./nginx -s stop 就是停止服务, 执行./nginx -s reload 时会启动一个新的进程(nginx), 该进程解析到 reload 的时候, 就控制 nginx 重载配置文件, 向 master 进程发送信号了, 随后就和上面旧版本的方式一样的过程了.
以上就是 nginx 内部工作 (内部究竟干了啥活) 了, 但是此时考虑一个问题: worker 进程如何处理请求的呢?
worker 进程工作方式
? 既然 worker 进程是由 master 进程 fork 出来的, 并且每个 worker 进程都是对等的, 那么当一个请求过来时, 任何一个 worker 进程都有可能处理它, 这个时候怎么办呢?
? 为了保证只有一个进程处理该连接请求, 所有的 worker 进程会进行竞争, 触发锁机制, 一般是互斥锁, 抢到锁的进程获得权利来处理这个请求 (读事件开始调用 accept 接受该连接), 进行读取, 解析, 处理请求, 将结果(数据信息) 返回给客户端, 最后断开连接, 这个请求完整走完它的人生. 因此, 一个请求完全是由也仅仅由一个 worker 进程处理.
Nginx 为什么选择多进程模型?
? 这个问题其实不是特别容易说, 没有进程线程的理论基础可能也无法理解原理, 这边给出其具备的 "独立" 特征所带来的优势吧.
? "独立" 的进程, 意味着不需要加锁, 节省开销, 并且多个 worker 进程之间互不影响, 某一个出现 bug 后, 会有新的 worker 进程开始工作, 从而降低风险, 也不会中断其他服务, 同时也简化了编程和检查问题.
? 那么, 问题又来了, 使用多进程模型, 每个 worker 进程中也只有一个主线程, 如何可以处理高并发呢?
且看下节.
Nginx 异步非阻塞的处理请求方式(简单说说)
简单理解阻塞与非阻塞
阻塞就是线程在执行 IO 操作获取数据时, 这个 IO 可能会需要一定的时间才能等到数据返回, 然后才能接着执行下面的命令. 那么, 此时, 这个线程的等待状态 (一般在 nginx 中称作内核等待, 而 nginx 中最忌讳阻塞的系统调用) 我们就把它称为阻塞. 没有充分利用起 CPU 的资源.
非阻塞还是这个线程在进行 IO 操作时, 无需等待数据的返回, 可以接着往下执行代码命令, 会返回一个结果给你, 你可以使用 CPU 资源做其他的事情.
举个例子: 阻塞就好比下课你去食堂吃饭, 但去的时候人太多了, 你就傻傻地在原地排队等.
非阻塞就好比是你去食堂吃饭, 人依旧很多, 但是你可以先去上个洗手间, 看会儿资讯, 不影响你干这些事情. 充分利用时间, 这个时间就好比是 CPU 的使用率, 非阻塞的存在可以避免浪费 CPU 资源.
为什么需要异步方式?
上面说的非阻塞, 在 nginx 应用时, 虽然不阻塞了, 但你得不时地过来检查一下事件的状态, 你可以做更多的事情了, 但带来的开销也是不小的. 所以, 才会采取异步方式.
同步: 同步指的当线程进行 IO 操作请求数据时, 是你主动 "关心" 数据的返回.
异步: 当前线程无需主动关心数据是否返回, 当数据返回时, 会有相关的事件通知你.
举个例子: 同步就是你有不懂的问题问同事, 他给你开始讲解解决思路或方案, 你一直在主动听取他的内容, 异步就是同样你问同事问题, 他可能说我先考虑考虑, 想出来了主动来告诉你.
异步方式 + 非阻塞处理请求可以避免浪费 CPU 资源, 同时提高响应速度, 工作效率. 其实本质上就是说 worker 进程, 在循环执行异步请求(事件), 从而处理高并发.
因此, 设置 worker 的个数为 CPU 的核数, 在这里就很容易理解了, 更多的 worker 数, 只会导致进程来竞争 CPU 资源了, 从而带来不必要的资源浪费.
结尾给出 Nginx 源码目录介绍吧.
Nginx 代码的目录结构
解压 nginx 软件, 进入其目录就可以看到它的目录结构如下:
- [[email protected] nginx-1.16.1]# tree -d
- .
├── auto #自动编译安装相关目录
│?? ├── cc #针对各种编译器进行相应的编译配置目录, 包括 gcc,ccc 等
│?? ├── lib #程序依赖的各种库, 包括 openssl,pcre ,perl 等
│?? │?? ├── geoip
│?? │?? ├── google-perftools
│?? │?? ├── libatomic
│?? │?? ├── libgd
│?? │?? ├── libxslt
│?? │?? ├── openssl
│?? │?? ├── pcre
│?? │?? ├── perl
│?? │?? └── zlib
│?? ├── os #针对不同的操作系统所做的编译配置目录
│?? └── types #与数据类型相关的一些辅助脚本
├── conf #存放默认配置文件, 在 make install 后, 会拷贝到安装目录中去
├── contrib #存放一些实用工具, 如 geo 配置生成工具(geo2nginx.pl
│?? ├── unicode2nginx #
│?? └── VIM #
│?? ├── ftdetect #
│?? ├── ftplugin #
│?? ├── indent #
│?? └── syntax #
├── html #存放默认的网页文件, 在 make install 后, 会拷贝到安装目录中去
├── man #手册
└── src #存放 nginx 的源代码
├── core #nginx 的核心源代码, 包括常用数据结构的定义, 以及 nginx 初始化运行的核心代码如 main 函数
├── event #对系统事件处理机制的封装, 以及定时器的实现相关代码
│?? └── modules #不同事件处理方式的模块化, 如 select,poll,epoll,kqueue 等
├── http #nginx 作为 http 服务器相关的代码
│?? ├── modules #包含 http 的各种功能模块
│?? │?? └── perl #
│?? └── v2 #
├── mail #nginx 作为邮件代理服务器相关的代码
├── misc #一些辅助代码, 测试 c++ 头的兼容性, 以及对 google_perftools 的支持
├── os #主要是对各种不同体系统结构所提供的系统函数的封装, 对外提供统一的系统调用接口
│?? └── unix #
└── stream #实现四层协议的转发, 代理或者负载均衡等第三方模块
对于使用者而言最关键的是 conf 目录, HTML 目录, 对于开发者而言可能需要看其源码文件: 目录为 src, 这就涉及到 nginx 的核心部分, 包括模块, 模块对应的功能等.
参考链接: tengine.taobao.org
来源: http://www.bubuko.com/infodetail-3483789.html