Nginx 基础架构解析(上)
1. 进程模型
前面介绍 时有介绍过 的进程模型。 启动时首先启动 Master 进程,然后由 Master 进程启动或者多个 Worker 子进程。Master 进程主要完成配置读取,通过发送信号控制 Worker 进程的启动和停止等,而 Worker 子进程是用来处理客户端发来的 Http 请求,且Worker进程之间会通过共享内存进行通信。
假设 启动了多个 Worker 进程,并且在 Master 进程中通过 socket 字监听80端口。
这些 Worker 进程 fork 自 Master 进程,然后每个worker进程都可以去 accept 这个监听的 socket。 当连接进来后,所有在 accept 个 socket 上面的进程,都会收到消息,但是只有进程可以 accept 这个连接,其它的则 accept 失败,这便是所谓的惊群现象。 处理这种情况的方式就是加锁。有了锁之后,在同一时刻,就只会有 Worker 进程在 accpet 连接,这样就不会有惊群问题了。在 Worker 进程拿到 Http 请求后,就开始按照前面介绍的 11个阶段处理该 Http 请求,最后返回响应结果并断开连接。
最早我们学习了 命令行操作,这些命令行操作都是给 Master 进程发信号,然后再由 Master 进程发送信号给 Worker 进程,从而达到控制 Worker 进程的目标。我们以 的热部署命令./ -s reload
来描述 命令行的执行流程。具体过程如下:
首先 Master 进程会检查 .conf 是否存在语法,并从中找到 .pid 配置路径(没有配置会使用认值)
reload 参数表示向 Master 进程发送 HUP 信号。 会根据会保存在 .pid 中的值找到 Master 进程的 pid。如果 进程没有启动,则没有该 .pid ,命令行会报错;
# 在 的中配置 .pid 的保存路径[root@server sbin]# ./ -s reload: [error] "/root//logs/.pid" (: No such file or directory)
Master 进程打开新的监听端口;
Master 进程用新配置启动新的 Worker 进程。新的 Worker 进程起来后,开始接收 Http 请求并处理,此时老的 Worker进程会停止接受 Http 请求;
Master 进程会向老的 Worker进程发送 QUIT 信号;
老的 Worker 进程监听句柄,处理完的请求后结束进程。
命令行中 -s 参数的每个值都对应这信号。因此,我们也可以直接对 Master 进程发生相应信号达到同样的目的。
# pid表示的主进程id号# kill -s 信号 pid
2. 的事件驱动模型
事件驱动模型是实现异步非阻塞的手段。对于 web 服务器来说,客户端 A 的请求连接到服务端时,服务端的某个进程( 的 worker 进程)会处理该请求,此进程在没有返回给客户端 A 结果时,它又去处理了客户端 B 的请求。服务端把客户端 A 以及客户端 B 发来的请求作为事件交给了"事件收集器",而"事件收集器"再把收集到的事件交由"事件发送器"发送给"事件处理器"进行处理。最后"事件处理器"处理完该事件后,服务端进程,服务端进程再把结果返回给客户端A、客户端B。个过程中,服务端进程做的事情属于级别的,而事件处理这部分工作属于内核级别的。也就是说这个事件驱动模型是需要操作系统内核来作为支撑的。
的事件驱动模型, select、poll、epoll、rtsig、kqueue、/dev/poll、eventport 等。
最常用的是前三种,特别是 epoll 模型,这也是 中的认配置。可以说 epoll 模型成就了 的高和高并发特性。
select模型:
创建所关注事件的描述符集合,对于描述符,可以关注其上面的读(Read)事件、写(Write)事件以及异常发生(Exception)事件。
在select模型中,要创建这3类事件描述符集合。
底层提供的select(),等待事件发生。
轮询所有事件描述符集合中的每事件描述符,检查是否有相应的事件发生,如果有就进行处理。
poll模型:
poll模型是Linux平台上的事件驱动模型,在Linux2.1.23中引入的,Windows平台该模型。
poll模型和select模型工作方式基本相同,区别在于,select模型创建了3个描述符集合,而poll模型只创建描述符集合。
epoll模型:
epoll模型属于poll模型的变种,在Linux2.5.44中引入。epoll比poll更加高效,原因在于它不需要轮询整个描述符集合,而是Linux内核会关注事件集合,当有变动时,内核会发来。
正式这种异步,非阻塞、IO多路复用的事件驱动模型,才使得 有很高的运行效率。
3. 案例
按照前面的讲解,我们测试给 的 Master 进程直接发送信号,并观察进程情况:
# 确认没有 进程 [root@server sbin]# ps -ef | grep root 10603 10137 0 14:23 pts/2 00:00:00 grep --color=auto # 启动 [root@server sbin]# ./ # 可以看到 启动的进程 [root@server sbin]# ps -ef | grep root 10640 1 0 14:23 ? 00:00:00 : master process ./ root 10642 10640 0 14:23 ? 00:00:00 : worker process root 10643 10640 0 14:23 ? 00:00:00 : worker process root 10644 10640 0 14:23 ? 00:00:00 : cache manager process root 10645 10640 0 14:23 ? 00:00:00 : cache loader process
可以看到 启动了 Master 进程(pid=10640),后由 Master 进程 fork 除了两个 Worker 进程和两个 Cache 进程,他们的父进程 id 均为10640。现在做如下几个操作:
进程号等于10642的 Worker 进程
[root@server sbin]# sudo kill -SIGTERM 10642 [root@server sbin]# ps -ef | grep root 10640 1 0 14:23 ? 00:00:00 : master process ./ root 10643 10640 0 14:23 ? 00:00:00 : worker process root 10644 10640 0 14:23 ? 00:00:00 : cache manager process root 10869 10640 0 14:32 ? 00:00:00 : worker process root 10939 10137 0 14:32 pts/2 00:00:00 grep --color=auto
可以看到原先的 Worker 进程被杀死后, 的主进程又立马拉起来新的 Worker 进程提供服务。这说明 是非常可靠的,只要 Master 进程还在就会保证 Worker 进程持续存在并提供服务。
向主进程发送 SIGHUP 信号,等价于 -s reload 操作。可以看到除了 Master 进程外,所有其他进程已经是新启动的进程了。
[root@server sbin]# sudo kill -SIGHUP 10640 [root@server sbin]# ps -ef | grep root 10640 1 0 14:23 ? 00:00:00 : master process ./ root 11059 10640 0 14:37 ? 00:00:00 : worker process root 11060 10640 0 14:37 ? 00:00:00 : worker process root 11061 10640 0 14:37 ? 00:00:00 : cache manager process root 11098 10137 0 14:37 pts/2 00:00:00 grep --color=auto
向主进程发生 SIGTERM 信号,等价于 -s stop 操作,即停止 服务,所有进程
[root@server sbin]# sudo kill -SIGTERM 10640 [root@server sbin]# ps -ef | grep root 11267 10137 0 14:43 pts/2 00:00:00 grep --color=auto
向主进程发生 USR1 信号,等价于 -s repoen 操作,即重新打开日志
[root@server sbin]# ./ [root@server sbin]# ps -ef | grep root 11408 1 0 14:48 ? 00:00:00 : master process ./ root 11410 11408 0 14:48 ? 00:00:00 : worker process root 11411 11408 0 14:48 ? 00:00:00 : worker process root 11412 11408 0 14:48 ? 00:00:00 : cache manager process root 11413 11408 0 14:48 ? 00:00:00 : cache loader process root 11484 10137 0 14:49 pts/2 00:00:00 grep --color=auto [root@server sbin]# ll ../logs/access.log -rw-r--r-- 1 root root 384349 Feb 11 14:26 ../logs/access.log [root@server sbin]# rm -rf ../logs/access.log [root@server sbin]# ll ../logs/access.log ls: cannot access ../logs/access.log: No such file or directory [root@server sbin]# kill -USR1 11408 [root@server sbin]# ll ../logs/access.log -rw-r--r-- 1 root root 0 Feb 11 14:50 ../logs/access.log
可以看到,在移除 的 access.log 日志后,在向 主进程发送 USR1 信号, 会重新新的 access.log 日志。
4. 小结
本节主要介绍 的进程模型以及介绍了 Master 和 Worker 之通信过程。此外,我们介绍了 的事件驱动模型。异步、多路IO复用、非阻塞等特点早就了 的高。接下来,我们完成了 进程模型的几个实验,直观体检 的进程模型。下一节将重点介绍 模块相关的,并手动实现简单的 http 模块。