传统的 Web 服务器如 Apache 使用多线程或多进程模型处理连接,每个请求分配一个线程。这种模型在并发量不高时工作良好,但在高并发场景下,线程切换开销和内存占用成为瓶颈。Nginx 采用了master-worker 进程模型:一个 Master 进程负责管理 Worker 进程,多个 Worker 进程负责处理请求,每个 Worker 使用单线程事件驱动的方式处理数千个连接。
Master 进程:
Worker 进程:
# nginx.conf 配置 worker 进程数
worker_processes 4; # 通常设置为 CPU 核心数
worker_cpu_affinity 0001 0010 0100 1000; # 绑定 CPU 核心
# 查看 Nginx 进程
ps -ef | grep nginx
# root 12345 1 0 08:00 ? 00:00:00 nginx: master process nginx
# nginx 12346 12345 0 08:00 ? 00:00:00 nginx: worker process
# nginx 12347 12345 0 08:00 ? 00:00:00 nginx: worker process
# nginx 12348 12345 0 08:00 ? 00:00:00 nginx: worker process
# nginx 12349 12345 0 08:00 ? 00:00:00 nginx: worker process
# 平滑重启 (重新加载配置,不中断服务)
nginx -s reload
# 优雅停止
nginx -s quit
2000 年代初,Web 服务器面临一个重大挑战:如何同时处理 10,000 个并发连接(C10K 问题)。传统 select 和 poll 模型在连接数增多时性能急剧下降。Linux 提供了 epoll 机制,它能够事件驱动地通知连接状态变化,而不是轮询所有连接。
epoll 核心接口:
触发模式:
# 创建 epoll 实例
int epfd = epoll_create1(0);
struct epoll_event ev, events[MAX_EVENTS];
# 添加监听套接字到 epoll
ev.events = EPOLLIN | EPOLLET; # 边缘触发
ev.data.fd = listen_sock;
epoll_ctl(epfd, EPOLL_CTL_ADD, listen_sock, &ev);
while (1) {
# 等待事件就绪
int nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);
for (int i = 0; i < nfds; ++i) {
if (events[i].data.fd == listen_sock) {
# 新连接
int conn_fd = accept(listen_sock, ...);
set_nonblocking(conn_fd);
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = conn_fd;
epoll_ctl(epfd, EPOLL_CTL_ADD, conn_fd, &ev);
} else {
# 处理已有连接的数据
handle_connection(events[i].data.fd);
}
}
}
代理服务器在客户端和服务器之间充当中介,可以隐藏真实 IP、缓存内容、加速访问、过滤请求。根据代理方向的不同,分为正向代理(代理客户端)和反向代理(代理服务器)。Nginx 主要作为反向代理使用,但也可以配置为正向代理。
正向代理 (Forward Proxy): 客户端配置代理服务器,由代理服务器转发请求到目标服务器。常见用途:突破网络限制、隐藏客户端 IP。
反向代理 (Reverse Proxy): 服务器配置代理,由代理服务器接收客户端请求并转发到后端服务器。常见用途:负载均衡、缓存、安全防护。
# 反向代理配置
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:8080; # 后端服务器
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
# 正向代理配置 (Nginx 支持有限)
# 通常使用 Squid 或 HAProxy 作为正向代理
当后端有多个服务器时,需要一个分发策略来决定每个请求应该转发到哪台服务器。好的负载均衡算法应该在资源利用率、响应时间、会话保持之间取得平衡。Nginx 提供了多种负载均衡算法,适应不同的业务场景。
轮询 (Round Robin): 请求依次分配到每台后端服务器,默认算法。适用于服务器性能相近的场景。
加权轮询 (Weighted Round Robin): 为每台服务器分配权重,按权重比例分发请求。适用于服务器性能差异明显的场景。
最少连接 (Least Connections): 将请求分配给当前活动连接最少的服务器。适用于长连接场景。
IP 哈希 (IP Hash): 根据客户端 IP 计算哈希值,将同一 IP 的请求固定到同一台服务器。适用于需要会话保持的场景。
# upstream 块定义后端服务器组
upstream backend {
# 轮询 (默认)
server 192.168.1.10:8080;
server 192.168.1.11:8080;
# 加权轮询
server 192.168.1.10:8080 weight=3;
server 192.168.1.11:8080 weight=2;
server 192.168.1.12:8080 weight=1;
# 最少连接
least_conn;
# IP 哈希 (会话保持)
ip_hash;
# 一致性哈希 (使用变量如 $http_x_forwarded_for)
hash $remote_addr consistent;
}
server {
listen 80;
location / {
proxy_pass http://backend;
}
}
Web 应用通常包含静态资源(HTML、CSS、JS、图片)和动态资源(API 接口、页面渲染)。静态资源无需计算,直接返回文件;动态资源需要业务逻辑处理。将静态资源与动态资源分离,可以分别优化:静态资源由 Nginx 直接返回,动态资源由后端应用服务器处理。
Nginx 静态文件服务: 使用 root 或 alias 指令指定文件目录,Nginx 直接读取文件并返回,性能极高。
# 静态资源处理
location /static/ {
root /var/www/html;
expires 30d; # 设置强缓存
add_header Cache-Control "public, immutable";
}
location /images/ {
root /var/www/html;
alias /var/www/images/;
expires 1y;
}
# 动态资源转发到后端
location /api/ {
proxy_pass http://backend_app;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# 默认路径: HTML 页面
location / {
root /var/www/html;
try_files $uri $uri/ /index.html;
}
Nginx 的 location 指令决定了不同 URL 路径的处理方式——是返回静态文件、代理到后端,还是执行其他处理。正确理解 location 的匹配规则和优先级,是 Nginx 配置的基础。
Location 修饰符:
# 1. 精确匹配 (优先级最高)
location = /favicon.ico {
root /var/www/static;
}
# 2. 前缀匹配 ^~ (停止正则搜索)
location ^~ /static/ {
root /var/www;
expires 30d;
}
# 3. 正则匹配 (区分大小写)
location ~ \.php$ {
fastcgi_pass php-fpm;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
# 4. 正则匹配 (不区分大小写)
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
root /var/www/static;
expires 1y;
add_header Cache-Control "public, immutable";
}
# 5. 普通前缀匹配 (最长匹配)
location / {
proxy_pass http://backend;
}