文章目录
- [1. Nginx核心架构与工作原理](#1. Nginx核心架构与工作原理)
-
- [1.1. 多进程模型:主进程与工作进程](#1.1. 多进程模型:主进程与工作进程)
- [1.2. 异步非阻塞的事件驱动模型](#1.2. 异步非阻塞的事件驱动模型)
- [1.3. 高度模块化的设计](#1.3. 高度模块化的设计)
- [2. Nginx反向代理 (Reverse Proxy) 实现机制](#2. Nginx反向代理 (Reverse Proxy) 实现机制)
-
- [2.1. proxy_pass指令与核心工作原理](#2.1. proxy_pass指令与核心工作原理)
- [2.2. 负载均衡 (Load Balancing)](#2.2. 负载均衡 (Load Balancing))
- [2.3. 健康检查 (Health Checks)](#2.3. 健康检查 (Health Checks))
- [2.4. 会话保持 (Session Persistence)](#2.4. 会话保持 (Session Persistence))
- [2.5. TLS/SSL终止 (TLS/SSL Termination)](#2.5. TLS/SSL终止 (TLS/SSL Termination))
- [3. Nginx正向代理 (Forward Proxy) 实现机制](#3. Nginx正向代理 (Forward Proxy) 实现机制)
-
- [3.1. HTTP正向代理](#3.1. HTTP正向代理)
- [3.2. HTTPS正向代理 (基于CONNECT方法)](#3.2. HTTPS正向代理 (基于CONNECT方法))
- [4. TCP/UDP代理 (Stream模块)](#4. TCP/UDP代理 (Stream模块))
-
- [4.1. 配置与实现](#4.1. 配置与实现)
1. Nginx核心架构与工作原理
Nginx的高性能根植于其独特而高效的架构设计,它摒弃了传统Web服务器为每个连接创建新进程或线程的模式,转而采用一种更为先进的异步、非阻塞事件驱动模型。
1.1. 多进程模型:主进程与工作进程
Nginx启动后会创建一个 主进程(Master Process) 和至少一个 工作进程(Worker Process) 。这种设计明确了职责分离,是其稳定性和灵活性的基石。
主进程(Master Process):
- 角色定位:作为整个Nginx实例的"管理者"或"协调者",它本身不直接处理任何网络请求。
- 核心职责 :
- 配置加载与验证:读取并解析nginx.conf等配置文件,验证其语法正确性。
- 工作进程管理:根据配置(如worker_processes指令)创建、绑定(例如绑定到特定CPU核心)并维护指定数量的工作进程 。
- 权限管理 :主进程通常以root用户身份启动,以便监听80、443等特权端口。启动后,它会以指定的非特权用户(如www-data或nginx)身份创建工作进程,增强了系统安全性。
- 信号处理:响应来自操作系统的信号,实现对Nginx服务的平滑管理。例如,接收到HUP信号时会平滑地重新加载配置,而接收到QUIT信号则会优雅地关闭工作进程 。
- 平滑升级:支持在不中断服务的情况下升级Nginx二进制文件。主进程可以启动新版本的工作进程,并逐步将请求转移过去,然后关闭旧进程 。
工作进程(Worker Process):
- 角色定位:是处理网络请求的实际执行单元。每个工作进程都是单线程的,这避免了线程间锁竞争和上下文切换带来的开销 。
- 核心职责 :
- 网络事件监听:通过epoll(Linux)、kqueue(FreeBSD)等多路复用I/O技术,一个工作进程可以高效地监听和管理成千上万个并发连接 。
- 请求处理:接收客户端连接,读取请求,处理请求(例如提供静态文件、代理请求),并将响应写回客户端。所有这些操作都是以非阻塞方式进行的 。
- 独立运行:工作进程之间相互独立。如果某个工作进程因异常崩溃,主进程会迅速侦测到并重新启动一个新的工作进程,而其他工作进程则继续正常服务,保证了服务的高可用性。
1.2. 异步非阻塞的事件驱动模型
事件驱动是Nginx实现高并发的核心秘诀 。它将一个完整的请求/响应过程分解为一系列离散的"事件"。
核心组件:
- 事件收集器 (Event Collector) :利用操作系统的I/O多路复用机制(如epoll),等待多个连接上的事件(如新连接到达、数据可读、数据可写)发生。
- 事件发送器 (Event Dispatcher) :将已发生的事件分发给对应的事件处理器。
- 事件处理器 (Event Handler) :即Nginx模块中预定义的函数,负责处理特定事件。
工作流程(事件循环 - Event Loop):
- 工作进程在其单线程内启动一个无限循环,即 事件循环(Event Loop) 。
- 在循环中,工作进程调用epoll_wait(以epoll为例)等函数,将自己"挂起",等待网络事件的发生。
- 当一个或多个事件(如客户端发起连接、数据到达、可以向客户端发送数据等)发生时,操作系统会通知工作进程。
- 工作进程被唤醒,并根据事件类型调用相应的处理函数(Handler)。这些函数执行一小段非阻塞操作,例如读取一小部分数据到缓冲区,或发送一小部分数据。
- 如果一个操作无法立即完成(如等待后端响应),Nginx不会阻塞,而是注册一个关心后续事件的回调函数,然后返回事件循环,继续处理其他连接的事件 。
- 当后续事件(如后端数据返回)发生时,事件循环会再次调用之前注册的回调函数,继续处理该连接的后续阶段。
这种模型使得单个工作进程能够同时处理大量并发连接,因为进程几乎所有时间都用于实际的数据处理,而非空闲等待I/O操作完成。
1.3. 高度模块化的设计
Nginx的功能是由一系列模块组合而成的 。这种设计提供了极高的灵活性和可扩展性。
模块分类:
- 核心模块 (Core Modules) :提供Nginx最基础的功能,如进程管理、事件处理、配置解析等。
标准HTTP模块:如ngx_http_proxy_module(反向代理)、ngx_http_static_module(静态文件服务)等,提供了常见的Web服务器功能。 - 可选模块:如ngx_http_ssl_module(HTTPS支持)、ngx_http_gzip_module(内容压缩)等,可根据需求在编译时选择性加入。
- 第三方模块:用于扩展Nginx的功能,如ngx_http_proxy_connect_module(HTTPS正向代理支持)或lua-nginx-module(集成Lua脚本能力) 。
请求处理流水线 (Request Processing Pipeline):
Nginx将一个HTTP请求的处理过程划分为11个阶段(Phases),例如NGX_HTTP_POST_READ_PHASE、NGX_HTTP_REWRITE_PHASE、NGX_HTTP_CONTENT_PHASE等 。不同的HTTP模块可以将自己的处理函数(Handler)"挂载"到这些阶段上。当一个请求到来时,它会按顺序流经这些阶段,每个阶段执行挂载其上的模块逻辑。这种设计如同一个流水线,使得功能模块间既能协作又能保持低耦合 。
2. Nginx反向代理 (Reverse Proxy) 实现机制
反向代理是Nginx最核心和最广泛的应用场景。它作为客户端与后端真实服务器之间的中间层,接收客户端请求,转发给后端,并将后端响应返回给客户端 。
2.1. proxy_pass指令与核心工作原理
proxy_pass是ngx_http_proxy_module模块提供的核心指令,用于定义将请求转发到的后端服务器地址 。
- 工作机制:当一个请求匹配到包含proxy_pass指令的location块时,Nginx会与proxy_pass指定的上游服务器(Upstream)建立一个新的连接,并将客户端的请求(可能经过修改)发送给上游服务器。收到上游服务器的响应后,Nginx再将其转发给客户端。
- URL处理逻辑 :proxy_pass后面跟的URL是否带有URI,会影响转发时请求URI的重写规则 。
- 不带URI (e.g., proxy_pass http://backend; ):Nginx会将location匹配的完整URI转发给上游。
- 带有URI (e.g., proxy_pass http://backend/app/; ):Nginx会将location匹配部分之后的那部分URI拼接到proxy_pass的URI之后。
2.2. 负载均衡 (Load Balancing)
当后端服务由多台服务器组成集群时,Nginx可以使用ngx_http_upstream_module模块实现负载均衡,将请求分发到不同的服务器,以提高系统的处理能力和可用性 。
配置:通过upstream块定义一个服务器组,然后在proxy_pass中引用该组名。
bash
upstream my_backend {
server backend1.example.com weight=5;
server backend2.example.com;
server 192.168.0.1:8080 backup;
}
server {
location / {
proxy_pass http://my_backend;
}
}
负载均衡算法:
- 轮询 (Round Robin) :默认算法,按顺序将请求逐一分配到各服务器。
- 加权轮询 (Weighted Round Robin) :根据服务器配置的weight值分配请求,权重越高性能越好的服务器处理的请求越多 。
- IP哈希 (IP Hash) :根据客户端请求的源IP地址进行哈希计算,确保来自同一客户端的请求总是被发送到同一台后端服务器。这对于需要保持会话状态的应用非常有用。
- 最少连接 (Least Connections) :将新请求转发给当前活动连接数最少的服务器,适用于处理长连接或请求处理时间差异较大的场景。
2.3. 健康检查 (Health Checks)
为确保高可用性,Nginx需要能够检测后端服务器的健康状况,并在服务器故障时自动将其从负载均衡池中移除。
- 被动健康检查:这是Nginx开源版本内置的功能。通过在upstream的server指令中配置max_fails和fail_timeout参数实现。当在fail_timeout时间内,对某台服务器的连接尝试失败次数达到max_fails时,Nginx会认为该服务器宕机,并在接下来的fail_timeout时间内不再向其转发请求。
- 主动健康检查:这是Nginx Plus(商业版)提供的高级功能。Nginx Plus会定期主动向上游服务器发送特制的健康检查请求,并根据响应状态码或响应内容来判断服务器的健康状况,从而能更快速、更准确地发现和恢复故障服务器。
2.4. 会话保持 (Session Persistence)
在某些应用中,需要将来自同一用户的所有请求都定向到同一台后端服务器。
- 基于IP Hash:如前所述,ip_hash指令可以实现基于客户端IP的会话保持 。
- 基于Cookie (Sticky Cookie) :Nginx Plus支持"粘性Cookie"方法。Nginx在首次响应中插入一个特殊的Cookie,用于标识后端服务器。客户端随后的请求将携带此Cookie,Nginx据此将请求转发到指定的服务器。
2.5. TLS/SSL终止 (TLS/SSL Termination)
Nginx可以作为客户端与服务器之间的TLS/SSL终止点。这意味着客户端与Nginx之间建立加密的HTTPS连接,而Nginx与后端服务器之间则可以使用非加密的HTTP连接 。
-
优势:
- 卸载CPU密集型操作:将SSL/TLS的加解密计算任务集中在Nginx上,减轻了后端应用服务器的负担。
- 简化后端配置:后端服务器无需配置和管理SSL证书。
- 集中管理证书:所有证书和安全策略都在Nginx层统一管理。
-
配置实现:
在server块中,使用listen 443 ssl;指令监听HTTPS端口,并使用ssl_certificate和ssl_certificate_key指令指定服务器证书和私钥的路径。
bash
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /path/to/your/fullchain.pem;
ssl_certificate_key /path/to/your/private.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:...'; // 推荐使用安全的密码套件
location / {
proxy_pass http://my_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme; // 告知后端原始协议是https
}
}
其中,proxy_set_header X-Forwarded-Proto $scheme; 非常重要,它让后端应用能够知道客户端最初是使用HTTPS协议发起的请求。
3. Nginx正向代理 (Forward Proxy) 实现机制
正向代理隐藏了真实客户端,它代表局域网内的客户端去访问互联网上的服务器。客户端需要明确配置代理服务器地址。
3.1. HTTP正向代理
Nginx原生支持HTTP正向代理。其核心原理是利用resolver指令进行DNS解析,并将请求转发。
工作原理:
- 客户端配置Nginx为代理服务器,并向其发送一个包含完整URL的HTTP请求(如 GET http://www.example.com/HTTP/1.1)。
- Nginx接收到请求后,通过resolver指令配置的DNS服务器解析URL中的域名(www.example.com) 。
- Nginx向解析出的IP地址发起新的HTTP请求,并将原始请求的内容(可能经过修改)转发过去。
接收到目标服务器的响应后,Nginx再将其返回给客户端。
配置示例:
bash
server {
listen 8080; # 代理服务器监听的端口
# DNS解析服务器地址,必须配置
resolver 8.8.8.8;
location / {
# $http_host 变量包含原始请求的目标主机名
proxy_pass http://$http_host$request_uri;
# 传递一些重要的头部信息
proxy_set_header Host $http_host;
# 访问控制,只允许特定IP段使用代理
allow 192.168.1.0/24;
deny all;
# 基本认证
auth_basic "Proxy Authentication";
auth_basic_user_file /etc/nginx/proxy_users;
}
}
在这个配置中,resolver是关键,因为Nginx需要动态解析请求中的域名。同时,通常会配置访问控制(allow/deny)和身份认证(auth_basic)来限制谁可以使用该代理服务。
3.2. HTTPS正向代理 (基于CONNECT方法)
对于HTTPS流量,客户端与目标服务器之间需要建立一个端到端的加密隧道。这通过HTTP的CONNECT方法实现。然而,Nginx原生并不支持CONNECT方法 。
要使Nginx支持HTTPS正向代理,必须借助第三方模块,最常用的是 ngx_http_proxy_connect_module 。
工作原理:
- 客户端向Nginx代理发送一个CONNECT请求,要求与目标HTTPS服务器(如 example.com:443)建立隧道。
- 安装了ngx_http_proxy_connect_module的Nginx接收到CONNECT请求后,不再试图解析HTTP内容,而是作为纯粹的TCP代理。
- Nginx与目标服务器(example.com:443)建立一个TCP连接。
- 连接建立成功后,Nginx向客户端返回一个HTTP/1.1 200 Connection Established响应。
- 此时,一个从客户端到目标服务器的TCP隧道已经打通。客户端与目标服务器在此隧道上进行标准的TLS握手和后续的加密数据传输。Nginx仅在两者之间盲目地转发TCP数据包,不关心其内容。
编译与配置:
- 编译:下载Nginx源码和ngx_http_proxy_connect_module源码,在编译Nginx时通过--add-module参数将其加入 。
- 配置示例:
bash
server {
listen 8080;
resolver 8.8.8.8;
# proxy_connect指令启用CONNECT方法支持
proxy_connect;
# proxy_connect_allow指令指定允许CONNECT的端口
proxy_connect_allow 443 563; # 允许HTTPS和SNEWS
proxy_connect_connect_timeout 10s;
proxy_connect_read_timeout 10s;
# 对于非CONNECT方法的普通HTTP请求,按常规正向代理处理
location / {
proxy_pass http://$http_host$request_uri;
proxy_set_header Host $http_host;
}
}
该模块的核心指令是proxy_connect和proxy_connect_allow,前者启用功能,后者出于安全考虑限制了可以建立隧道的目的端口。
4. TCP/UDP代理 (Stream模块)
从1.9.0版本开始,Nginx引入了stream模块(ngx_stream_core_module),使其能力从HTTP(第七层)扩展到了TCP/UDP(第四层) 。这使得Nginx可以代理几乎任何基于TCP或UDP的服务,如数据库(MySQL, PostgreSQL)、消息队列(RabbitMQ)、DNS等。
4.1. 配置与实现
stream模块的配置需要放在http块之外的顶层stream块中,并且需要在编译时使用--with-stream参数启用。
- TCP/UDP反向代理与负载均衡:
bash
stream {
upstream my_db_cluster {
# least_conn; # 可以使用最少连接算法
server db1.example.com:3306;
server db2.example.com:3306;
}
server {
listen 3306; # 监听MySQL端口
proxy_pass my_db_cluster;
proxy_timeout 20s;
proxy_connect_timeout 5s;
}
}
此配置将Nginx服务器的3306端口作为MySQL集群的入口,并将客户端连接负载均衡到后端两台数据库服务器。
- TCP/UDP正向代理:
stream模块同样可以实现TCP/UDP的正向代理。例如,允许内网客户端通过代理访问外部的某个SSH服务。
bash
stream {
resolver 8.8.8.8;
server {
listen 2222; # 代理监听的端口
# 使用$proxy_protocol_addr和$proxy_protocol_port变量
# 这需要客户端使用PROXY protocol协议,或者通过其他方式传递目标地址
# 一个更通用的正向代理场景较难直接用stream实现,因为它不知道客户端想连哪里
# 但如果目标是固定的,可以这样配置:
proxy_pass some_external_ssh_server:22;
proxy_bind $remote_addr transparent; # 尝试绑定源IP,可能需要特殊权限
}
}
需要注意的是,通用的TCP正向代理(即客户端可以指定任意目标地址和端口)使用stream模块实现起来比HTTP正向代理更复杂,因为它缺乏像HTTP请求头那样传递目标信息的标准方式。通常需要依赖如SOCKS协议等更专业的代理协议,或者通过特定应用逻辑传递目标信息。