使用过nginx的朋友都知道nginx的性能很高,而其缘由可能少有人知。首先,nginx的架构就奠基了其高性能的基础。那么就先来看看nginx的基础架构吧,以下图所示:(不能彻底理清楚全部内容也不要紧,由于本小节讲述的主要内容是Nginx的进程模型)html
本小节先来讲说Nginx基础架构中的进程模型:linux
所谓进程模型,即Nginx响应请求或服务时程序运行(机器执行指令集)的方式,通常在nginx服务启动后,在Unix系统中会以daemon的方式在后台运行,后台进程包含一个master进程以及多个worker进程。nginx
而在咱们进行调试时,能够手动关闭后台模式以及设置nginx取消master进程,使其以单进程方式运行,可是生产环境中确定不容许这样上线服务,主流的运行方式仍是默认使用多进程。c++
这里插一下关于进程和程序的区别:shell
其实这二者彻底不是一个概念,程序只是一堆等待执行的代码和部分待处理的数据,只有被加到内存中,由CPU处理执行代码,才能够发挥其做用,从而造成一个真正的“活的”动态的进程。因此两者的典型区别不言而喻,程序未被执行时是静态的,只有运行时才变成动态的进程,而且进程善始善终(进程的“生老病死”)。编程
言归正传,仍是回来讲nginx的多进程模式。nginx启动后,会有如上图所示的一个master进程和多个worker进程。vim
master进程:负责管理worker进程,接收、发送信号,监控worker进程状态;操做控制Nginx只须要经过与master进程通讯就能够了。服务器
worker进程:负责处理基本网络事件,每一个worker进程都是平等且独立的(记住独立两个字)。通常worker进程数会设置为机器(服务器)的CPU核心数(缘由下文会讲到)。网络
咱们从nginx启动时的工做流程以及启动后所响应外部的操做来理解nginx处理一个链接的工做原理及过程。数据结构
Nginx启动时,会先解析配置文件,获取须要监听的ip地址与端口号(涉及网络编程以及TCP/IP理论),而后在master进程中,首先初始化好这个监控的socket,而后fork多个子进程,子进程竞争(例如互斥锁机制)accept新的链接。此时,Nginx以及启动好,等待客户端来链接本身。
fork——属于系统编程内容,原意为叉子,可能老外使用的是叉子,而决定使用这个单词来比喻fork的做用,fork函数的功能就是建立(有时候理解为派生)出多个子进程
客户机发起链接请求,经过TCP三次握手与Nginx创建一个链接,此时竞争成功的一个worker进程获取到这个创建好链接的socket,开始建立nginx对链接的封装(其实说白了就是结构体封装)。随之执行读写事件(本文所述的事件理解为网络事件便可)函数、添加读写事件来与客户端进行数据交互。最后,其中一方主动断开链接(四次挥手),到此,一个链接也就寿终正寝了(该worker进程被宣告退休)。
当咱们执行命令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进程是由master进程fork出来的,而且每一个worker进程都是对等的,那么当一个请求过来时,任何一个worker进程都有可能处理它,这个时候怎么办呢?
为了保证只有一个进程处理该链接请求,全部的worker进程会进行竞争,触发锁机制,通常是互斥锁,抢到锁的进程得到权利来处理这个请求(读事件开始调用accept接受该链接),进行读取、解析、处理请求,将结果(数据信息)返回给客户端,最后断开链接,这个请求完整走完它的人生。所以,一个请求彻底是由也仅仅由一个worker进程处理。
这个问题其实不是特别容易说,没有进程线程的理论基础可能也没法理解原理,这边给出其具有的”独立“特征所带来的优点吧。
”独立“的进程,意味着不须要加锁,节省开销,而且多个worker进程之间互不影响,某一个出现bug后,会有新的worker进程开始工做,从而下降风险,也不会中断其余服务,同时也简化了编程和检查问题。
那么,问题又来了,使用多进程模型,每一个worker进程中也只有一个主线程,如何能够处理高并发呢?
且看下节。
举个例子:阻塞就比如下课你去食堂吃饭,但去的时候人太多了,你就傻傻地在原地排队等。
非阻塞就比如是你去食堂吃饭,人依旧不少,可是你能够先去上个洗手间,看会儿资讯,不影响你干这些事情。充分利用时间,这个时间就比如是CPU的使用率,非阻塞的存在能够避免浪费CPU资源。
上面说的非阻塞,在nginx应用时,虽然不阻塞了,但你得不时地过来检查一下事件的状态,你能够作更多的事情了,但带来的开销也是不小的。因此,才会采起异步方式。
举个例子:同步就是你有不懂的问题问同事,他给你开始讲解解决思路或方案,你一直在主动听取他的内容,异步就是一样你问同事问题,他可能说我先考虑考虑,想出来了主动来告诉你。
异步方式+非阻塞处理请求能够避免浪费CPU资源,同时提升响应速度,工做效率。其实本质上就是说worker进程,在循环执行异步请求(事件),从而处理高并发。
所以,设置worker的个数为cpu的核数,在这里就很容易理解了,更多的worker数,只会致使进程来竞争cpu资源了,从而带来没必要要的资源浪费。
结尾给出Nginx源码目录介绍吧。
解压nginx软件,进入其目录就能够看到它的目录结构以下:
[root@localhost 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