解析初始化配置文件后会 创建(fork)一个master进程 之后 这个进程会退出 master 进程会 变为孤儿进程 由init进程托管。(可以通过python 或php 启动后创建子进程,然后杀去世父进程得见子进程会由init进程托管)如下图可以看到nginx master 进程由init(ppid 为1 )进程管理。
1、master 首先nginx 创建一个master 进程,通过socket() 创建一个sock文件描述符用来监听(sockfd)绑定端口(bind) 开启监听(listen)。nginx 一样平常监听80(http) 或 443 (https)端口(fork 多个子进程后,master 会监听worker进程,和等待旗子暗记)2、worker然后 创建(fork)多个 worker子进程(复制master 进程的数据),此时所有的worker进程 继续了sockfd(socket文件描述符),当有连接进来之后 worker进程就可以accpet()创建已连接描述符,然后通过已连接描述符与客户端通讯
惊群征象
由于worker进程 继续了master进程的sockfd,当连接进来是,所有的子进程都将收到关照并“争着”与它建立连接,这就叫惊群征象。大量的进程被激活又挂起,末了只有一个进程accpet() 到这个连接,这会花费系统资源(等待关照,进程被内核全部唤醒,只有一个进程accept成功,其他进程又休眠。这种摧残浪费蹂躏征象叫惊群)
nginx 对惊群征象的处理
缘故原由: 多个进程监听同一个端口引发的。办理: 如果可以同一时候只能有一个进程监听端口,这样就不会发生“惊群”了,此时新连接事宜只能唤醒正在监听的唯一进程。 如何保持一个时候只能有一个worker进程监听端口呢?nginx设置了一个accept_mutex锁,在利用accept_mutex锁是, 只有进程成功调用了ngx_trylock_accept_mutex方法获取锁后才可以监听端口(linux 内核2.6 之后 不会涌现惊群征象,只会有一个进程被唤醒)
代码大略理解
C/C++Linux做事器开拓精彩内容包括:C/C++,Linux,Nginx,ZeroMQ,MySQL,Redis,MongoDB,ZK,流媒体,P2P,Linux内核,Docker,TCP/IP,协程,DPDK多个高等知识点分享,视频获取+qun:正在跳转

三、worker进程worker进程做了什么事
从上图中,我们可以看到worker进程做了1、accept() 与客户端建立连接2、recv()吸收客户端发过来的数据3、send() 向客户端发送数据4、close() 关闭客户端连接
如果不该用io多路复用 会是什么样的
首先 等待客户端有连接进来accpet() 与客户端建立连接后recv() 一贯等待客户的发送过来数据(此时处于io壅塞状态)如果此时又有客户端过来建立连接,那么只能等待,须要一贯等待close() 之后才可以建立连接也便是说这个worker进程会由于recv() 而处于壅塞状态,而不能处理与其他客户端建立连接,这段韶光不能做任何事,这是对性能了摧残浪费蹂躏。(进程 和线程的切换也是须要花费 韶光的。)
能不能利用io堵塞的韶光 accept,recv
nginx 采取了io多路复用技能实现了
四、io多路复用什么是io复用
IO复用办理的便是并发行的问题,比如多个用户并发访问一个WEB网站,对付做事端后台而言就会产生多个要求,处理多个要求对付中间件就会产生多个IO流对付系统的读写。那么对付IO流要求操作系统内核有并行处理和串行处理的观点,串行处理的办法是一个个处理,前面的发生壅塞,就没办法完成后面的要求。这个时候我们必须考虑并行的办法完玉成部IO流的要求来实现最大的并发和吞吐,这时候便是用到IO复用技能。IO复用便是让一个Socket来作为复用完玉成部IO流的要求。当然实现全体IO流的要求多线程的办法便是个中一种。(一个socket作为复用来完玉成部io流的要求连接建立(accept),而处理要求(recv,send,close)则采取多线程)
io复用之多线程处理
# -- coding:utf-8 --import socketfrom threading import Threaddef comm(conn): data = conn.recv(1024) conn.send(data) conn.close()obj = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # sockfd (socket 文件描述符,这个描述符是用来监听的--监听socket)obj.bind(('127.0.0.1',8082)) # 绑定地址obj.listen(5) # 开启监听while True: conn,addr = obj.accept() // 每建立一个 连接 就交由线程处理 t = Thread(target=comm,args=(conn,)) // 创建线程工具 t.start() // 启动 // 这样就可以处理多个io要求,不会由于一个没处理完而导致的堵塞
io 多路复用
多个描述符(监听描述符,已连接描述符) 的io 操作都能在一个线程内并发交替顺序完成,这就叫io多路复用,这里的复用指的是复用同一个线程
io 多路复用的三种机制 select poll epollselect
#include<sys/select.h>intselect(int nfds, fd_set readfds, fd_set writefds, fd_set exceptfds, struct timeval timeout); //参数nfds是须要监听的最大的文件描述符值+1//rdset,wrset,exset分别对应须要检测的可读文件描述符的凑集,//可写文件描述符的凑集集非常文件描述符的凑集//参数timoout为构造timeval,用来设置select()的等待韶光
1、用户将自己所关心的文件描述符添加进描述符集中,并且明确关心的是读,写,还是非常事宜
2、select 通过轮询的办法不断扫码所有被关心的文件描述符,详细韶光由参数timeout决定的
3、实行成功则返回文件描述符已改变的个数
4、详细哪一个或哪几个文件描述符就绪,则须要文件描述符集传出,它既是输入型参数,又是输出型参数
5、fd_set 是用位图存储文件描述符的,由于文件描述符是唯一且递增的整数
特点:
1、可关心的文件描述符数量是有上限的,取决于fd_set(文件描述符集)的大小
2、每次的调用select 前,都要把文件描述符重新添加进fd_set(文件描述符集)中,由于fd_set也是输出型参数 在函数返回后,fd_set中只有就绪的文件描述符
3、常日我们要关心的文件描述符不止一个,所有首先用数组保存文件描述符,每次调用select前再通过遍历数逐个添加进去
缺陷:
1、每次调用select都须要手动设置fd_Set
2、每次调用select 须要遍历fd_set 凑集,而且要将fd_set 凑集从用户态拷贝到内核态,如何fd很多时,开销会很大
3、select 支持的文件描述符数量太少 32- 1024 64 -2048
poll
#include <sys/poll.h> int poll(struct pollfd fds, nfds_t nfds, int timeout); // 第一个参数是指向一个构造数组的第一个元素的指针 // 第二个参数是要监听的文件描述符的个数 // 第三个参数意义与select相同 //pollfd构造 struct pollfd{ int fd; short events; short revents; }; //events 是我们要关心的事宜,revents是调用后操作系统设置的参数, //也便是表明该文件描述符是否就绪首先创建一个pollfd构造体变量数组fd_list,然后将我们然后将我们关心的fd(文件描述符)放置在数组中的构造变量中, 并添加我们所关系的事宜,调用poll函数,函数返回后我们再通过遍历的办法去查看数组中那些文件描述符上的事宜就绪了。
特点(相对付select)
1、每次调用poll之前不须要手动设置文件描述符集
2、poll将用户关系的实际和发生的实际进程分离
3、支持的文件描述符数量理论上是无上限的,实在也有, 由于一个进程能打开的文件数量是有上限的 ulimit -n 查看进程可打开的最大文件数
1、poll 返回后,也须要轮询pollfd 来获取就绪的描述符 2、同时连接的大量客户端,可能只有很少的处于就绪状态,因此随着监事的描述符数量的增长,其效率也会线性低落
select poll 的共同点都做了很多 无效的 轮询检测描述符是否就绪的操作
epoll代码
#include <sys/epoll.h>int epoll_create(int size); // 在内核里,统统皆文件。以是,epoll向内核注册了一个文件系统,//epoll_create的浸染是创建一个epoll模型,该模型在底层建立了->//红黑树,就绪行列步队,回调机制 //size可以被忽略,不做阐明int epoll_ctl(int epfd, int op, int fd, struct epoll_events event); //epfd:epoll_create()的返回值(epoll的句柄,实质上也是一个文件描述符) //op:表示动作,用三个宏来表示// EPOLL_CTL_ADD:注册新的fd到epfd中 // EPOLL_CTL_MOD:修正已经注册的fd的监听事宜 // EPOLL_CTL_DEL:从epfd中删除一个事宜 //fd:须要监听的文件描述符 //event:详细须要在该文件描述符上监听的事宜 int epoll_wait(int epfd, struct epoll_event events, int maxevents, int timeout); //函数调用成功,返回文件描述符就绪的个数,也便是就绪行列步队中文件描述符的个数, //返回0表示超时,小于0表示出错 //epoll_event构造体 struct epoll_event{ uint32_t events; / Epoll events / epoll_data_t data;/ User data variable / }__EPOLL_PACKED; //events可以是一堆宏的凑集,这里先容几个常用的 // EPOLLIN:表示对应的文件描述符可以读(包括对端socket正常关闭) // EPOLLOUT:表示对应的文件描述符可以写 // EPOLLET:将EPOLL设为边缘触发(Edge Triggered)模式, // 默认情形下epoll为水平触发(Level Triggered)模式 typedef union epoll_data{ void ptr; int fd; uint32_t u32; uint64_t u64; }epoll_data_t; //联合体里常日只须要添补fd就OK了,其他参数暂时可以不予理会
事情事理1、创建一个epoll 工具,向epoll 工具中添加文件描述符以及我们所关心的在在该文件描述符上发生的事宜
2、通过epoll_ctl 向我们须要关心的文件描述符中注册事宜(读,写,非常等), 操作系统将该事宜和工具的文件描述符作为一个节点插入到底层建立的红黑树中
3、添加到文件描述符上的实际都会与网卡建立回调机制,也便是实际发生时会自主调用一个回调方法, 将事宜所在的文件描述符插入到就绪行列步队中
4、引用程序调用epoll_wait 就可以直接从就绪行列步队中将所有就绪的文件描述符拿到,可以说韶光繁芜度O(1)
水平触发事情办法(LT)处理socket时,纵然一次没将数据读完,下次调用epoll_wait时该文件描述符也会就绪,可以连续读取数据
边沿触发事情办法(ET)处理socket时没有一次将数据读完,那么下次再调用epoll_wait该文件描述符将不再显示就绪,除非有新数据写入
在该事情办法,当一个文件描述符就绪时,我们要一次性的将数据读完
隐患问题当我们调用read读取缓冲去数据时,如果已经读取完了,对端没有关系写段,read就会堵塞,影响后续逻辑
办理办法便是将文件描述符,设置成非堵塞的,当没有数据的时候,read也不会被堵塞, 可以处理后续逻辑(读取其他的fd或者连续wait)
ET 的性能要好与LT,由于epoll_wait返回的次数比较少,ninx中默认采取ET模式利用epoll
特点1、采取了回调机制,与轮询差异看待
2、底层采取红黑树构造管理已经注册的文件描述符
3、采取就绪行列步队保存已经就绪的文件描述符
优点1、文件描述符数目无上限:通过epoll_ctl 注册一个文件描述符后,底层采取红黑树构造管理所有须要监控的文件描述符
2、基于实际的就绪关照办法:每当有文件描述符就绪时,该相应事宜会调用回调方法将该文件描述符插入到就绪行列步队中, 不须要内核每次去轮询式的查看每个被关心的文件描述符
3、掩护就绪行列步队:当文件描述符就绪的时候,就会被放到内核中的一个就绪行列步队中, 调用epoll_wait可以直接从就绪行列步队中获取就绪的文件描述符,韶光繁芜度是O(1)
四、总结
nginx 通过 多进程 + io多路复用(epoll) 实现了高并发采取多个worker 进程实现对 多cpu 的利用通过eopll 对 多个文件描述符 事宜回调机制和就绪描述符的处理 实现单线程io复用从而实现高并发