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 网关的区别
| 能力 | Nginx | API 网关 |
|---|---|---|
| 路由/代理 | ✅ | ✅ |
| 负载均衡 | ✅ | ✅ |
| 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 快的底层原因总结
| 层级 | 技术 | 效果 |
|---|---|---|
| 系统调用 | epoll | O(1) 事件通知,不遍历 |
| I/O 模型 | 非阻塞 + 异步 | 一个线程处理几万连接 |
| 数据传输 | sendfile 零拷贝 | 静态文件少 2 次内存拷贝 |
| 内存管理 | 内存池 | O(1) 分配,无碎片,无泄漏 |
| 进程模型 | 多 Worker 无锁 | 无锁竞争,CPU 亲和 |
| 架构设计 | 11 阶段流水线 | 模块解耦,灵活可扩展 |
本质上 Nginx 就是把操作系统提供的高性能原语(epoll、sendfile、共享内存)用到了极致,再加上精心的内存管理和无锁架构。
安装与配置
安装方式
# Ubuntu/Debiansudo apt update && sudo apt install nginx
# CentOS/RHELsudo yum install nginx
# macOSbrew install nginx
# Docker(推荐开发用)docker run -d -p 80:80 --name nginx nginx:latest安装后:
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 自动跳转 HTTPSserver { listen 80; server_name example.com; return 301 https://$host$request_uri;}用 Let’s Encrypt 免费证书:
sudo apt install certbot python3-certbot-nginxsudo 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"; }}常用运维命令
# 测试配置是否正确(改完配置必须先测)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常见问题排查
# 配置测试报错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/htmlsudo chown -R www-data:www-data /var/www/html
# 端口被占用sudo lsof -i :80开发时的工作流
# 1. 写配置sudo vim /etc/nginx/conf.d/myapp.conf
# 2. 测试sudo nginx -t
# 3. 生效sudo nginx -s reload
# 4. 验证curl -I http://localhost改配置只需要这三步:编辑 → 测试 → 重载,全程不中断线上服务。