3886 words
19 minutes
Nginx 从入门到底层原理

Nginx 是什么#

Nginx(发音 “engine-x”)是一个开源的高性能 Web 服务器和反向代理服务器。

核心功能#

  • HTTP 服务器 — 直接提供静态文件(HTML、CSS、JS、图片等),性能极高,采用事件驱动、异步非阻塞架构,能以少量内存处理大量并发连接
  • 反向代理 — 接收客户端请求,转发给后端应用服务器(如 Node.js、Python、Java),再把响应返回给客户端。客户端不知道后端是谁
  • 负载均衡 — 将请求分发到多台后端服务器,支持轮询、加权、最少连接等策略,实现水平扩展
  • 邮件代理 — 支持 IMAP/POP3/SMTP 代理(较少使用)

典型使用场景#

客户端 → Nginx (80/443) → 后端服务 A (3000)
→ 后端服务 B (3001)
→ 静态文件 (/var/www)

为什么用它而不是直接暴露应用服务器#

  • 应用服务器(如 Express、Django)擅长处理业务逻辑,不擅长处理高并发静态资源
  • Nginx 在前端挡一层,处理 SSL 终止、压缩、缓存、限流等
  • 单机可以同时代理多个不同服务

关键配置示例#

server {
listen 80;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:3000; # 反向代理到 Node.js
}
location /static/ {
root /var/www; # 直接返回静态文件
}
}

Nginx 是互联网基础设施中最常见的”前台接待”——它接客、分流、挡活,让后端应用专注于业务逻辑。全球约 1/3 的网站使用 Nginx。

Nginx 是网关吗#

在大多数 Web 架构里,Nginx 扮演的就是网关的角色,但不完全等同。

Nginx 作为网关做的事#

客户端 → [Nginx 网关] → 内部服务 A
→ 内部服务 B
→ 内部服务 C
  • 统一入口,外部只暴露 Nginx 的端口
  • 路由分发(按路径/域名转发到不同服务)
  • SSL 终止(HTTPS → HTTP)
  • 限流、IP 黑白名单
  • 负载均衡

和 API 网关的区别#

能力NginxAPI 网关
路由/代理
负载均衡
SSL 终止
认证鉴权(JWT、OAuth)需要插件/Lua✅ 内置
限流/熔断基础能力✅ 更精细
服务发现
API 生命周期管理
动态配置(不改配置文件)❌(需要 reload)✅ 热更新
插件生态有限丰富

简单说:

  • Nginx 是网络层网关——处理”请求往哪转”的问题
  • API 网关(Kong、APISIX 等)是业务层网关——在 Nginx 之上加了认证、限流、服务发现等微服务治理能力
  • APISIX 底层其实就是基于 Nginx(OpenResty)的

Nginx 可以理解为网关的基础款,够用但功能有限。需要更复杂的微服务治理时,就在它上面加一层 API 网关。

Nginx 核心原理#

Nginx 的核心原理可以用一句话概括:一个主进程管理多个工作进程,每个工作进程用事件驱动的方式同时处理成千上万个连接,全程不阻塞。

进程模型#

Master 进程(1个) — 管理、读配置、监控
├── Worker 进程 1 — 实际处理请求
├── Worker 进程 2
├── Worker 进程 3
└── Worker 进程 N — 通常 = CPU 核心数
  • Master 不处理业务,只负责启动 Worker、热加载配置
  • 每个 Worker 是独立进程,互不干扰,一个挂了不影响其他的
  • Worker 数量通常设为 CPU 核心数,避免上下文切换开销

事件驱动(核心中的核心)#

传统服务器(如 Apache)用一个线程处理一个连接:

连接1 → 线程1(等着读数据...阻塞中...)
连接2 → 线程2(等着读数据...阻塞中...)
连接3 → 线程3(等着读数据...阻塞中...)
→ 1万个连接 = 1万个线程,内存炸了

Nginx 用 epoll(Linux)实现事件驱动:

Worker 进程(单个):
epoll 告诉我哪些连接有数据了?
→ 连接3有数据 → 处理
→ 连接7有数据 → 处理
→ 连接2有数据 → 处理
→ 没有数据的连接?不管它,不阻塞等
  • 一个 Worker 用一个线程就能处理几万个连接
  • 没有数据可读/可写时,不会阻塞等待,去处理其他连接
  • 这是 Nginx 高并发的根本原因

一次请求的完整生命周期#

以反向代理为例:

客户端发请求
① Nginx Worker 接收连接(epoll 通知有新连接)
② 解析 HTTP 请求(读取 method、path、headers)
③ 匹配 location 规则(比如 location /api/ → proxy_pass)
┌─────────────────────┐
│ ④ 建立到后端的连接 │
│ 把请求转发给后端服务 │
│ 注意:这也是非阻塞的!│
└─────────────────────┘
⑤ 后端返回响应,epoll 通知 Worker
⑥ Worker 把响应写回客户端(非阻塞写入)
⑦ 关闭/复用连接

关键点:步骤 ④ 等后端响应时,Worker 不会傻等,而是去处理其他连接。后端响应到了,epoll 会再次通知它。

零拷贝技术(高效传输静态文件)#

普通方式:文件从磁盘 → 内核空间 → 用户空间 → 内核空间 → 网卡

Nginx 用 sendfile()

磁盘 → 内核空间 → 网卡
(跳过了用户空间的拷贝,速度快一倍)

每个机制的贡献#

机制解决什么问题
多 Worker 进程多核利用 + 进程隔离
epoll 事件驱动一个线程处理几万连接,不阻塞
非阻塞 I/O等待时不占 CPU,去处理别的
零拷贝 sendfile静态文件传输省去多余内存拷贝
内存池小块内存复用,减少 malloc 开销

核心思想就八个字:事件驱动,非阻塞 I/O。这让它用极少的资源扛住巨大的并发。

底层原理深入#

epoll 的真正原理#

Nginx 高性能的根基是 Linux 的 epoll 系统调用。

select/poll 的问题(传统方式):

// 每次调用都要把所有 fd 传给内核
select(all_fds, &readable_fds, NULL, NULL, &timeout);
// 内核遍历全部 10000 个 fd → O(n)
// 返回后,用户态再遍历一次找出哪些就绪 → 又 O(n)

每次调用都是 O(n) 遍历,1 万个连接就要扫描 1 万次。

epoll 的做法:三步分离

// 第1步:创建 epoll 实例(只做一次)
int epfd = epoll_create1(0);
// 第2步:注册感兴趣的 fd(每个连接只做一次)
epoll_ctl(epfd, EPOLL_CTL_ADD, client_fd, &event);
// 第3步:等待事件(只返回就绪的 fd)
epoll_wait(epfd, events, max_events, timeout);

关键区别:内核维护了一个红黑树(存放所有被监听的 fd)和一个就绪链表(存放已有数据的 fd)。

红黑树(所有被监听的 fd) 就绪链表(有数据到达的 fd)
fd=3 fd=7 ✅
fd=5 fd=12 ✅
fd=7 ←── 网卡中断 ──→
fd=12 ←── 网卡中断 ──→
fd=15
...(共 10000 个)
  • 网卡收到数据 → 触发硬件中断 → 内核中断处理程序找到对应 fd → 把它放进就绪链表
  • epoll_wait 只需从就绪链表取数据,O(1),不管你监听了多少个 fd
  • 注册/删除 fd 是 O(log n)(红黑树)

这就是 Nginx 能轻松处理 10 万并发连接的底层原因。

请求处理流水线(11 个阶段)#

Nginx 把一个 HTTP 请求拆成了 11 个阶段(phase),每个阶段挂载不同的 handler 模块:

请求进来
① NGX_HTTP_POST_READ_PHASE — 刚读完请求头
② NGX_HTTP_SERVER_REWRITE_PHASE — server 块内的 rewrite
③ NGX_HTTP_FIND_CONFIG_PHASE — 查找匹配的 location
④ NGX_HTTP_REWRITE_PHASE — location 块内的 rewrite
⑤ NGX_HTTP_POST_REWRITE_PHASE — rewrite 后处理
⑥ NGX_HTTP_PREACCESS_PHASE — 访问前(限流、连接数限制)
⑦ NGX_HTTP_ACCESS_PHASE — 访问控制(IP黑白名单、认证)
⑧ NGX_HTTP_POST_ACCESS_PHASE — 访问控制后
⑨ NGX_HTTP_PRECONTENT_PHASE — 内容生成前(try_files)
⑩ NGX_HTTP_CONTENT_PHASE — 生成内容(proxy_pass、静态文件)
⑪ NGX_HTTP_LOG_PHASE — 记录日志

每个阶段可以挂多个 handler,按顺序执行。任何一个 handler 返回 NGX_DONE 就中断流水线。

这就是 Nginx 模块化的核心——不是一大坨 if-else,而是一条流水线,每个模块只管自己的阶段

内存池(ngx_pool_t)#

Nginx 不用 malloc/free 管理请求内存,而是用自己的内存池

ngx_pool_t(一个请求一个)
├── 小块内存区(分配 <= 4096 字节)
│ ┌──────────┬──────────┬──────────┐
│ │ 已分配 │ 已分配 │ 剩余空间 │
│ └──────────┴──────────┴──────────┘
│ 分配 = 移动指针,O(1)
├── 大块内存区(分配 > 4096 字节)
│ 用 malloc 单独分配,挂在链表上
└── 请求结束时:一次性销毁整个 pool,全部释放
// 不需要 free 每个小块,请求结束时统一销毁
ngx_destroy_pool(request->pool);

好处:

  • 分配:移动指针,O(1),没有 malloc 的碎片和锁开销
  • 释放:不需要逐个 free,整块回收
  • 没有内存泄漏——请求结束,池子销毁,全部归还

连接的内核级数据流#

一次 proxy_pass 请求,数据在内核中怎么流动的:

用户空间(Nginx Worker 进程)
│ ▲
read()│ │write()
│ ▲
─────────┼─┼────────── 内核空间边界
│ │
┌────▼─┴────┐
│ socket │ ← 内核 socket 缓冲区
│ 接收缓冲区 │ (客户端 → Nginx)
│ 发送缓冲区 │ (Nginx → 客户端)
└────────────┘
│ ▲
│ │ ← TCP/IP 协议栈
┌────▼─┴────┐
│ 网卡 │
└────────────┘
│ ▲
客户端 ← → 后端服务器

Nginx 的 Worker 在用户空间维护了两个 socket:

  • client socket:和客户端的连接
  • upstream socket:和后端服务器的连接

数据流:client socket → 读到用户空间 → 写到 upstream socket

这是两次系统调用 + 两次数据拷贝。

sendfile 零拷贝(静态文件场景)#

返回静态文件时,Nginx 用了 sendfile() 系统调用,数据完全不经过用户空间

普通 read() + write():
磁盘 → 内核页缓存 → 用户空间 buffer → 内核 socket 缓冲区 → 网卡
拷贝1 拷贝2 拷贝3
sendfile():
磁盘 → 内核页缓存 → 网卡
(只1次拷贝,用户空间完全不参与)
location /static/ {
sendfile on; # 开启零拷贝
tcp_nopush on; # 包满才发,减少小包
tcp_nodely on; # 最后的数据包不延迟
}

Worker 之间的通信#

Worker 进程之间不需要共享连接状态,但有些场景需要通信(比如热升级、缓存管理),Nginx 用 共享内存 + 进程间信号

Worker 1 ←── 共享内存(缓存、限流计数器)──→ Worker 2
│ │
└── signal(SIGTERM、SIGHUP 等)──→ Master ──┘
  • 限流limit_req 模块的计数器放在共享内存里,所有 Worker 共用
  • 缓存proxy_cache 的缓存索引在共享内存,缓存文件在磁盘
  • 热加载:Master 收到 SIGHUP → 加载新配置 → 启动新 Worker → 通知旧 Worker 优雅退出

优雅关闭(Graceful Shutdown)#

收到 SIGTERM / reload
① Worker 停止接受新连接
② 继续处理已建立的连接上的请求
③ 所有请求处理完毕 → 关闭连接
④ Worker 进程退出

worker_shutdown_timeout 可以设超时,防止某个长连接卡住不退出。

Nginx 快的底层原因总结#

层级技术效果
系统调用epollO(1) 事件通知,不遍历
I/O 模型非阻塞 + 异步一个线程处理几万连接
数据传输sendfile 零拷贝静态文件少 2 次内存拷贝
内存管理内存池O(1) 分配,无碎片,无泄漏
进程模型多 Worker 无锁无锁竞争,CPU 亲和
架构设计11 阶段流水线模块解耦,灵活可扩展

本质上 Nginx 就是把操作系统提供的高性能原语(epoll、sendfile、共享内存)用到了极致,再加上精心的内存管理和无锁架构。

安装与配置#

安装方式#

Terminal window
# Ubuntu/Debian
sudo apt update && sudo apt install nginx
# CentOS/RHEL
sudo yum install nginx
# macOS
brew install nginx
# Docker(推荐开发用)
docker run -d -p 80:80 --name nginx nginx:latest

安装后:

Terminal window
sudo systemctl start nginx # 启动
sudo systemctl enable nginx # 开机自启
sudo systemctl status nginx # 查看状态

配置文件结构#

/etc/nginx/
├── nginx.conf ← 主配置文件
├── conf.d/ ← 自定义配置(推荐放这里)
│ └── mysite.conf
├── sites-available/ ← 可用的站点配置
├── sites-enabled/ ← 已启用的(软链接)
└── mime.types ← 文件类型映射

主配置文件 nginx.conf 全貌#

# ===== 全局块 =====
worker_processes auto; # Worker 数量,auto = CPU 核心数
worker_rlimit_nofile 65535; # 每个 Worker 最大打开文件数
error_log /var/log/nginx/error.log warn; # 错误日志级别
pid /run/nginx.pid;
events {
worker_connections 4096; # 每个 Worker 最大连接数
use epoll; # Linux 用 epoll
multi_accept on; # 一次接受所有新连接
}
http {
# ----- 基础设置 -----
include mime.types;
default_type application/octet-stream;
# 日志格式
log_format main '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent"';
access_log /var/log/nginx/access.log main;
# 性能优化
sendfile on; # 零拷贝
tcp_nopush on; # 包满再发
tcp_nodelay on; # 不延迟
keepalive_timeout 65; # 长连接超时
client_max_body_size 50m; # 上传文件大小限制
# Gzip 压缩
gzip on;
gzip_types text/plain text/css application/json
application/javascript text/xml;
gzip_min_length 1024; # 小于 1KB 不压缩
# ===== 包含其他配置 =====
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}

常见场景配置#

静态网站#

server {
listen 80;
server_name example.com www.example.com;
root /var/www/html;
index index.html;
location / {
try_files $uri $uri/ =404;
}
# 静态资源缓存
location ~* \.(jpg|png|css|js|ico)$ {
expires 30d;
add_header Cache-Control "public, no-transform";
}
}

反向代理#

server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://127.0.0.1:3000; # 转发到后端
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;
# WebSocket 支持
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# 超时设置
proxy_connect_timeout 10s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
}
}

负载均衡#

# 定义后端服务器组
upstream backend {
# 默认轮询(round-robin)
server 192.168.1.10:3000 weight=3; # 权重 3,分到更多请求
server 192.168.1.11:3000 weight=1;
server 192.168.1.12:3000 backup; # 备用,其他挂了才用
# 保持会话一致(同一用户总是打到同一台)
ip_hash;
# 健康检查(商业版有主动检查,开源版用被动检查)
# max_fails=3 fail_timeout=30s → 30秒内失败3次就标记不可用
}
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}

其他负载均衡策略:

upstream backend {
# 最少连接数
least_conn;
server 192.168.1.10:3000;
server 192.168.1.11:3000;
}
upstream backend {
# IP 哈希(会话保持)
ip_hash;
server 192.168.1.10:3000;
server 192.168.1.11:3000;
}

HTTPS(SSL)#

server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/nginx/ssl/example.com.pem;
ssl_certificate_key /etc/nginx/ssl/example.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# SSL 会话复用,减少握手开销
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
location / {
proxy_pass http://127.0.0.1:3000;
}
}
# HTTP 自动跳转 HTTPS
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}

用 Let’s Encrypt 免费证书:

Terminal window
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d example.com # 自动配置 SSL

多个服务共存#

# 前端
server {
listen 80;
server_name www.example.com;
root /var/www/frontend;
location / {
try_files $uri $uri/ /index.html; # SPA 路由兜底
}
}
# API 后端
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://127.0.0.1:3000;
}
}
# 同一个域名,按路径分流
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:8080; # 前端
}
location /api/ {
proxy_pass http://127.0.0.1:3000; # 后端 API
}
location /ws/ {
proxy_pass http://127.0.0.1:4000; # WebSocket
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}

常用运维命令#

Terminal window
# 测试配置是否正确(改完配置必须先测)
sudo nginx -t
# 重新加载配置(不中断服务)
sudo nginx -s reload
# 停止
sudo nginx -s stop # 立即停止
sudo nginx -s quit # 优雅停止(处理完当前请求)
# 查看版本和编译参数
nginx -V
# 查看连接数
ss -tunlp | grep nginx
# 实时监控日志
tail -f /var/log/nginx/access.log

常见问题排查#

Terminal window
# 配置测试报错
sudo nginx -t
# → 看具体哪一行有语法错误
# 502 Bad Gateway → 后端服务没启动或挂了
# 检查后端是否在跑
curl http://127.0.0.1:3000/health
# 504 Gateway Timeout → 后端太慢
# 调大超时
proxy_read_timeout 120s;
# 权限问题
sudo chmod -R 755 /var/www/html
sudo chown -R www-data:www-data /var/www/html
# 端口被占用
sudo lsof -i :80

开发时的工作流#

Terminal window
# 1. 写配置
sudo vim /etc/nginx/conf.d/myapp.conf
# 2. 测试
sudo nginx -t
# 3. 生效
sudo nginx -s reload
# 4. 验证
curl -I http://localhost

改配置只需要这三步:编辑 → 测试 → 重载,全程不中断线上服务。

Nginx 从入门到底层原理
https://sgjki547.top/posts/nginx-从入门到底层原理/
Author
SGJki
Published at
2026-06-07
License
CC BY-NC-SA 4.0