面试官:说说看Nginx是如何处理请求的?
当客户端发起一个请求时,Nginx的工作进程会监听网络端口,接收客户端的连接请求。以下是Nginx处理请求的具体流程:
(1) 接收连接请求:Nginx接收到客户端的连接请求后,会为该连接分配一个连接对象(ngx_connection_t)。连接对象包含连接的状态信息、读写事件处理器等。
(2) 读取请求头信息:Nginx从客户端读取请求头信息。请求头包含HTTP方法(如GET、POST)、URL、HTTP版本以及各种请求头字段(如Host、User-Agent、Content-Length等)。
(3) 解析请求头信息:Nginx解析请求头信息,提取必要的参数,如请求方法、URI、Host等。解析后的请求头信息存储在ngx_http_request_t结构体中。
(4) 查找匹配的虚拟主机和location块:
- Nginx根据请求头中的Host字段查找匹配的虚拟主机(server块)。每个虚拟主机可以配置不同的域名和监听端口。
- 在找到匹配的虚拟主机后,Nginx继续查找与请求URI匹配的location块。location块定义了如何处理特定路径的请求。
(5) 执行处理阶段:Nginx的请求处理分为多个阶段,每个阶段可以由多个模块处理。这些阶段包括:
- rewrite phase:执行重写规则,如URL重写。
- post rewrite phase:处理重写后的请求。
- preaccess phase:执行访问控制前的检查,如IP地址过滤。
- access phase:执行访问控制,如身份验证。
- postaccess phase:访问权限控制后的处理。
- try-files:尝试访问文件或目录。
- content phase:生成响应内容,如静态文件服务、反向代理、FastCGI等。在这个阶段,Nginx根据配置生成响应内容,这可能涉及读取静态文件、调用后端服务(如反向代理、FastCGI、uWSGI等)、生成动态内容等。
(6) 生成并发送响应:
- Nginx将生成的响应头发送回客户端。响应头包含HTTP状态码、响应头字段(如Content-Type、Content-Length等)。
- Nginx将生成的响应体发送回客户端。响应体可以是静态文件内容、后端服务返回的数据等。
(7) 关闭连接:一旦响应发送完毕,Nginx会关闭连接。如果启用了keep-alive连接,则连接可以保持打开状态,用于后续请求。
面试官:说说看Nginx的进程架构是怎样的?为什么Nginx不使用多线程模型?
1. 进程模型
Nginx采用Master-Worker多进程架构,这种架构的设计可以确保责任分离,以便更好地管理系统资源、并发请求处理与故障恢复。
(1) Master-Worker架构:
① 主进程(Master Process):
- Nginx的核心组件,负责初始化Nginx、加载配置文件、创建Worker进程等。
- 监听配置文件的变更,并在不重启的情况下重新加载配置。
- 管理Worker进程的生命周期,包括启动、停止和管理Worker进程。
- 不直接处理客户端的请求,而是用于控制和管理Worker进程。
② 工作进程(Worker Process)
- Nginx的工作进程,负责处理客户端的请求。
- 每个Worker进程都是一个完整的Nginx服务器,多个Worker进程之间是对等的。
- 每个Worker进程可以处理成千上万的并发连接,Nginx的事件模型可以根据系统负载自动选择合适的事件通知机制(如epoll)。
(2) 进程间协作:
- Master进程和Worker进程之间通过信号和共享内存进行通信。Master进程会向Worker进程发送信号以管理它们的生命周期(如启动、停止、重启等)。
- Worker进程之间通过共享内存和进程间通信(IPC)机制进行必要的数据共享和同步。
(3) 负载均衡:
Nginx通过多进程模型实现了负载均衡。当有新的客户端连接请求到达时,这些连接会被平均分配给各个Worker进程,从而实现负载均衡。这种设计确保了Nginx能够高效地处理大量并发连接,避免了单个进程成为瓶颈。
(4) 高可用性:
Nginx的多进程模型还提供了高可用性。当一个Worker进程出现故障时,Master进程会自动重新启动一个新的Worker进程来替代原来的进程,从而保证服务器的高可用性。这种设计使得Nginx能够在高负载和复杂环境下稳定运行。
2. 为什么Nginx不使用多线程模型?
Nginx选择不使用多线程模型,而是采用多进程加异步非阻塞I/O的事件驱动模型,主要基于以下几个原因:
(1) 资源隔离与稳定性
- 多进程模型下,每个工作进程都是独立的,它们之间不会共享内存空间(除了通过共享内存等特定机制进行通信)。这种隔离性使得一个工作进程的崩溃不会影响到其他进程,从而提高了整个系统的稳定性。
- 在多线程模型中,线程之间共享进程的内存空间,这可能导致线程间的数据竞争、死锁等问题,增加了系统的复杂性和调试难度。
(2) 利用多核CPU
- Nginx的多进程模型可以很好地利用现代操作系统提供的进程调度机制,将工作进程分配到不同的CPU核心上运行,从而实现并行处理。
- 虽然多线程模型也可以利用多核CPU,但线程的创建、切换和同步开销通常比进程更高,尤其是在高并发场景下。
(3) 避免线程竞争和死锁
- 在多线程模型中,多个线程可能同时访问共享资源(如内存、文件等),这需要使用锁机制来确保数据的一致性和安全性。然而,锁的使用往往会导致线程竞争和死锁问题,降低系统的性能。
- Nginx通过采用异步非阻塞I/O和事件驱动模型,避免了锁的使用,从而减少了线程竞争和死锁的风险。
(4) 简化编程模型
- Nginx的多进程加异步非阻塞I/O模型相对简单明了,开发者可以更容易地理解和维护代码。
- 多线程编程往往涉及复杂的线程同步和通信机制,增加了编程的复杂性和出错的可能性。
(5) 设计初衷
- Nginx的设计初衷就是为了提供一个高性能、低资源消耗的Web服务器和反向代理服务器。在设计之初,Nginx的开发者就选择了多进程加异步非阻塞I/O的模型,并一直沿用至今。
- Nginx的社区和开发者群体也倾向于保持这种设计哲学,以确保Nginx的稳定性和性能优势。
面试官:什么是正向代理和反向代理?Nginx如何实现正向代理和反向代理功能?
反向代理功能是指代理服务器接受互联网上的连接请求,然后将这些请求转发给内部网络上的服务器,并将从内部服务器上得到的响应返回给互联网上请求连接的客户端。在这个过程中,代理服务器在外部世界中显示为服务器。
一般来说反向代理中代理服务器和后台服务是一伙儿的,绑定在一起。客户端不知道自己实际请求的到底是谁。
现实中的反向代理例子有:负载均衡服务器、网络安全防护(防DDoS攻击) 和内容分发网络 CDN等。
Nginx实现反向代理功能主要通过配置Nginx服务器,使其成为客户端和目标服务器之间的中介。以下是Nginx实现反向代理功能的具体步骤和要点:
(1) 配置Nginx:
Nginx的反向代理配置主要在nginx.conf文件中进行,或者在包含的子配置文件中进行。
- 监听端口:设置Nginx监听的端口,默认为80端口,用于接收HTTP请求。
- 服务器名称:定义Nginx服务器响应的域名。
- location块:根据请求的URI进行匹配,并定义相应的操作,如反向代理。
- 反向代理指令(proxy_pass):指定请求应被转发到的后端服务器的URL。
(2) 配置示例:
一个基本的反向代理配置示例如下:
http {
server {
listen 80; # 监听80端口
server_name example.com; # 服务器名称
location / {
proxy_pass http://backend-server:8080; # 后端服务器地址与端口
proxy_set_header Host $host; # 保留原始Host头
proxy_set_header X-Real-IP $remote_addr; # 传递真实客户端IP
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme; # 传递请求协议(http/https)
# 其他可选配置,如缓存、超时、重试等
}
}
}
正向代理用于将客户端的请求转发到目标服务器,并将服务器的响应返回给客户端。在这种方式下,客户端将请求发送给代理服务器,由代理服务器代替客户端向目标服务器发出请求,并将目标服务器的响应返回给客户端。通过正向代理,客户端可以直接访问外部网络,而无需直接与目标服务器建立连接。
现实中的正向代理例子有:VPN。
Nginx作为高性能的Web服务器和反向代理服务器,也可以实现正向代理功能。以下是Nginx实现正向代理的具体步骤和配置方法:
Nginx正向代理的配置方法
由于默认的Nginx发布版本不支持正向代理功能,需要借助ngx_http_proxy_connect_module这个第三方插件来完成。在配置文件中添加正向代理的配置,例如:
server {
listen 3128; # 监听端口
resolver 114.114.114.114; # DNS解析器
proxy_connect; # 允许CONNECT请求
proxy_connect_allow 443 563; # 允许连接的端口
proxy_connect_connect_timeout 10s; # 连接超时时间
proxy_connect_data_timeout 10s; # 数据传输超时时间
location / {
proxy_pass http://$host; # 转发请求到目标服务器
proxy_set_header Host $host; # 设置请求头
}
}
以上配置中,listen指令指定了Nginx监听的端口,resolver指令指定了DNS解析器的地址,proxy_connect等指令用于配置CONNECT请求的处理,location指令和proxy_pass指令则用于指定将请求转发到哪个目标服务器。
面试官:什么是动态资源、静态资源分离?为什么要做动、静分离?Nginx怎么做的动静分离?
动态资源与静态资源分离(简称动、静分离)是一种常见的Web应用架构模式。
1. 动态资源与静态资源
(1) 静态资源
当用户多次访问某个资源时,如果资源的源代码不会发生改变,那么该资源就被称为静态资源。常见的静态资源包括图片(img)、样式表(css)、脚本文件(js)、视频(mp4)等。这些资源通常可以被浏览器和CDN(内容分发网络)缓存,以减少对服务器的重复请求。
(2) 动态资源当
用户多次访问某个资源时,如果资源的源代码可能会发生变化,那么该资源就被称为动态资源。常见的动态资源包括JSP、FTL等服务器端脚本或模板文件。这些资源通常需要根据用户的请求动态生成响应内容。
2. 动、静分离的原因
(1) 性能优化
动态内容和静态内容在处理和分发上存在差异。将它们分离可以分别进行优化,从而提高整体性能。例如,静态资源可以由专门的静态资源服务器(如Nginx)直接处理和提供,而动态资源则由应用服务器处理。这样可以显著减少对动态资源服务器的请求量,降低其负载。
(2) 缓存管理
静态资源易于被缓存,而动态资源通常不适宜缓存或需要更精细的缓存控制。通过动、静分离,可以更好地管理缓存策略,提高缓存命中率,减少服务器响应时间和带宽消耗。
(3) 负载均衡
分离后,可以根据内容类型对资源进行优化分配,实现更有效的负载均衡。例如,可以根据动态资源的访问量和特点,针对性地增加动态资源服务器的数量和规模,以应对高并发的访问需求。
(4) 安全性增强
静态内容服务器通常不需要执行复杂的程序代码,因此攻击面较小,可以降低安全风险。将其与执行动态代码的服务器分离可以降低潜在的安全威胁。
以下是Nginx动静分离的具体实现方式:
3. 配置方法
在Nginx中,可以通过location指令来实现动静分离。location指令用于匹配请求的URI,并根据不同的路径将请求分发给不同的处理模块。
(1) 静态资源处理
在上述配置中,location /static/和location /images/分别匹配以/static/和/images/开头的请求,Nginx将在指定的root目录下查找对应的文件。如果文件存在,Nginx会直接将文件返回给客户端。location ~* \.(jpg|jpeg|png|gif|ico|css|js)$使用正则表达式匹配所有以这些扩展名结尾的请求,并设置缓存过期时间和Cache-Control头部。
静态资源通常直接由Nginx处理,因此可以在location块中指定静态资源的目录。
使用正则表达式匹配静态资源的文件扩展名,如.jpg、.jpeg、.png、.gif、.ico、.css、.js等。
配置示例:
server {
listen 80;
server_name www.example.com;
# 静态资源处理
location /static/ {
root /var/www/example;
}
location /images/ {
root /var/www/example;
}
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
root /var/www/example;
expires 30d; # 设置静态资源的缓存过期时间
add_header Cache-Control "public"; # 添加Cache-Control头部,强化浏览器缓存行为
}
}
(2) 动态请求处理
对于动态请求,可以将请求转发给后端应用服务器(如PHP-FPM、Django、Node.js等)处理。
配置示例:
server {
listen 80;
server_name www.example.com;
# 动态请求处理
location ~ \.php$ {
root /var/www/example;
fastcgi_pass 127.0.0.1:9000; # 指定将这些请求转发给PHP-FPM服务器处理
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
在上述配置中,location ~ \.php$使用正则表达式匹配所有以.php结尾的请求,并将这些请求视为动态请求。fastcgi_pass指定将这些请求转发给PHP-FPM服务器处理,127.0.0.1:9000是PHP-FPM的监听地址。其他参数用于设置FastCGI参数和请求的文件路径。
4. 注意事项
(1) Nginx的location匹配优先级
Nginx的location匹配遵循特定的优先级规则,包括精确匹配(使用=符号)、正则表达式匹配(使用~或~*)、前缀匹配(普通location)。在配置多个location块时,需要谨慎考虑它们之间的优先级关系,以确保正确的请求路由。
(2) 缓存策略
通过合理配置缓存,可以显著提高网站性能。Nginx提供了强大而灵活的缓存功能,可以通过proxy_cache_path和proxy_cache指令来实现。对于静态资源,可以设置较长的缓存过期时间,以减少对服务器的重复请求。
(3) 安全性
通过动静分离,可以将静态内容服务器与执行动态代码的服务器分离,从而降低安全风险。静态内容服务器通常不需要执行复杂的程序代码,因此攻击面较小。
面试官:Nginx负载均衡的算法策略有哪些?
1. 轮询(Round Robin)
原理:轮询算法按照服务器列表的顺序依次分发请求。当一个新的请求到达时,Nginx会将其分配给列表中的下一个服务器,如果到达列表末尾,则重新开始循环。
特点:
- 简单易用,无需额外配置。
- 适用于后端服务器性能相近的情况,因为每个服务器都会轮流接收到请求。
http {
upstream backend {
server backend1.example.com;
server backend2.example.com;
server backend3.example.com;
}
server {
location / {
proxy_pass http://backend;
}
}
}
2. 加权轮询(Weighted Round Robin)
原理:在轮询的基础上,为每个服务器分配一个权重值。权重值越高的服务器,接收到的请求越多。Nginx会根据权重值来计算每个服务器接收请求的比例。
特点:
- 考虑了服务器性能的差异,可以灵活分配请求。
- 需要手动配置权重值,以反映服务器的实际性能。
- 适用于后端服务器性能不均衡的情况,可以更好地利用服务器资源。
http {
upstream backend {
server backend1.example.com weight=3;
server backend2.example.com weight=2;
server backend3.example.com weight=1;
}
server {
location / {
proxy_pass http://backend;
}
}
}
3. IP哈希(IP Hash)
原理:根据客户端IP地址的哈希值来分配请求。Nginx会计算每个客户端IP地址的哈希值,并使用该哈希值来选择后端服务器。由于相同IP地址的哈希值相同,因此来自同一IP地址的请求总是被分配到同一台后端服务器。
特点:
- 实现了会话粘性(Session Persistence),即同一个客户端的请求总是被分配到同一台后端服务器。
- 适用于需要保持会话一致性的场景,如购物车、用户会话等。
- 但可能导致负载分布不均衡,因为某些IP地址范围内的客户端可能会频繁访问同一台服务器。
http {
upstream backend {
ip_hash;
server backend1.example.com;
server backend2.example.com;
server backend3.example.com;
}
server {
location / {
proxy_pass http://backend;
}
}
}
4. 最少连接(Least Connections)
原理:将请求分发到当前连接数最少的服务器上。Nginx会监控每台后端服务器的当前连接数,并将新请求分配给连接数最少的服务器。
特点:
- 考虑了服务器的当前负载情况,可以更有效地平衡负载。
- 适用于长连接场景,如WebSocket、数据库连接等。
- 但需要Nginx维护连接状态,可能会增加一些开销。
- Nginx本身不直接支持此策略,通常需要借助第三方模块或自定义脚本实现。
http {
upstream backend {
least_conn;
server backend1.example.com;
server backend2.example.com;
server backend3.example.com;
}
server {
location / {
proxy_pass http://backend;
}
}
}
5. Fair(第三方)
原理:根据后端服务器的响应时间来分配请求。Nginx会监控每台后端服务器的响应时间,并将新请求分配给响应时间最短的服务器。
特点:
- 实现了更智能的负载均衡,可以根据服务器的实际性能来分配请求。
- 适用于对响应时间要求较高的场景。
- 但需要安装第三方模块(如nginx-module-vts)来实现。
6. URL哈希(URL Hash,第三方)
原理:根据请求URL的哈希值来分配请求。Nginx会计算每个请求URL的哈希值,并使用该哈希值来选择后端服务器。由于相同URL的哈希值相同,因此相同URL的请求总是被分配到同一台后端服务器。
特点:
- 提高了缓存的命中率,因为相同URL的请求总是被分配到同一台后端服务器。
- 适用于缓存服务器集群。
- 但同样可能导致负载分布不均衡。
- Nginx本身不支持此策略,需要安装Nginx的hash软件包来实现。