目录
master-worker 进程模型 epoll 事件驱动 正向与反向代理 负载均衡算法 动静分离 location 匹配规则
01

master-worker 进程模型

背景 为什么不用多线程?

传统的 Web 服务器如 Apache 使用多线程多进程模型处理连接,每个请求分配一个线程。这种模型在并发量不高时工作良好,但在高并发场景下,线程切换开销和内存占用成为瓶颈。Nginx 采用了master-worker 进程模型:一个 Master 进程负责管理 Worker 进程,多个 Worker 进程负责处理请求,每个 Worker 使用单线程事件驱动的方式处理数千个连接。

第一性原理: master-worker 模型的核心思想是「用进程隔离保证稳定性,用事件驱动保证高并发」。Master 进程不处理请求,只负责管理 Worker 进程(创建、销毁、信号转发),避免 Worker 崩溃影响控制逻辑。Worker 进程之间无共享状态,通过套接字复用 (SO_REUSEPORT) 的方式处理请求,实现了CPU 亲和性高并发。这种设计使得 Nginx 可以在单个进程中处理数以万计的并发连接,同时保持极高的稳定性。

原理 进程结构 · 信号管理 · 平滑重启

Master 进程:

  • 负责读取配置文件、绑定端口、创建 Worker 进程
  • 接收系统信号(如 TERM、HUP、USR1),控制 Worker 行为
  • 监控 Worker 进程状态,异常时重新创建

Worker 进程:

  • 每个 Worker 独立处理连接,互不干扰
  • 使用 epoll 事件驱动模型处理数千个连接
  • Worker 数量通常设置为 CPU 核心数,实现负载均衡
Nginx master-worker 进程模型 Master 进程 PID: 12345 管理 Worker 1 PID: 12346 Worker 2 PID: 12347 Worker 3 PID: 12348 Worker 4 PID: 12349 各 Worker 共享监听套接字 (SO_REUSEPORT) 每个 Worker 独立处理连接,无锁竞争,CPU 亲和性调度 图:Master 管理 Worker,Worker 独立处理请求,高并发且稳定
图:Master 管理 Worker,Worker 独立处理请求,高并发且稳定
▸ Nginx 进程配置示例
# 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

演进 线程模型 → 进程模型 → 事件驱动

  • Apache prefork (1990s): 多进程模型,每个连接一个进程,内存占用大
  • Apache worker (2000s): 多线程模型,线程切换开销大,稳定性差
  • Nginx master-worker (2004): 单进程事件驱动 + 多进程管理,高并发低内存
  • SO_REUSEPORT (Linux 3.9): 允许多个 Worker 共享监听端口,避免锁竞争
"Nginx 的进程模型是对传统 Web 服务器的一次革命性颠覆。它放弃了『一个连接一个进程/线程』的旧模式,转而采用『一个进程处理数千连接』的新模式。这种转变使得 Nginx 能够轻松应对 C10K 问题,成为高性能 Web 服务器的代名词。"
—— Web 服务器设计哲学

取舍 设计中的权衡

⚡ 进程数 vs CPU 核心
Worker 进程数通常设置为 CPU 核心数。过多 Worker 会增加上下文切换开销,过少则无法充分利用 CPU。
🔒 稳定性 vs 内存
多进程模型提高了稳定性,一个 Worker 崩溃不会影响其他 Worker。但每个 Worker 独立内存空间,增加了内存占用。
🔄 平滑重启 vs 兼容性
Master 进程通过信号实现平滑重启,但需要 Worker 正确处理已有的连接。长时间运行的任务可能影响平滑重启的效果。
02

epoll 事件驱动

背景 C10K 问题的解决方案

2000 年代初,Web 服务器面临一个重大挑战:如何同时处理 10,000 个并发连接(C10K 问题)。传统 selectpoll 模型在连接数增多时性能急剧下降。Linux 提供了 epoll 机制,它能够事件驱动地通知连接状态变化,而不是轮询所有连接。

第一性原理: epoll 的本质是「将连接监视从用户态转移到内核态」。应用通过 epoll_ctl 注册感兴趣的事件,然后通过 epoll_wait 等待事件通知。内核只返回活跃连接的事件,避免了每次遍历所有连接的开销。这种「事件驱动」模型使得 Nginx 可以在单线程中管理数万个连接,实现了线性扩展的性能。

原理 epoll 接口 · 水平触发 vs 边缘触发 · 事件循环

epoll 核心接口:

  • epoll_create:创建 epoll 实例
  • epoll_ctl:注册/修改/删除感兴趣的事件
  • epoll_wait:等待事件发生,返回就绪的 fd

触发模式:

  • 水平触发 (LT): 只要 fd 就绪,每次 epoll_wait 都会返回
  • 边缘触发 (ET): 只在 fd 状态变化时返回一次,需要非阻塞 I/O 配合
epoll 事件驱动流程 Worker 进程事件循环 (Event Loop) epoll_wait() 等待事件 事件就绪 返回就绪列表 处理事件 非阻塞 I/O 连接管理 (非阻塞 I/O) connection 1 connection 2 connection 3 ... 数千个连接 图:Worker 进程事件循环,epoll 管理数千连接,非阻塞 I/O 处理
图:Worker 进程事件循环,epoll 管理数千连接,非阻塞 I/O 处理
▸ 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); } } }

演进 select → poll → epoll → io_uring

  • select (1983): 监视 fd 数量受限(默认 1024),每次调用需要重新构建 fd 集合
  • poll (1997): 无数量限制,但每次仍要扫描所有 fd,O(n) 复杂度
  • epoll (2002): 事件驱动,只返回就绪 fd,O(1) 复杂度,适合高并发
  • io_uring (2019): 异步 I/O 框架,共享环形队列,减少系统调用开销
"epoll 是 Nginx 高性能的底层引擎。它彻底改变了 Web 服务器处理连接的方式,从『遍历所有连接』『等待事件通知』,使得单机处理百万级并发成为可能。"
—— I/O 模型设计哲学

取舍 设计中的权衡

⚡ 水平触发 vs 边缘触发
水平触发简单但可能重复通知,边缘触发高效但需要非阻塞 I/O 和完整读取数据。Nginx 默认使用边缘触发以减少系统调用。
🔧 事件驱动 vs 线程池
事件驱动适用于 I/O 密集型场景,但处理 CPU 密集型任务时会阻塞事件循环。Nginx 将耗时的 SSL 操作(如握手)延迟处理,避免阻塞。
📊 单线程 vs 多线程
单线程事件驱动避免了锁和上下文切换,但无法利用多核。Nginx 通过多进程(每个进程单线程)实现并行,兼顾了二者。
03

正向与反向代理

背景 代理在 Web 架构中的角色

代理服务器在客户端和服务器之间充当中介,可以隐藏真实 IP、缓存内容、加速访问、过滤请求。根据代理方向的不同,分为正向代理(代理客户端)和反向代理(代理服务器)。Nginx 主要作为反向代理使用,但也可以配置为正向代理。

第一性原理: 正向代理和反向代理的本质区别在于「谁不知道谁」。正向代理中,服务器不知道客户端身份,代理代表客户端发送请求;反向代理中,客户端不知道服务器身份,代理代表服务器接收请求。这两种模式解决了不同的问题:正向代理用于访问控制和隐私保护,反向代理用于负载均衡和缓存

原理 正向代理 · 反向代理 · 透明代理

正向代理 (Forward Proxy): 客户端配置代理服务器,由代理服务器转发请求到目标服务器。常见用途:突破网络限制、隐藏客户端 IP。

反向代理 (Reverse Proxy): 服务器配置代理,由代理服务器接收客户端请求并转发到后端服务器。常见用途:负载均衡、缓存、安全防护。

正向代理 vs 反向代理 正向代理 客户端 请求 正向代理 请求 目标服务器 服务器不知道客户端身份 反向代理 客户端 请求 反向代理 转发 后端服务器 客户端不知道服务器身份 图:正向代理隐藏客户端,反向代理隐藏服务器
图:正向代理隐藏客户端,反向代理隐藏服务器
▸ Nginx 反向代理配置示例
# 反向代理配置 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 作为正向代理

演进 传统代理 → 反向代理 → API 网关

  • 传统正向代理 (1990s): 用于网络访问控制,如企业防火墙、学校网络
  • 反向代理 (2000s): 用于负载均衡、缓存、安全防护,Nginx 成为主流
  • API 网关 (2010s): 反向代理的进化,提供认证、限流、监控、转换等功能
  • Service Mesh (2018): 将反向代理功能分解为 Sidecar,实现更细粒度的服务控制
"从正向代理到反向代理再到 API 网关,代理技术的演进趋势是『从网络层到应用层』『从透明代理到智能网关』。Nginx 在这一演变中扮演了关键角色,它的可扩展性和高性能使其成为 API 网关的基础。"
—— 代理架构设计哲学

取舍 设计中的权衡

🔒 安全 vs 延迟
反向代理增加一层处理(解析 HTTP 头、转发请求),引入额外延迟。但通过缓存和负载均衡,整体响应时间通常降低。
📊 负载均衡 vs 单点
反向代理作为单点,如果故障会导致整个服务不可用。需要部署多个反向代理实例,配合 DNS 或硬件负载均衡器实现高可用。
⚡ 透明代理 vs 显式代理
透明代理不需要客户端配置,但需要网络层拦截,灵活性差;显式代理需要客户端配置,但提供更多控制功能。
04

负载均衡算法

背景 如何将请求分发到多台服务器?

当后端有多个服务器时,需要一个分发策略来决定每个请求应该转发到哪台服务器。好的负载均衡算法应该在资源利用率响应时间会话保持之间取得平衡。Nginx 提供了多种负载均衡算法,适应不同的业务场景。

第一性原理: 负载均衡算法的本质是「将请求映射到服务器的函数」。最简单的轮询将请求平均分配到所有服务器,但无法处理服务器性能差异;最少连接算法考虑了服务器当前的负载,但需要维护连接计数;IP 哈希算法将同一客户端的请求固定到同一台服务器,实现了会话保持。没有一种算法是完美的,需要根据业务场景选择。

原理 轮询 · 最少连接 · IP 哈希 · 权重 · 一致性哈希

轮询 (Round Robin): 请求依次分配到每台后端服务器,默认算法。适用于服务器性能相近的场景。

加权轮询 (Weighted Round Robin): 为每台服务器分配权重,按权重比例分发请求。适用于服务器性能差异明显的场景。

最少连接 (Least Connections): 将请求分配给当前活动连接最少的服务器。适用于长连接场景。

IP 哈希 (IP Hash): 根据客户端 IP 计算哈希值,将同一 IP 的请求固定到同一台服务器。适用于需要会话保持的场景。

负载均衡算法对比 轮询 (Round Robin) 请求 1 → Server A 请求 2 → Server B 请求 3 → Server C 请求 4 → Server A 最少连接 (Least Connections) Server A: 5 连接 Server B: 2 连接 Server C: 8 连接 新请求 → Server B IP 哈希 (IP Hash) 192.168.1.1 → Server A 192.168.1.2 → Server B 192.168.1.1 → Server A 同一 IP 始终相同 加权轮询示例 (权重: A=3, B=2, C=1) 请求顺序: A → A → A → B → B → C → A → A → A → B → B → C ... 图:不同负载均衡算法的分发策略对比
图:不同负载均衡算法的分发策略对比
▸ Nginx 负载均衡配置
# 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; } }

演进 轮询 → 加权 → 最少连接 → 一致性哈希

  • 轮询 (1990s): 简单均匀分发,适用于性能相同的服务器
  • 加权轮询 (2000s): 考虑服务器性能差异,权重调节
  • 最少连接 (2000s): 动态适应负载变化,适合长连接应用
  • IP 哈希 (2000s): 会话保持,适合需要用户状态的应用
  • 一致性哈希 (2010s): 在分布式缓存中应用,节点增减时最小化重新映射
"负载均衡算法的演进体现了『从静态到动态』『从无状态到有状态』的趋势。从简单的轮询到复杂的一致性哈希,算法的选择取决于应用对公平性响应时间会话连续性的需求。"
—— 负载均衡设计哲学

取舍 设计中的权衡

⚖️ 公平性 vs 性能
轮询保证公平,但无法处理服务器性能差异;最少连接动态适应负载,但连接计数有开销。需要在公平性和效率之间平衡。
🔗 会话保持 vs 负载均衡
IP 哈希实现会话保持但可能导致负载不均衡,尤其当某 IP 请求量特别大时。一致性哈希可以缓解此问题。
📊 动态算法 vs 静态算法
动态算法(如最少连接)适应性好但计算开销大;静态算法(轮询)简单高效但响应能力差。需要根据请求模式选择。
05

动静分离

背景 为什么要分离静态和动态内容?

Web 应用通常包含静态资源(HTML、CSS、JS、图片)和动态资源(API 接口、页面渲染)。静态资源无需计算,直接返回文件;动态资源需要业务逻辑处理。将静态资源与动态资源分离,可以分别优化:静态资源由 Nginx 直接返回,动态资源由后端应用服务器处理。

第一性原理: 动静分离的本质是「计算与传输分离」。静态资源利用 Nginx 的高效文件 I/O缓存能力直接返回,不需要进入应用层;动态资源由应用服务器处理,Nginx 仅作为反向代理。这种分离使得静态资源可以缓存到 CDN,动态资源可以扩展应用服务器,两者的性能瓶颈互不影响。

原理 Nginx 文件服务 · 缓存 · 动静分离配置

Nginx 静态文件服务: 使用 rootalias 指令指定文件目录,Nginx 直接读取文件并返回,性能极高。

动静分离架构 静态资源 (Static) 客户端 请求 Nginx 文件 I/O 磁盘文件 静态文件直接返回,无应用层处理 动态资源 (Dynamic) 客户端 请求 Nginx 转发 后端服务器 动态请求转发到后端处理 图:动静分离让 Nginx 直接服务静态资源,动态请求转发到后端
图:动静分离让 Nginx 直接服务静态资源,动态请求转发到后端
▸ 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; }

演进 单体应用 → 动静分离 → CDN + 微服务

  • 单体应用 (早期): 静态资源和动态代码同在一台服务器,资源竞争严重
  • 动静分离 (2000s): 静态资源由 Web 服务器(Nginx)处理,动态由应用服务器处理,性能提升
  • CDN (2010s): 静态资源部署到 CDN 边缘节点,全球加速
  • 微服务 + API 网关 (2015+): 动态资源微服务化,Nginx 作为网关统一路由
"动静分离是 Web 架构优化的第一步。它简单有效,将 Nginx 的专长发挥到极致。在 CDN 和微服务时代,动静分离仍然是优化 Web 性能的基础策略。"
—— Web 性能优化哲学

取舍 设计中的权衡

📦 文件缓存 vs 实时性
静态资源设置长缓存提高性能,但更新后需要清除缓存。通过版本号或哈希文件名解决缓存更新问题。
⚡ 文件 I/O vs 内存
Nginx 使用 sendfile 系统调用减少数据复制,但大量静态文件请求仍可能增加磁盘 I/O。利用内存缓存(如 open_file_cache)可以减少磁盘访问。
🌐 本地 vs CDN
静态资源部署在本地 Nginx 响应快但带宽有限;CDN 分布式部署带宽大但可能有延迟。大型站点通常结合使用 CDN 和 Nginx 缓存。
06

location 匹配规则

背景 如何精准路由请求?

Nginx 的 location 指令决定了不同 URL 路径的处理方式——是返回静态文件、代理到后端,还是执行其他处理。正确理解 location 的匹配规则和优先级,是 Nginx 配置的基础。

第一性原理: location 匹配的本质是「基于 URI 的路由选择」。Nginx 通过一系列规则(前缀匹配、正则匹配、精确匹配)决定哪个 location 块来处理请求。匹配规则的设计体现了「先精确,后前缀,再正则」的优先级原则,确保最具体的规则被优先执行。

原理 修饰符 · 匹配优先级 · 配置示例

Location 修饰符:

  • =:精确匹配,优先级最高
  • ^~:前缀匹配,匹配后停止正则匹配
  • ~:正则匹配(区分大小写)
  • ~*:正则匹配(不区分大小写)
  • 无修饰符:前缀匹配,继续检查正则
location 匹配优先级流程 1. 精确匹配 = 2. 前缀匹配 ^~ ? (如果匹配,停止搜索) 3. 正则匹配 ~ / ~* ? (按顺序匹配) 4. 普通前缀匹配 (无修饰符) ? (选择最长匹配) 示例: 请求 /images/logo.png location = /images/logo.png → 精确匹配 location ^~ /images/ → 前缀匹配 location ~* \.png$ → 正则匹配 location /images/ → 普通前缀匹配 图:location 匹配优先级,精确 > ^~ > 正则 > 普通前缀
图: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; }

演进 简单前缀 → 正则 → 命名位置

  • 简单前缀 (早期): 仅支持字符串前缀匹配,功能有限
  • 正则匹配 (Nginx 0.7+): 引入正则表达式,支持复杂模式匹配
  • 命名位置 (Named Location): @location 用于内部跳转,不对外暴露
  • 变量匹配 (Nginx 1.9+): 支持在 location 中使用变量和条件判断
"location 匹配规则的演进体现了 Nginx 对灵活性性能的追求。从简单的前缀匹配到正则表达式,再到变量支持,Nginx 提供了越来越强大的路由能力,同时保持了优先级清晰的特性,易于理解和调试。"
—— 路由设计哲学

取舍 设计中的权衡

⚡ 正则 vs 性能
正则匹配功能强大但性能开销较大。对于大量静态资源,建议使用前缀匹配 + 扩展名匹配,减少正则使用。
🔍 优先级 vs 可读性
复杂的 location 嵌套和正则可能让配置难以理解和维护。建议按照精确 → 前缀 → 正则 → 默认的顺序组织配置,提高可读性。
📦 命名位置 vs 内部跳转
命名位置(@location)用于内部重定向,不对外暴露。使用命名位置可以避免在 location 中重复配置,但增加了配置的复杂度。