nginx的线程模型与网络模型
众所周知了,nginx一般用来当作HTTP服务器、反向代理、负载均衡器,其有着强悍的性能。
PHP服务早期还是LAMP架构,后来apache被nginx替代,变成流行的LNMP架构。
nginx性能为什么这么好?当然要从它的线程模型和IO模型入手。
线程模型与IO模型
nginx采用多进程单线程的线程模型,IO模型采用IO多路复用和非阻塞IO模型。
线程模型
work@x_centos:~$ ps -ef --forest | grep -v grep | grep nginx
root 5320 1 0 15:57 ? 00:00:00 nginx: master process /usr/local/nginx/sbin/nginx
nobody 5436 5320 0 16:02 ? 00:00:00 \_ nginx: worker process
nobody 5437 5320 0 16:02 ? 00:00:00 \_ nginx: worker process
nobody 5438 5320 0 16:02 ? 00:00:00 \_ nginx: worker process
nobody 5439 5320 0 16:02 ? 00:00:00 \_ nginx: worker process
nobody 5440 5320 0 16:02 ? 00:00:00 \_ nginx: cache manager process
nginx由一个master进程,fork出多个worker进程,(cache manager和cache loader 是静态文件缓存组件,这里不做重点)。
master进程读取conf,建立listen socket监听端口,并fork出 worker进程,由 worker进程来真正accept和处理请求或者和upstream服务器通信。
IO模型
nginx事件驱动模型,采用IO多路复用+非阻塞IO。
IO复用 epoll之前文章分享过,这里不多做介绍了。简单说,一个worker进程同时监听N个客户端fd,等待IO事件就绪,去执行对应设置好的回调函数。
抓包查看epoll
找个worker进程抓包看下系统调用,strace如下:
可以看到这个worker进程一直在反复epoll_wait(epoll实例的fd是10,超时时间是500ms)。显然这期间它并没有与任何客户端通信。
# 查看系统调用
work@x_centos:~$ sudo strace -p 5437
Process 5437 attached
gettimeofday({1653995976, 840165}, NULL) = 0
epoll_wait(10, {}, 512, 500) = 0
gettimeofday({1653995977, 340901}, NULL) = 0
epoll_wait(10, {}, 512, 500) = 0
gettimeofday({1653995977, 841557}, NULL) = 0
epoll_wait(10, {}, 512, 500) = 0
# 查看epoll实例fd
work@x_centos:~$ sudo lsof -p 5437 | grep 10u
nginx 5437 nobody 10u a_inode 0,9 0 4784 [eventpoll]
master进程
master进程,如上描述,主要负责读取配置、监听端口、fork子进程如worker进程等,并对子进程进行管理如发信号等。以配置热加载举例:
配置热加载的流程
当修改完nginx配置后,使之生效只需要执行 nginx -s reload
。此时就是给master进程发SIGHUP信号。
当master进程收到 SIGHUP信号时,会做两件事:
- 重新加载配置,并fork出一组新的 worker进程。这些新的 worker进程立即开始接受连接和处理流量(使用新的配置设置)。
- 向旧 worker进程发出信号以正常退出,旧worker进程停止接受新连接。一旦当前 HTTP请求完成,worker进程就会关闭当前连接,当关闭所有连接,这个老的worker进程就会退出。
这便完成配置变更后的热加载。
work@x_centos:~$ ps -ef --forest | grep -v grep | grep nginx
root 5320 1 0 15:57 ? 00:00:00 nginx: master process /usr/local/nginx/sbin/nginx
nobody 5438 5320 0 16:02 ? 00:00:00 \_ nginx: worker process is shutting down
nobody 6558 5320 0 17:39 ? 00:00:00 \_ nginx: worker process
nobody 6559 5320 0 17:39 ? 00:00:00 \_ nginx: worker process
nobody 6560 5320 0 17:39 ? 00:00:00 \_ nginx: worker process
nobody 6561 5320 0 17:39 ? 00:00:00 \_ nginx: worker process
nobody 6562 5320 0 17:39 ? 00:00:00 \_ nginx: cache manager process
这是热加载时,可以看到5438的 worker进程正在关闭(is shutting down)。
worker进程
nginx的worker进程数可配置,一般在nginx.conf 如 worker_processes 4;
通常配置cpu核数。
worker 进程就是负责真正干活的,接收accept客户端请求、读写处理请求和后端代理服务通信等。
事件驱动,这里不再赘述了。
有一点值得说下,如图描述nginx是使用Non-Blocking事件驱动,并且拿到connfd时,把它置为non-blocking。这是为什么呢?
IO多路复用与非阻塞IO
非阻塞IO简单说就是,系统调用read时如果能读就读出来,不能读则会立即返回EAGAIN(EWOULDBLOCK)错误标识而不会阻塞住。
那为什么往往IO多路复用都会搭配着非阻塞IO呢?其实从惊群问题的场景就能回答:
当多个进程都收到listenfd有可读事件发生时,都去read,显然只有一个可以读成功,那如果是阻塞IO这些进程怎么办?就会一直hang着了。
因此(当然还有其他原因)IO复用模型都会搭配非阻塞IO使用。
OK,以上就是对nginx线程模型和IO模型的介绍。实际当真正理解了epoll,这些都很容易理解。