共计 7040 个字符,预计需要花费 18 分钟才能阅读完成。
倒退
Nginx 的开发始于 2002 年,由 Igor Sysoev 发动,最后是为了解决 C10K 问题,即如何让一个服务器同时解决超过一万个客户端连贯。
第一个公开版本公布于 2004 年 10 月。自此,Nginx 此开始了它的疾速倒退。Nginx 目前是世界上应用最多的 Web 服务器之一,依据 Netcraft 的统计,截至 2024 年 2 月,Nginx 占据了寰球 Web 服务器市场的 32.8% 的份额,仅次于 Apache。
装置与治理
支流 Linux 发行版的包管理工具都反对下载 nginx,这里以 Manjaro 为例:
sudo pacman -S nginx
Nginx 可通过 -s
参数响应一些自带的信号,比方:
stop
:立刻敞开quit
:失常敞开reload
:从新加载配置文件reopen
:从新关上日志文件
例如,当咱们在 etc/nginx/conf.d
中批改配置文件增加 Web 服务时,改变内容并不会间接失效,须要咱们重启 nginx 服务或者应用 -s reload
指令传递从新加载配置文件的信号。
此外,咱们能够通过 systemctl
等指令治理本地的 Nginx 服务,比方最常见的查看 Nginx 状态的指令:
systemctl status nginx
Nginx 配置文件构造
在正式介绍之前,我想要先从 nginx 配置文件动手,这可能会更加便于了解,毕竟配置文件是咱们接触 nginx 最间接的中央。
Linux 下 nginx 配置文件的默认地位是在/etc/nginx/nginx.conf
,这里先举一个简略的例子:
# 全局块
user nobody; # 指定运行 nginx 服务的用户和用户组
worker_processes 1; # 指定工作线程数
error_log logs/error.log; # 指定谬误日志的门路和级别
pid logs/nginx.pid; # 指定 pid 文件的门路
# events 块
events {worker_connections 1024; # 指定每个工作过程能够同时开启的最大连接数}
# http 块
http {
# http 全局块
include mime.types; # 引入 MIME-Type 定义文件
default_type application/octet-stream; # 设置默认的 MIME-Type
sendfile on; # 开启 sendfile 传输文件的优化
keepalive_timeout 65; # 设置连贯超时工夫
# server 块
server {
# server 全局块
listen 8000; # 监听 8000 端口
server_name localhost; # 设置虚拟主机的名称
# location 块
location / {
root html; # 设置根目录的门路
index index.html index.htm; # 设置默认的首页文件
}
# location 块
location /images {
root /data; # 设置图片资源的门路
autoindex on; # 开启目录浏览性能
}
# location 块
location ~ \.php$ {
root html; # 设置 PHP 文件的门路
fastcgi_pass 127.0.0.1:9000; # 设置 PHP-FPM 的地址和端口
fastcgi_index index.php; # 设置默认的 PHP 文件
fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; # 设置 PHP 脚本的残缺门路
include fastcgi_params; # 引入 fastcgi 的配置文件
}
}
}
- 全局块:从开始到
events
块之间的局部,次要设置一些影响 nginx 服务器整体运行的配置指令,例如user
,worker_processes
,error_log
,pid
等。 events
块:波及的指令次要影响 nginx 服务器与用户的网络连接,例如worker_connections
,accept_mutex
,multi_accept
,use
等。http
块:蕴含http
全局块和多个server
块,代理、缓存和日志定义等绝大多数的性能和第三方模块的配置都能够放在这个块中。http
全局块是不蕴含在server
块中的局部,次要设置一些影响http
协定的配置指令,例如include
,default_type
等。server
块是虚拟主机的配置,每个server
块能够蕴含server
全局块和多个location
块。server
全局块是不蕴含在location
块中的局部,次要设置一些影响虚拟主机的配置指令,例如listen
,server_name
等。location
块是 URL 匹配的配置,每个location
块能够蕴含一些针对特定申请的解决规定,例如root
,index
,proxy_pass
等。
从不便了解的角度,每个 server
块就对应一个利用,而 location
能够了解为利用对应的路由。个别状况下,咱们想要在本人的服务器中新增某个 Web 利用配置,就在 http
块中增加 server
块即可。
你能够尝试在 nginx 默认端口为 80 的 server
块中增加上面这个 location
块:
location / {
default_type text/plain
return 200 "pong"
}
退出保留,应用 nginx -s reload
从新加载配置文件,应用 curl
或在浏览器中申请 http://ocalhost/ping
,即可失去返回的pong
响应数据。
curl http://localhost/ping # pong
工作模式概述
在正式开始 Nginx 过程模型的介绍之前,还须要理解下 nginx 的根本工作模式是怎么的。
Nginx 服务启动后,会创立一个 master 主过程,该主过程在进行一部分初始化工作后,会产生一个或多个工作过程 worker;收到来自客户端的申请后,nginx 可能波及与后端服务器的通信,它能够将收到的 http 申请代理转发到指标服务器,由专门的后端服务器解决数据。
同时,为了进步对申请的响应效率,升高服务器受到的网络压力,nginx 采纳了缓存机制,将历史应答数据缓存到本地,保障了客户端对缓存文件的快速访问。
过程模型
Nginx 采纳了 master-worker 过程模型。相比于 apache 采纳的传统多进模型,nginx 的过程模型有一些显著的劣势:
- Master-worker 异步非阻塞的特点容许 nginx 在高并发下放弃低资源低消耗高性能,同时也进步了服务的稳定性,单个 workder 过程出现异常不影响其余 worker 和 master 的运行;
- 实现了热部署,即在不重启 nginx 服务的前提下从新加载配置文件;
- ……
master 过程
master 模块负责接管内部信号,在依据信号的不同治理 worker 模块以实现对应的性能。master 模块自身不会解决网络申请,它只是作为一个调度者,作为用户与 worker 之间的桥梁而存在。
在 nginx 服务初始化时,master 会读取并解析配置文件,呈现谬误就报告并推出。咱们能够应用 -t
参数被动查看配置文件是否存在谬误,以确保不会重启 nginx 失败:
sudo nginx -t
在运行过程中,master 过程也会监控 worker 过程运行状态、如果 worker 模块出现异常,master 就会 fork()
一个新的 worker 过程,保障整个 nginx 服务失常运行。
那当初,咱们会想晓得 master 过程具体是如何治理 worker 过程的呢 🤔️
master 治理 worker
master 过程通过信号量机制(Semaphore Mechanism)和定时器机制(Timer Mechanism)来监控并治理 worker 过程。
信号量机制(semaphore mechanism)是一种用于实现过程间同步和互斥的办法,它应用一个整数变量来示意零碎中某种资源的数量或状态,过程能够通过原子操作(atomic operation)来减少或缩小信号量的值,从而实现对资源的申请和开释。
nginx 的 master 模块和 worker 模块之间通过信号(signal)来实现同步和通信,信号是一种用于过程间交互的软件中断,它能够用来传递一些简略的信息或命令。nginx 应用了一些预约义的信号,比方 SIGCHLD
、SIGALRM
、SIGTERM
、SIGQUIT
、SIGHUP
、SIGUSR1
等,每个信号都有特定的含意和作用。
- 当 worker 模块退出或者解体时,它会向 master 模块发送
SIGCHLD
信号,告诉 master 模块有子过程曾经终止。master 模块在收到SIGCHLD
信号后,会调用waitpid()
函数来回收子过程的资源,并查看子过程的退出码,如果是非正常退出,就会从新fork()
一个新的 worker 模块,保障服务的可用性。 - 当 master 模块须要重启或者降级时,它会向 worker 模块发送
SIGTERM
或者SIGQUIT
信号,要求 worker 模块失常敞开或者立刻终止。worker 模块在收到这些信号后,会进行承受新的申请,并解决完曾经承受的申请,而后退出。master 模块在收到所有 worker 模块的SIGCHLD
信号后,会从新加载配置文件,并启动新的 worker 模块。 - 当 master 模块须要从新加载配置文件或者从新关上日志文件时,它会向 worker 模块发送
SIGHUP
或者SIGUSR1
信号,要求 worker 模块从新读取配置文件或者从新关上日志文件。worker 模块在收到这些信号后,会先敞开旧的配置文件或者日志文件,而后关上新的配置文件或者日志文件,并持续解决申请。
而定时器机制(timer mechanism)是一种用于实现过程间通信和调度的办法,它应用一个计数器来示意零碎中某种事件的产生工夫或距离,过程能够通过设置或勾销定时器来触发或勾销某种动作,从而实现对事件的响应和管制。
nginx 则应用了 SIGALRM
信号来实现定时器的性能。
- 当 master 模块启动时,它会设置一个定时器,每隔肯定的工夫(默认是 5 秒),就会向 worker 模块发送
SIGALRM
信号,要求 worker 模块向 master 模块报告本人的状态,比方是否存活、是否繁忙、是否有异样等。master 模块在收到 worker 模块的回应后,会更新 worker 模块的状态,并依据 worker 模块的状态来调整定时器的距离,如果 worker 模块频繁退出或者出错,就会缩短定时器的距离,反之则会缩短定时器的距离。 - 当 master 模块收到
SIGALRM
信号时,它会查看 worker 模块是否存活,如果发现有 worker 模块曾经死亡,就会从新fork()
一个新的 worker 模块,保障服务的可用性。master 模块还会查看 worker 模块是否繁忙,如果发现有 worker 模块长时间没有解决申请,就会认为 worker 模块曾经卡死,而后向 worker 模块发送SIGKILL
信号,强制终止 worker 模块,并从新fork()
一个新的 worker 模块,保障服务的可用性。
worker 过程
worker 承受 master 的调度,负责解决客户端的连贯和申请。客户端的申请齐全由 worker 解决,而且申请与 worker 是一一对应的关系。同时,worker 过程之间都是平等关系。
这句话能够引出来一个问题:worker 之间是平等的关系,每条申请只由单个 worker 解决,但接管申请时会存在多个 worker,那 master 是怎么决定让哪个 worker 去解决以后申请的呢?
worker 工作流程
在开始之前,有必要先介绍 nginx 的 accepy_mutex 机制,该机制无效的防止了惊群效应(thundering herd problem)和锁队列(lock convoy)问题,而这些问题会导致 nginx 的性能降落和资源节约。
accept_mutex 是一个互斥锁(mutex),它能够保障在同一时刻,只有一个 worker 能够承受新连贯,其余 worker 则会期待或者解决已有的连贯。
accept_mutex 机制的开启和敞开能够通过配置文件中的 accept_mutex
指令来管制,它的默认值是 off
。同时,还会有一个accept_mutex_delay
工夫参数,它指定了在另一个 worker 正在承受新连贯的状况下,worker 尝试从新开始承受新连贯的最长工夫,它的默认值是500ms
。这个参数能够防止工作过程频繁地争夺 accept_mutex,从而缩小零碎开销。
好,到这里就差不多。当初介绍 worker 工作流程。
在 nginx 服务器启动时,会先创立一个 master 过程,master 会先建设好须要 listen
的 socket(listenfd)之后,再依据配置文件中 worker_process
指令创立指定数量的 worker 过程,用于解决申请。而后,master 会创立 accept_mutex,并把它传递给 worker。
每个 worker 都会初始化事件模块(event module)和连贯模块(connection module),事件模块负责监听和处理事件,连贯模块负责管理连贯。worker 会依据配置文件中的 use
指令,抉择最合适的事件告诉机制, 再依据配置文件中的 worker_connections
指令,创立一个连接池(connection pool),并调配肯定数量的连贯(connection)给事件模块,每个连贯都有一个读事件(read event)和一个写事件(write event)。
当有新连贯到来时,事件模块会告诉 worker,而后所有 worker 会来抢惟一的 accept_mutex,抢到 mutex 的 worker 过程就会注册 listenfd 读事件,在读事件里调用 accept 承受该连贯。当 worker 承受完新连贯后,它会开释 accept_mutex,并解决新连贯的申请。
那没抢到 mutex 的 worker 会做什么呢?它们会期待 accept_mutex_delay
的工夫,如果在这段时间内没有其余 worker 取得 mutex,它们就会再次尝试获取 mutex,并反复上述过程;如果在这段时间内有其余 worker 取得 mutex,那么期待的 worker 就会放弃,转而持续解决已有的连贯或者进入休眠状态。
抢到 mutex 的 worker 会进入事件循环(event loop),一直地查看事件队列中是否有就绪的事件,如果有,就调用相应的事件处理函数。对于读事件,事件处理函数会读取客户端发送的数据,并依据数据的类型,调用相应的模块来解决申请,例如,如果是 HTTP 申请,就调用 HTTP 模块(HTTP module);如果是邮件申请,就调用邮件模块(mail module)……对于写事件,事件处理函数会发送数据给客户端,并依据数据的状态,决定是否敞开连贯或者持续解决申请。
解决完一个连贯或者申请时,worker 会把连贯放回连接池中,期待下一次应用,或者开释连贯,以便其余工作过程应用。
补充
Netcraft
Netcraft 是一家英国的网络安全公司,成立于 1995 年。该公司次要提供互联网基础架构,网络安全,以及网站评测等服务。其中,Netcraft 的网站评测性能能够帮忙用户评估网站的安全性,包含网站托管地址,服务器软件,以及脚本语言等信息。
Netcraft 每月都会对寰球的网站进行抽样调查,收集网站的响应头、域名、IP 地址、证书等数据,而后依据这些数据分析出网站应用的服务器软件,操作系统,网络服务商等信息。
Netcraft 的调查报告曾经成为人们理解寰球网站数量以及各种服务器市场份额等状况的次要根据。拜访 Netcraft 的官网就能够查看最新的调查结果,或者应用它的搜寻性能,查问任意网站的相干信息。
异步非阻塞
前文中有提到 master-worker 是一种异步非阻塞的过程模型。我认为能够在这里做一个更具体的形容,就是该过程模型为什么是“异步非阻塞”的:
- 异步:在 nginx 的 master-worker 模式中,master 过程负责管理 worker 过程,而 worker 过程则解决理论的客户端申请。这种架构下,master 过程和 worker 过程之间采纳异步通信形式,master 过程不会阻塞在期待 worker 过程的响应上,从而进步了整体的并发解决能力;
- 非阻塞:nginx 中的 worker 过程应用非阻塞 I/O 操作来解决客户端申请。这意味着当一个申请须要进行 I/O 操作时(比方读取文件或从网络接收数据),worker 过程不会始终期待数据准备就绪,而是会持续解决其余申请。一旦数据准备就绪,worker 过程会立刻解决它,而不会阻塞在这个操作上;
- 事件驱动:nginx 应用事件驱动的形式来解决 I/O 操作。它利用操作系统提供的事件告诉机制(如 epoll 或 kqueue)来实现非阻塞 I/O。当一个事件产生时(比方一个连贯建设或数据可读),nginx 将相应的事件增加到事件队列中,并通过事件驱动的形式解决这些事件,而不是通过阻塞式的期待。
nginx 的 master-worker 模式通过异步、非阻塞和事件驱动的设计,实现了高性能和高并发解决能力。这种模式使得 nginx 可能高效地解决大量并发申请,而不会因为阻塞在 I/O 操作上而导致性能降落。而 apache 传统的多过程模型(Prefork)无奈实现异步非阻塞,所以会把这点作为 nginx 相比 apache 的一个劣势。
后话
只是一个概述,很多内容等着补充 😪️
参考文档
- Nginx 中文文档
- Nginx 齐全手册,by freeCodeCamp
- Nginx 工作模式和过程模型,by 已下线