Nginx 深度解析
Nginx 概述
为什么需要 Nginx
Nginx(读作 “Engine X”)由 Igor Sysoev 于 2004 年发布,初衷是解决 C10K 问题——即单台服务器如何同时处理 10,000 个并发连接。经过近二十年的发展,Nginx 已成为全球最流行的 Web 服务器之一,根据 Netcraft 的统计,全球前 100 万站点中超过 30% 使用 Nginx。
在高并发场景下,Nginx 主要承担以下三种角色:
反向代理(Reverse Proxy):作为客户端与后端服务器之间的中间层,Nginx 接收客户端请求后将请求转发给内部服务器,再把响应返回给客户端。这样做的好处是:隐藏后端服务器细节、统一入口进行安全控制、SSL 卸载等。
负载均衡(Load Balancer):将请求按照特定算法分发到多台后端服务器,避免单点过载,提升系统整体吞吐量和可用性。Nginx 支持多种负载均衡算法,从简单的轮询到基于一致性哈希的会话保持。
静态资源服务(Static File Server):直接处理静态文件(HTML/CSS/JS/图片/视频等)的请求,单机即可达到数万 QPS,无需经过后端应用服务器(Tomcat/Gunicorn/Node.js 等),极大节省计算资源。
一个小型站点的典型架构如下:
1 | 客户端 ---> Nginx (反向代理/静态资源) ---> 上游服务器集群 (应用服务器) |
Nginx vs Apache
Apache 自 1995 年诞生以来,一直是互联网基础设施的中坚力量。它采用 进程驱动(Process-Driven) 模型,主要有两种 MPM:
- prefork 模式:每个连接一个独立进程,稳定但内存开销极大,面对数千并发连接时内存耗尽。
- worker 模式:每个连接一个线程,内存消耗有所改善,但线程切换开销随并发数线性增长。
Nginx 采用 事件驱动(Event-Driven) 模型,使用少量的 worker 进程,每个进程通过异步非阻塞 I/O 同时处理数万个连接。以下是对比:
| 特性 | Nginx | Apache |
|---|---|---|
| 并发模型 | 异步非阻塞,事件驱动 | 进程/线程,阻塞 I/O |
| 内存消耗 | 极低,数 MB 即可服务数千连接 | prefork 模式下每个进程数 MB |
| 静态文件性能 | 极高(直接 sendfile) | 一般 |
| 动态内容处理 | 需反向代理到后端 | 可通过 mod_php/mod_perl 直接处理 |
| 配置灵活性 | 简洁,逻辑清晰 | 支持 .htaccess 目录级配置 |
| 模块系统 | 编译时静态链接 | 可动态加载 |
| SSL 性能 | 优秀 | 一般 |
适用场景选择:
- 静态内容、反向代理、高并发 → Nginx
- 共享主机(需要 .htaccess)、内置 PHP 处理 → Apache
- 复杂 URL 重写、需要大量第三方模块 → Apache 更灵活
- 混合部署:Nginx 前置处理静态资源 + 反向代理,Apache 处理动态逻辑
事件驱动架构详解
Nginx 的核心架构可以概括为 master-worker 模型 + 异步非阻塞 I/O:
1 | +-----------+ |
Master 进程负责:
- 读取并验证配置文件(
nginx -t) - 创建、绑定并监听共享 socket
- 启动、停止、热重载 worker 进程
- 处理信号(
SIGHUP热重载,SIGUSR1日志重开,SIGQUIT优雅退出)
Worker 进程负责:
- 实际处理客户端连接和请求
- 执行事件循环(epoll/kqueue)
- 读取、解析请求,生成响应
- 与上游服务器通信(作为反向代理时)
- 缓存读写
所有 worker 进程共享监听同一个 socket,由内核通过带锁的 accept 机制分发连接。每个 worker 是单线程的,内部使用异步非阻塞模型处理所有 I/O 操作——这正是 Nginx 能支撑数万并发连接的秘密。
为什么单线程能处理这么多连接?核心在于:一个线程不需要阻塞等待任何一个 I/O 操作。当读操作无法立即完成时,Nginx 不会挂起线程,而是将这个连接的读事件注册到 epoll 中继续处理下一个连接。待数据就绪后,epoll 通知该线程回来继续处理。这种模式将 CPU 利用率大幅提升,避免了线程切换和内核态拷贝的开销。
核心架构
Master 进程
Master 进程本身不处理任何客户端请求,它的全部工作围绕”管理”展开。以下是一个典型的 Nginx master 进程(PID = 1234)的信号处理逻辑:
SIGHUP — 热重载配置:
Master 收到 SIGHUP 后,会重新解析配置文件,创建新的一批 worker 进程,然后通知旧 worker 进程优雅退出——即让旧 worker 处理完当前请求后关闭。整个过程不丢失连接,实现”无中断重启”。
1 | # 热重载 Nginx 配置 |
SIGUSR1 — 重开日志:
日志切割时使用。logrotate 轮转日志文件后,需要通知 Nginx 重新打开日志文件。
1 | nginx -s reopen |
SIGQUIT — 优雅关闭:
不再接收新连接,等待现有连接处理完毕后退出。
1 | nginx -s quit |
SIGTERM / SIGINT — 快速关闭:
立即关闭所有连接并退出。
Master 进程的一个重要细节是 CPU 亲和性(affinity):通过 worker_cpu_affinity 指令,可以将每个 worker 进程绑定到特定 CPU 核心,减少 CPU 缓存失效,进一步提升性能。
1 | # 4 核 CPU,将 4 个 worker 分别绑定到 core 0~3 |
Worker 进程与 epoll 事件循环
每个 worker 进程的核心是事件循环(Event Loop),这是 Nginx 高性能的基石。简化的事件循环逻辑如下:
1 | while (true) { |
epoll 的工作原理:
传统的 select/poll 每次调用都需要将整个 fd 集合从用户态拷贝到内核态,且内核采用 O(n) 的轮询方式检查就绪 fd。当并发连接达到数万时,select/poll 的性能急剧下降。
epoll 则完全不同:
- epoll_create:在内核中创建 eventpoll 对象,维护一棵红黑树和就绪链表。
- epoll_ctl:将 fd 注册到红黑树中,只需一次拷贝。
- epoll_wait:直接返回就绪链表中的 fd,时间复杂度 O(1)。
这就是 Nginx 选择 epoll(Linux)/ kqueue(FreeBSD)/ IOCP(Windows)等高性能 I/O 多路复用机制的原因。
惊群效应(Thundering Herd)与 accept_mutex:
当新的 TCP 连接到达时,所有阻塞在 epoll_wait 上的 worker 进程都会被内核唤醒,但实际上只有一个 worker 能成功 accept 这个连接。其余被唤醒的 worker 发现无事可做就会重新睡眠——这种现象称为”惊群效应”,会浪费 CPU 资源。
Nginx 的解决方案是 accept_mutex(接受锁):在调用 accept 之前,worker 必须先获取这把互斥锁。只有拿到锁的 worker 才能接受新连接,其他 worker 继续等待自己的事件。
1 | events { |
Linux 内核 4.5+ 提供了 SO_REUSEPORT socket 选项,允许每个 worker 绑定到独立的 listen socket。当新连接到达时,内核通过哈希算法将其分配到特定的 socket 上,从根本上解决惊群问题。但 Nginx 早期就已引入 accept_mutex,因此 SO_REUSEPORT 的支持是相对晚近的事。
模块化设计
Nginx 的模块系统是其灵活性的来源。模块化设计严格遵循分层架构,每个请求按顺序经过一系列模块处理。核心模块类型包括:
Handler 模块(处理模块):
负责处理特定类型的请求并生成响应。例如:
ngx_http_static_module:处理静态文件请求ngx_http_proxy_module:将请求转发到上游服务器ngx_http_fastcgi_module:与 FastCGI 后端通信ngx_http_uwsgi_module:与 uWSGI 后端通信ngx_http_grpc_module:与 gRPC 后端通信
Filter 模块(过滤模块):
对 Handler 生成的响应进行后处理。Filter 以链式方式组织——响应数据像穿洋葱一样顺序经过各层 filter:
ngx_http_gzip_filter_module:Gzip 压缩ngx_http_range_header_filter_module:Range 请求处理ngx_http_charset_filter_module:字符集转换ngx_http_headers_filter_module:响应头修改
Upstream 模块(上游模块):
管理与上游(后端)服务器的连接池和负载均衡,是实现 proxy_pass/fastcgi_pass 等指令的基础。
Load-Balancer 模块(负载均衡模块):
实现具体的负载均衡算法,如轮询、IP 哈希、最小连接数等。用户可通过 upstream 块配置。
请求处理流程中的模块调用顺序(Nginx 的 11 个处理阶段):
1 | POST_READ → SERVER_REWRITE → FIND_CONFIG → REWRITE → POST_REWRITE |
理解这四个模块的分工后,再看一段简单的配置就能明白其背后各个模块的工作:
1 | location /api/ { |
proxy_pass触发了ngx_http_proxy_module(Handler 模块)proxy_set_header在此 Handler 内部使用gzip on触发了ngx_http_gzip_filter_module(Filter 模块),响应在发送前被压缩
事件驱动模型与高性能原理
回到核心问题:Nginx 为什么快?
进程模型轻量:固定数量的 worker 进程(通常等于 CPU 核心数),不会随并发连接增长而创建新进程/线程。
异步非阻塞 I/O:所有 I/O 操作(网络读写、文件读写、代理转发)全部非阻塞,线程不会在任何 I/O 操作上挂起等待。
内存池(Memory Pool):Nginx 为每个连接/请求分配独立的内存池,结束时整体释放。这种设计避免频繁的 malloc/free 操作,同时消除内存泄漏隐患。
零拷贝(Zero-Copy):通过 Linux
sendfile系统调用,数据从磁盘到网络不经用户态拷贝,从内核缓存直接发送到 socket。事件驱动架构:基于 epoll/kqueue 等高性能 I/O 多路复用器,事件通知从源头避免无效轮询。
模块化与可扩展性:过滤器和处理器解耦,各司其职,新功能通过新模块加入,不影响核心流程。
极致优化的代码:C 语言编写,处处考虑性能。例如 HTTP 解析器使用自动机而非正则表达式;数据结构(红黑树/基数树/队列)全部内联实现。
反向代理
proxy_pass 原理与配置
反向代理是 Nginx 最常见的应用场景之一。proxy_pass 指令将请求转发到指定的上游服务器——Nginx 先接收客户端请求,建立与后端的连接,转发请求,接收后端响应,再返回给客户端。
1 | location /api/ { |
路径替换规则(这是配置中最容易出错的地方):
proxy_pass 的 URI 部分如果是 / 结尾,则 location 匹配到的前缀部分会被替换为 proxy_pass 的 URI。
1 | # location 前缀: /api/ |
请求转发流程:
1 | 客户端 ──→ [Nginx Worker] |
proxy_set_header 头部管理
请求经过反向代理后,后端服务器看到的连接信息会发生变化——源 IP 变为了 Nginx 的 IP。若需要获取客户端真实信息,必须显式传递这些头:
1 | location /api/ { |
X-Real-IP vs X-Forwarded-For:
X-Real-IP:只包含直接客户端的 IP(即 Nginx 看到的远端 IP),常被简单场景使用。X-Forwarded-For:记录了完整的代理链 IP。格式为client, proxy1, proxy2, ...。通过$proxy_add_x_forwarded_for变量,Nginx 会将当前客户端 IP 追加到已有值的末尾。
当多个代理串联时,X-Forwarded-For 能完整追踪一个请求穿越了哪些代理节点,这对安全审计和问题排查极为重要。
proxy_buffering 缓冲机制
反向代理中,缓冲机制直接影响响应性能和用户体验。Nginx 作为中间层,两端网络速度可能不对等——例如后端很快但客户端很慢。缓冲能解耦这二者,让后端将完整响应快速交给 Nginx 后去处理下一个请求,Nginx 再按客户端速度慢慢发送。
缓冲开启(默认):
1 | proxy_buffering on; # 开启缓冲(默认) |
缓冲工作方式:
- Nginx 从后端接收响应,首先填充
proxy_buffer_size指定的头缓冲区。 - 响应体则存储在
proxy_buffers定义的缓冲池(8×64k = 512k)中。 - 若响应体超过缓冲池总大小,多余部分写入临时文件(
proxy_temp_path)。 - 客户端读取时,Nginx 从缓冲中取出数据发送。只要缓冲未满,后端就可继续向 Nginx 发送数据。
关闭缓冲(适用于流式/实时场景):
1 | location /stream/ { |
关闭缓冲意味着 Nginx 收到后端的每个数据块立即同步发送给客户端,适用于:
- Server-Sent Events (SSE)
- 大文件下载(避免耗尽 Nginx 内存)
- 实时日志流
- 长轮询(Long Polling)
缓冲大小调优建议:
| 场景 | proxy_buffer_size | proxy_buffers | 说明 |
|---|---|---|---|
| 一般 API 响应 | 4k | 8 16k | 响应小,少量缓冲即可 |
| 中等页面响应 | 8k | 16 32k | 页面数十 KB |
| 大文件下载 | 64k | 32 128k | 但建议关闭缓冲 |
| 请关闭缓冲 | — | — | 流式、SSE、长轮询 |
代理超时配置
反向代理必须设置合理的超时时间。默认超时设置比较保守(60 秒),生产环境中需根据场景精细调控。
1 | location /api/ { |
重要:proxy_read_timeout 是连续两次成功读操作之间的最大间隔。如果后端持续稳定地发送数据(即便很慢),此超时也不会触发。若连接不是长时处理的,建议设为 30 秒以内,以便快速释放资源。
WebSocket 代理
WebSocket 通过 HTTP 升级握手后保持长连接进行双向通信。Nginx 必须显式配置支持 WebSocket 协议升级:
1 | location /ws/ { |
踩坑提示:如果返回 101 Switching Protocols 之后的 WebSocket 连接秒断,90% 的情况是:
- 未配置
proxy_http_version 1.1(默认 HTTP/1.0) - 未配置
proxy_set_header Connection "upgrade" proxy_read_timeout过短导到长连接被关闭
gRPC 代理
gRPC 基于 HTTP/2,要求端到端的 HTTP/2 支持。Nginx 提供了专门的 grpc_pass 指令:
1 | server { |
gRPC 代理同样支持负载均衡、健康检查等功能,upstream 块中使用 grpc:// 协议前缀即可。
负载均衡
四层 vs 七层负载均衡
理解负载均衡在 OSI 模型中的工作层次至关重要:
四层负载均衡(传输层):工作在 TCP/UDP 层,根据 IP 地址和端口进行转发。Nginx 的 stream 模块提供四层负载均衡能力。
1 | stream { |
七层负载均衡(应用层):工作在 HTTP 层,可以解析 HTTP 协议内容,根据 URL、Header、Cookie 等做精细化路由,支持会话保持、缓存、压缩等高级功能。
| 对比维度 | 四层 (stream) | 七层 (http) |
|---|---|---|
| 协议支持 | TCP/UDP 通用 | HTTP/HTTPS/HTTP2/gRPC |
| 精细路由 | 不支持 | 支持根据 URL/Header 等 |
| 性能 | 极快(不进应用层) | 略慢(需解析 HTTP) |
| 会话保持 | 基于源 IP | Cookie/Header 等方式 |
| 缓存/压缩 | 不支持 | 支持 |
| SSL 终结 | 透明转发 | 可终结 |
| 适用场景 | 数据库/MQ/非 HTTP 应用 | Web 应用 |
实际架构中通常同时用两层:四层(LVS/HAProxy)在最外层做 TCP 级别的高性能和简单分发,七层(Nginx/Envoy)在内层做细粒度的 HTTP 路由。
负载均衡算法
Nginx 的 upstream 块定义一组后端服务器,并可通过不同算法指定请求分发策略:
1 | upstream backend { |
轮询(Round Robin,默认):
请求按顺序轮流分发给每台上游服务器,配合 weight 实现加权轮询。
1 | # 加权轮询:每 4 个请求中,3 个到 server1,1 个到 server2 |
ip_hash:
根据客户端 IP 的哈希值分配服务器。同一 IP 的请求始终打到同一台后端。适用于需要会话保持的场景,但不适合有大量 NAT 或代理的环境。
1 | upstream backend { |
least_conn(最少连接数):
将请求发送到当前活跃连接数最少的服务器。适用于长连接或处理时长不均匀的场景。
1 | upstream backend { |
hash(哈希):
根据自定义键的哈希值选择服务器,常用于基于 URL 或请求参数的一致性哈希路由。添加 consistent 后使用一致性哈希环,服务器增删时仅重定向部分请求,适合缓存场景。
1 | upstream backend { |
random(随机):random 模块提供随机算法,支持 two(随机选两台,选连接数少的)等方法。
1 | upstream backend { |
upstream 健康检查
健康检查机制保障只有健康的后端才接收流量。Nginx 提供被动和主动两种方式。
被动健康检查(默认,开源版内置):
1 | upstream backend { |
max_fails:在fail_timeout时间内允许的最大失败次数。fail_timeout:服务器被标记为不可用后,在此时间段内不再尝试向其转发请求。超过此时间后,Nginx 仁慈地重新尝试一次——若成功就恢复标记为可用。- 默认值:
max_fails=1,fail_timeout=10s。
主动健康检查(需 nginx_upstream_check_module 模块):
商业版 Nginx Plus 或通过 nginx_upstream_check_module 补丁编译的社区版支持主动探测。配置示例如下:
1 | upstream backend { |
interval:探测间隔(毫秒)rise:连续成功多少次后标记为可用fall:连续失败多少次后标记为不可用timeout:探测超时type:探测协议类型(http/tcp/ssl_hello/mysql/ajp)
通用健康检查接口建议:后端应用应暴露独立的 /health 或 /healthz 端点,该接口仅检查应用本身是否正常工作(不依赖外部服务),避免因依赖服务故障导致健康检查失败引发的雪崩。
会话保持
在多服务器集群中,若应用有状态(如 Session),需确保同一用户的请求路由到同一台服务器。
ip_hash(最简单):
1 | upstream backend { |
ip_hash 的局限性:NAT 网络下大量用户共享同一出口 IP,导致流量不均;客户端 IP 变化(如移动网络切换)会导致会话丢失。
sticky cookie(Nginx Plus / nginx-sticky-module):
1 | upstream backend { |
工作原理:第一个请求到达时 Nginx 分配一个后端,在响应中添加 Set-Cookie: srv_id=...。后续请求携带此 Cookie,Nginx 据此路由到同一后端。这种方法解决了 ip_hash 的问题。
通用做法:将会话状态抽取到共享存储(Redis/Memcached)或使用无状态 Token(JWT),从根本上解决会话保持的需求。
动态 upstream
传统的 upstream 变更需要修改配置文件 + nginx -s reload,这条命令实际上执行的是优雅重启——启动新 worker,关闭旧 worker。对于大规模集群,频繁重载会增加运维负担。
ngx_http_dyups_module 是阿里开源的动态 upstream 模块,允许通过 HTTP API 动态添加/删除上游服务器:
1 | # dyups 模块配置示例 |
通过 HTTP 接口管理:
1 | # 查看所有 upstream |
现代替代方案:在 Kubernetes 等容器编排环境中,upstream 的动态变更可由 Ingress Controller + Service 自动处理,无需在 Nginx 层面手动管理。
静态资源与缓存
静态文件服务
Nginx 配合操作系统零拷贝机制,能以极低开销处理静态文件。
1 | server { |
sendfile / tcp_nopush / tcp_nodelay 是静态文件高性能传输的三板斧:
1 | http { |
三者配合的逻辑:
- sendfile:用户态零拷贝,直接内核对内核传输。
- tcp_nopush:当使用 sendfile 发送文件时,积累数据直到达到 MSS(最大报文段大小)再发送,减少网络包数量。
- tcp_nodelay:对 keepalive 的读-写往返禁用 Nagle 算法,适用于需要低延迟的小数据交互。
浏览器缓存
通过 expires 指令设置缓存失效时间,Nginx 自动生成 Cache-Control 头。对版本号或哈希管理的静态资源(如 Webpack 的带哈希文件名),可设置极长缓存时间。
1 | location /assets/ { |
ETag:Nginx 默认生成 ETag(基于文件最后修改时间和内容长度)。对于多服务器集群,各机器返回的 ETag 可能不同(inode 等因素),可关闭 ETag 统一使用 Last-Modified:
1 | location / { |
代理缓存
代理缓存是反向代理最强大的功能之一——Nginx 缓存上游服务器的响应,直接返回缓存的副本,绕过后端请求。
1 | http { |
$upstream_cache_status 可能的值:
MISS:缓存未命中(第一次请求,或缓存过期)HIT:缓存命中EXPIRED:缓存过期,正向源验证(返回 304 则复用旧缓存)STALE:后端不可用,返回了过期缓存(需开启proxy_cache_use_stale)BYPASS:请求被proxy_cache_bypass条件跳过REVALIDATED:源返回 304 Not Modified,旧缓存复用UPDATING:旧缓存正被更新
缓存失效策略:
1 | # 后端出错时返回过期缓存(确保可用性) |
缓存清理:Nginx 本身不带缓存内容删除功能。商业化 Nginx Plus 有 purge API;开源版可以搭配 ngx_cache_purge 模块实现:
1 | location ~ /purge(/.*) { |
Gzip 压缩
Gzip 可将文本传输量降至原来的 20%~30%,代价是少量 CPU 开销。生产环境推荐配置:
1 | http { |
gzip_static:当磁盘上存在预压缩的 .gz 文件时,直接发送该文件而无需实时压缩。使用 Webpack/Nginx 等构建工具预生成 gzip 文件,发送时完全省去压缩的 CPU 消耗。
1 | location /assets/ { |
HTTPS 配置
SSL/TLS 配置
生产环境的 HTTPS 配置需兼顾安全性与兼容性。以下是现代安全的推荐配置:
1 | server { |
生成 DH 参数文件:
1 | openssl dhparam -out /etc/nginx/certs/dhparam.pem 2048 |
HTTP/2 配置
HTTP/2 通过二进制分帧、多路复用、服务器推送等特性大幅提升 Web 性能。在 Nginx 中开启 HTTP/2 只需在 listen 指令后添加 http2:
1 | server { |
使用 HTTP/2 Server Push 的另一种方式是通过后端在响应头中添加 Link 头,Nginx 配合 http2_push_preload on 自动识别并推送:
1 | Link: </css/main.css>; rel=preload; as=style |
注意:Chrome 106+ 已弃用 HTTP/2 Server Push。推荐使用
<link rel="preload">替代。HTTP/2 最大的价值在于多路复用和头部压缩,Push 已非核心特性。
HSTS 配置
HTTP Strict Transport Security (HSTS) 告知浏览器只能通过 HTTPS 访问该域名,杜绝 SSL 剥离攻击:
1 | server { |
同时配置 HTTP→HTTPS 强制跳转:
1 | server { |
SSL 会话缓存
SSL/TLS 握手涉及非对称加密运算,消耗较大。SSL 会话缓存可将握手参数复用,将后续握手的开销降低近 90%。
1 | http { |
性能优化
worker 配置
Nginx worker 的关键参数直接影响并发处理能力:
1 | # 自动等于 CPU 核心数 |
最大并发连接数计算公式:
1 | max_clients = worker_processes × worker_connections |
以 8 核 CPU、worker_connections 4096 为例:
- 作为反向代理:
max_clients = 8 × 4096 / 2 = 16384(因每个客户端连接同时占用一个后端连接) - 作为静态服务器:
max_clients = 8 × 4096 = 32768
worker_connections 设置多少合适?
不是越大越好。每个连接都会申请一个约 128256 字节的接收缓冲区。100,000 个并发连接需要约 2025 MB 的内存,十万级连接内存还远未到瓶颈,但要注意系统级别的限制:
1 | # 查看当前系统限制 |
keepalive 优化
HTTP keepalive 允许单个 TCP 连接复用多个 HTTP 请求,避免频繁握手的开销:
1 | http { |
upstream keepalive 的重要性:反向代理场景下,客户端到 Nginx 和 Nginx 到后端都有连接管理的开销。开启 upstream keepalive 后,Nginx 与后端之间的连接得以复用,省去了每次代理请求都建立新连接的开销(TCP 三次握手、TLS 握手等)。
内核参数优化
系统内核参数瓶颈通常在十万级并发时显现。调整 Linux 内核参数:
1 | # /etc/sysctl.conf 或 /etc/sysctl.d/99-nginx.conf |
应用配置:
1 | sysctl -p /etc/sysctl.d/99-nginx.conf |
日志优化
作为反向代理,Nginx 的访问日志是排查问题的重要依据,但同时也是性能损耗的来源。优化策略:
日志缓冲:批量写入而非每条单写。
1 | http { |
日志压缩:
1 | # Nginx 1.7.6+ 支持直接输出 gzip 压缩日志 |
条件日志:过滤不需要记录的请求(如健康检查探测、静态资源 200),避免日志膨胀:
1 | map $request_uri $loggable { |
异步日志(syslog):将日志发送到 syslog,由专用日志服务异步处理:
1 | access_log syslog:server=unix:/dev/log combined; |
常用配置示例
静态文件服务器
1 | server { |
反向代理 + 负载均衡
1 | upstream app_servers { |
HTTPS 站点配置(完整)
1 | server { |
限流配置
Nginx 提供两种限流机制:请求速率限流(limit_req)和并发连接数限流(limit_conn)。
limit_req(请求速率限制):
1 | http { |
limit_conn(并发连接数限制):
1 | http { |
使用 $binary_remote_addr 而非 $remote_addr 的理由:IP 地址 “192.168.1.1” 作为字符串存储需 12~15 个字节,而二进制的 $binary_remote_addr 仅需 4 个字节(IPv4)或 16 个字节(IPv6),内存占用大幅降低。
CORS 配置
跨域资源共享(CORS)是现代 Web 开发的必备配置:
1 | server { |
注意:当使用 Access-Control-Allow-Credentials true 时,Access-Control-Allow-Origin 不能设为 *,必须指定具体域名。
防盗链
防止其他网站直接引用本站图片/资源(盗链),节省带宽:
1 | location ~* \.(png|jpg|jpeg|gif|svg|mp4|webm)$ { |
重定向
return(简单重定向):
1 | # 301 永久重定向 |
rewrite(复杂重写):
1 | # URL 重写为 SEO 友好的格式 |
return vs rewrite 的选择:return 比 rewrite 效率更高,因为 rewrite 在 Server 和 Location 之间会引起额外的匹配循环。只要简单重定向就应使用 return。
OpenResty 简介
OpenResty vs Nginx
OpenResty 是基于 Nginx 核心和 LuaJIT 构建的高性能 Web 平台,由章亦春(agentzh)创建。它与原生 Nginx 的关系像浏览器与操作系统的关系——Nginx 提供了底层事件驱动引擎,OpenResty 提供了丰富的即用层。本质上是 Nginx 的超级增强版。
1 | # 原生 Nginx 架构 |
核心区别:
| 特性 | Nginx | OpenResty |
|---|---|---|
| 编程模型 | 纯配置驱动 | Lua 脚本 + 配置 |
| 动态路由 | 有限(Rewrite 模块) | 完全动态(Lua 访问数据库/Redis) |
| 扩展方式 | C 模块(编译时链接) | Lua 脚本(运行时加载) |
| 学习曲线 | 中等 | 较高 |
| 性能 | 极高 | 接近原生(LuaJIT 优化) |
| 应用场景 | 反向代理/静态服务 | API 网关/WAF/动态路由 |
Lua 脚本与请求阶段
OpenResty 将 Lua 脚本注入了 Nginx 请求处理的每个阶段,极其灵活:
1 | server { |
与 Nginx 11 个阶段的对应:
| Nginx 阶段 | OpenResty 指令 | 典型用途 |
|---|---|---|
| POST_READ | set_by_lua |
变量初始化 |
| REWRITE | rewrite_by_lua |
URL 重写、重定向 |
| ACCESS | access_by_lua |
鉴权、限流、IP 黑白名单 |
| CONTENT | content_by_lua |
完全动态生成响应 |
| LOG | log_by_lua |
自定义日志、审计上报 |
应用场景
动态路由:根据请求参数、User-Agent 等动态选择后端:
1 | upstream web_backend { server 10.0.0.1:8080; } |
API 网关:OpenResty(尤其是基于它的 Kong、APISIX 等产品)是构建 API 网关的主流选择,支持认证、限流、日志、监控、AB 测试等功能的一站式处理。
WAF(Web 应用防火墙):如著名的 ngx_lua_waf——使用 Lua 规则在 Nginx 层面拦截 SQL 注入、XSS、文件包含等攻击。
Nginx 与后端架构
Nginx 在微服务中的角色
在微服务架构中,Nginx 通常扮演 API 网关和入站负载均衡两重角色:
1 | Internet |
入口 Nginx 的职责:
- 全局路由:按域名/路径将请求分发到不同服务
- SSL 终结:TLS 握手在入口层完成,内部走 HTTP
- 统一鉴权:在入口层统一校验 Token/Session
- 全局限流:全局速率限制,保护下游
- 请求/响应日志统一收集
内部 Nginx 的职责:
- 服务发现集成(
resolver+ DNS) - 实例级负载均衡
- 蓝绿/灰度发布
Nginx + Keepalived 高可用
单台 Nginx 存在单点故障风险。通过 Keepalived 实现双机热备,对外暴露同一个 Virtual IP(VIP):
1 | VIP: 192.168.1.100 |
Keepalived 配置示例(/etc/keepalived/keepalived.conf):
1 | vrrp_instance VI_1 { |
典型问题与处理:Keepalived 主备切换时存在短暂(1~3 秒)的流量中断,对于金融/支付场景,使用云厂商的弹性负载均衡(ELB/ALB/CLB)能获得更高的可用性保证。
Nginx vs LVS / HAProxy / Envoy
| 维度 | Nginx | LVS | HAProxy | Envoy |
|---|---|---|---|---|
| 工作层次 | 4/7 层 | 4 层 | 4/7 层 | 4/7 层 |
| 反向代理 | 是、核心功能 | 否 | 是 | 是 |
| 静态服务 | 是 | 否 | 否 | 否 |
| 负载均衡算法 | 5+ 种 | 10 种 | 10+ 种 | 多种 |
| 性能基准 | 极高(~50k 并发/MB 内存) | 最高 | 非常高 | 高 |
| HTTP/2 | Nginx 原生支持 | 否 | HAProxy 2+ | 原生支持 |
| gRPC | 支持 | 不支持 | 支持 | 原生支持(基于 HTTP/2) |
| 动态配置 | 有限 | 有限 | Runtime API | xDS 全动态 |
| 可观测性 | 日志文件 | 有限 | Prometheus | 开箱即用 |
| 云原生 | 一般 | 不支持 | 部分支持 | 完全支持 |
| 适合场景 | Web 应用 | 极简 TCP/UDP 转发 | TCP 高并发 | 微服务、Service Mesh |
选择建议:
- 传统 Web 应用、反向代理、静态文件 → Nginx
- 最简单高效的四层转发、数据库负载均衡 → LVS
- TCP 代理、高并发四层 + 七层 → HAProxy
- 微服务、Kubernetes、Service Mesh、全动态配置 → Envoy
常见问题排查
502 Bad Gateway
原因:Nginx 从后端收到了无效响应(例如后端返回的响应不合法、连接已断开、响应头格式错误)。
常见场景及排查:
- 后端服务已挂:检查后端进程是否运行。
1 | curl -v http://backend_host:port/health |
- 后端超时:Nginx 的
proxy_read_timeout小于后端的处理时间。
1 | proxy_read_timeout 60s; # 调大该值 |
负载过高:后端请求处理不过来,连接队列满,连接被拒绝。
后端错误:PHP-FPM / Gunicorn / Node.js 进程崩溃,内部报错返回了未预期的数据。
header 过大:后端的响应头超过了
proxy_buffer_size。
1 | proxy_buffer_size 16k; # 增大响应头缓冲区 |
快速诊断:
1 | # 查看 Nginx 错误日志(80% 的问题都能在此找到线索) |
504 Gateway Timeout
原因:Nginx 在 proxy_read_timeout 时间内未从后端收到完整响应。
常见场景:
- 后端处理超时(长任务、慢 SQL、外部 API 调用)
- 后端进程被阻塞(线程池满、死锁)
- 网络问题(Nginx 与后端的网络不稳定)
解决方案:
1 | # 按业务场景合理设置超时 |
根本方法:504 通常是应用层问题。优先检查后端服务日志(慢查询、GC 停顿、线程池满等),而非无限增大超时。
413 Request Entity Too Large
上传文件超过 Nginx 请求体大小限制。
1 | client_max_body_size 50m; # 允许 50MB 的请求体 |
upstream 节点反复上线/下线
日志中上游节点反复出现 connect() failed (111: Connection refused) 并自动切换到其他节点——这通常是 max_fails 过于激进、或者后端响应慢触发了被动健康检查。
解决:
1 | upstream backend { |
排查:被动健康检查非常容易被个别慢请求触发,表现为一台后端反复被标记为 FAIL。监控后端响应时间分布(P99),设置合理的超时配合
proxy_next_upstream和proxy_next_upstream_tries。
总结:
Nginx 由一款轻量 Web 服务器跃升为现代互联网基础设施的核心组件。其事件驱动架构、模块化设计和极其稳定的性能,使其在”单机十万并发”成为现实。掌握 Nginx 不仅意味着能写出可用的配置——更意味着理解了网络协议在高并发下的工作原理,懂得如何设计稳定、可扩展的后端架构。
对于后端工程师而言,Nginx 绝不是锦上添花的运维工具——它是理解现代后端系统中流量入口、请求路由、负载分发这些关键概念的必修课。将 Nginx 用好、用透,是在高流量、高可用系统设计中少走弯路的基础功。