一、Nginx 整体架构概览
1. Nginx简介
Nginx 是采用 C 语言 编写的高性能 Web 服务器、反向代理服务器及邮件代理服务器,特点是:高并发、高可用、低内存占用、模块化设计。
架构核心理念:
Master-Worker 多进程模型
事件驱动(Event-Driven) + 异步非阻塞
高度模块化设计
2. 进程模型
Nginx 的进程模型非常轻量,通常包含:
1. Master 进程
启动时由 shell 进程 fork 出来
主要负责:
读取配置文件
管理 Worker 子进程(启动/重启/关闭)
平滑升级(不间断服务)
2. Worker 进程
实际处理请求的进程
默认情况下每个 CPU 一个 worker(可配置)
每个 worker 独立工作,通过 epoll 等机制异步处理连接
3. Cache Manager/Loader(可选)
用于管理磁盘缓存(如 proxy_cache)
管理缓存的过期与空间
Worker 之间互不通信,避免锁竞争,共享资源通常通过共享内存实现。
3. 事件驱动与异步非阻塞机制
Nginx 使用Reactor 模型 + 非阻塞 IO,事件模型封装成 ngx_event
模块,底层可选择:
Linux:
epoll
BSD:
kqueue
Windows:
IOCP
Others:
select/poll
每个 worker 维护一个事件循环(event loop),处理以下类型事件:
新连接事件(accept)
读写事件(read/write)
定时器事件(超时)
信号事件
示例事件循环伪代码:
while (true) {
events = epoll_wait(epoll_fd, timeout);
for (event in events) {
if (event == read) {
handle_read();
} else if (event == write) {
handle_write();
}
}
process_timers();
}
4. 模块化设计
Nginx 模块系统非常强大,主程序仅提供框架,几乎所有功能都通过模块扩展。
模块分类
类型 | 说明 |
---|---|
核心模块 | 与配置、事件循环、进程管理相关 |
标准模块 | 官方提供,如 http , gzip , rewrite |
第三方模块 | 社区开发,如 lua-nginx-module , ngx_brotli |
事件模块 | 封装不同操作系统的 IO 模型(epoll/kqueue 等) |
过滤模块 | 对请求或响应进行过滤、转换(如 gzip, chunk) |
处理模块 | 提供服务逻辑,如 ngx_http_proxy_module |
模块生命周期 Hook:
模块通过 Hook 函数注册以下生命周期点:
postconfiguration
init_module
init_process
init_thread
exit_thread
exit_process
exit_master
5. 请求处理流程
以 HTTP 请求为例,完整流程如下:
1. 请求接收
Worker 进程通过 epoll 监听 socket fd 上的 accept 事件
建立连接后封装为
ngx_connection_t
2. 请求解析
使用状态机解析 HTTP 请求头、方法、URI 等
填充
ngx_http_request_t
结构
3. 查找 Location 配置
基于 URI 匹配 location block
加载对应 handler(如 proxy_pass)
4. 模块链处理
Nginx 通过模块链的方式处理请求,每个模块处理一部分逻辑:
HTTP 请求
→ handler 模块
→ access 模块
→ rewrite 模块
→ content 模块(如 proxy)
→ filter 模块(gzip,chunked)
→ output
每个模块通过 ngx_http_module_t
结构中的回调函数插入处理链。
5. 响应输出
各模块处理完响应后输出到客户端
使用
sendfile
+writev
实现 零拷贝
6. 内存管理与连接池
Nginx 有自己的一套高性能内存池机制,用于避免频繁 malloc/free:
ngx_pool_t
: 每个请求创建自己的内存池,生命周期跟随请求可申请小块内存(<4096)快速分配
请求结束时统一释放内存池,避免内存泄漏
连接相关资源也使用对象池(如 ngx_connection_t
、ngx_buf_t
)复用,提高性能。
7. 配置解析系统
Nginx 配置文件是模块驱动的,结构清晰。
配置解析过程:
Master 进程读取配置文件(
nginx.conf
)每个模块注册自己的配置指令及解析函数
创建配置结构体,填入配置参数
绑定到
ngx_cycle_t
中
支持嵌套结构、变量引用($host
)、include 等高级配置特性。
8. 零拷贝机制(Zero Copy)
为了提升性能,Nginx 避免数据拷贝,使用以下机制:
技术 | 说明 |
---|---|
sendfile() |
直接从磁盘文件到 socket,无需中转缓冲区 |
mmap() |
映射文件到内存 |
writev() |
将多个缓冲区一次性写入 socket |
splice() |
在 pipe 与 socket 之间传输,无需用户态 |
这些机制显著降低了 CPU 消耗和内存拷贝,提高了吞吐量。
9. 共享内存与缓存管理
Nginx 提供共享内存机制支持:
状态统计模块(如 stub_status)
缓存(proxy_cache, fastcgi_cache)
限流(limit_req, limit_conn)
Nginx Plus 状态页
通过 ngx_shm_zone_t
管理共享内存区域,使用 slab 分配器精细管理内存块。
10. 日志系统
Nginx 日志系统也高度模块化:
access_log
,error_log
格式化配置灵活:
log_format
支持异步写入、缓存 flush、缓冲池等
每个请求独立生成
ngx_log_t
对象
11. Nginx 底层架构图示
┌────────────────────┐
│ Master 进程 │
└────────────────────┘
│
┌────────────┴────────────┐
▼ ▼
┌─────────────┐ ┌─────────────┐
│ Worker 进程 │ ... │ Worker 进程 │
└─────────────┘ └─────────────┘
│ epoll / kqueue / IOCP
▼
┌──────────────────────────────────────┐
│ Reactor 模型:事件循环 │
├──────────────────────────────────────┤
│ Connection Pool + 内存池 + 模块链 │
├──────────────────────────────────────┤
│ HTTP / TCP 模块处理,输出响应 │
└──────────────────────────────────────┘
12. 小结
关键模块 | 技术点 |
---|---|
高性能 | 异步非阻塞 IO,sendfile |
高并发 | 多 worker,事件驱动 |
模块化 | 所有功能模块驱动 |
内存管理 | 内存池、连接池 |
高扩展性 | 插件化模块架构 |
二、Nginx配置详解并结合使用场景深入分析
1. 主配置(全局配置块)
主配置一般出现在 nginx.conf
的最上层,用于控制整个服务器级别的行为。
常见配置项:
配置项 | 含义和作用 |
---|---|
user |
指定 worker 子进程运行的系统用户 |
worker_processes |
启动的 worker 数量,建议设置为 CPU 核心数 |
worker_cpu_affinity |
绑定 CPU,提高性能(用于多核) |
error_log |
全局错误日志路径及级别(`debug |
pid |
指定 pid 文件路径 |
worker_rlimit_nofile |
最大文件描述符限制 |
示例:
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
使用场景分析:
高并发服务:建议
worker_processes
设置为auto
,根据 CPU 自动适配。Debug 排查问题时:可以临时设置
error_log
为debug
。
2. events 块配置(事件模型)
控制 Nginx 的事件处理机制,位于 nginx.conf
的 events
块中。
常见配置项:
配置项 | 含义和作用 |
---|---|
worker_connections |
每个 worker 支持的最大连接数(非客户端连接) |
use |
强制指定事件模型(如 epoll/kqueue) |
multi_accept |
是否每次尽可能多接受新连接 |
示例:
events {
use epoll;
worker_connections 10240;
}
使用场景分析:
高连接量网站:
worker_connections * worker_processes
决定最大并发连接数。性能调优:使用
epoll
或kqueue
(操作系统支持前提下)可显著提升性能。
3. http 块配置(Web 服务核心)
这是 Nginx 配置中最重要的一部分,包含:
虚拟主机配置(
server
)路由与 location 规则
缓存、代理、gzip 等模块
日志、限速、连接控制等功能
4. server 块(虚拟主机配置)
一个 server 表示一个网站服务,可监听不同端口/域名。
常见配置项:
配置项 | 含义和作用 |
---|---|
listen |
指定监听的端口和地址 |
server_name |
指定虚拟主机域名 |
root |
指定 web 根目录 |
index |
默认首页文件 |
error_page |
自定义错误页 |
示例:
server {
listen 80;
server_name www.example.com;
root /var/www/example;
index index.html index.htm;
error_page 404 /404.html;
}
使用场景分析:
部署多个站点时,通过
server_name
和listen
实现虚拟主机划分。多端口监听(如 80 和 443)可以写多个
server
。
5. location 块(请求路由匹配)
定义请求 URI 匹配规则和处理方式,是 HTTP 配置的核心。
匹配类型:
类型 | 示例 | 含义 |
---|---|---|
精确匹配 | location = /foo |
严格等于 /foo 时生效 |
前缀匹配 | location /img/ |
URI 以 /img/ 开头 |
正则匹配 | location ~ \.php$ |
匹配以 .php 结尾的路径 |
示例:
location / {
try_files $uri $uri/ =404;
}
location /images/ {
root /data;
}
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
}
使用场景分析:
静态资源分流:不同类型资源设置不同缓存头、路径。
正则匹配动态请求,如 PHP、API 等。
6. 反向代理(proxy)
用于将客户端请求转发到后端服务器,是 Nginx 的核心能力之一。
常见配置项:
配置项 | 说明 |
---|---|
proxy_pass |
设置代理后端地址 |
proxy_set_header |
设置请求头信息,传递客户端信息 |
proxy_connect_timeout |
连接后端超时时间 |
proxy_read_timeout |
读取响应超时时间 |
proxy_redirect |
修改后端跳转地址 |
示例:
location /api/ {
proxy_pass http://backend_server/api/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
使用场景分析:
微服务网关:按路径路由不同服务。
解决跨域问题:配合
add_header
设置 CORS。
7. 负载均衡配置(upstream)
Nginx 作为七层负载均衡器,可配置多台后端服务。
配置示例:
upstream backend {
server 10.0.0.1:8080 weight=3;
server 10.0.0.2:8080 max_fails=2 fail_timeout=30s;
}
server {
location / {
proxy_pass http://backend;
}
}
支持算法:
round-robin
(默认)least_conn
ip_hash
hash $request_uri consistent;
(需第三方模块)
使用场景分析:
动态后端多副本:如服务部署多个 pod 实例,使用
upstream
配置调度。实现后端故障自动摘除(
max_fails
,fail_timeout
)。
8. 限流与连接控制
请求限速:
limit_req_zone $binary_remote_addr zone=req_limit:10m rate=1r/s;
server {
location /login/ {
limit_req zone=req_limit burst=5;
}
}
并发连接数限制:
limit_conn_zone $binary_remote_addr zone=addr:10m;
server {
location / {
limit_conn addr 1;
}
}
使用场景分析:
防止暴力破解、接口刷爆:对登录、注册接口限速。
限制单个 IP 并发连接,防止 DDoS。
9. 安全控制
拒绝某 IP:
deny 192.168.1.1;
allow 192.168.0.0/16;
强制 HTTPS:
server {
listen 80;
return 301 https://$host$request_uri;
}
设置头防御:
add_header X-Frame-Options DENY;
add_header X-XSS-Protection "1; mode=block";
10. 缓存配置
静态资源缓存:
location ~* \.(jpg|png|gif|css|js)$ {
expires 30d;
access_log off;
}
反向代理缓存(proxy_cache):
proxy_cache_path /tmp/nginx_cache levels=1:2 keys_zone=cache_zone:10m inactive=60m;
location /api/ {
proxy_cache cache_zone;
proxy_pass http://backend;
proxy_cache_valid 200 1h;
}
11. 日志配置
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;
使用场景分析:
业务分析:配合 ELK 分析用户行为。
安全审计:统计恶意请求、慢请求等。
12. 常用变量(部分)
变量名 | 含义 |
---|---|
$remote_addr |
客户端 IP |
$http_user_agent |
客户端 UA |
$request_uri |
请求 URI |
$host |
请求的 Host 头 |
$upstream_addr |
实际转发到的后端地址(proxy) |
13. 小结:分类速查表
分类 | 功能 |
---|---|
主配置 | 进程管理、日志、用户 |
events | IO 模型、最大连接数 |
http/server | 网站配置、虚拟主机 |
location | 路由控制、反向代理、限速 |
upstream | 负载均衡、服务池 |
cache | 缓存目录、缓存策略 |
security | 黑名单、头安全、重定向 |
logging | 日志格式与存储路径 |
三、Nginx如何解决跨域问题
1. 什么是跨域?为什么会出现跨域问题?
1.1 浏览器的同源策略(Same-Origin Policy)
同源策略要求:只有协议、域名、端口都相同,才能互相访问资源。
http://example.com:80/api/user ✅
http://example.com:8080/api/user ❌ (端口不同)
https://example.com/api/user ❌ (协议不同)
http://other.com/api/user ❌ (域名不同)
1.2 跨域行为触发场景
当以下前端行为访问非同源资源时,会触发跨域限制:
使用
XMLHttpRequest
,fetch
,axios
发起请求请求类型为非简单请求(如
PUT
,DELETE
,application/json
)请求带有自定义头(如
Authorization
)页面中嵌入
<iframe>
、<img>
、<script>
等外部域资源
2. 跨域类型与浏览器行为
2.1 简单请求
满足以下条件被认为是“简单请求”:
方法为
GET
/POST
/HEAD
请求头不超出:
Accept
,Accept-Language
,Content-Language
,Content-Type(必须是表单类型)
不带
Authorization
、自定义 headers 等
浏览器行为:
✅ 直接发请求 + 检查响应头是否包含允许跨域的信息(如 Access-Control-Allow-Origin
)
2.2 非简单请求(复杂请求)
如:
axios.post("http://api.server.com/data", data, {
headers: { "Content-Type": "application/json" },
});
浏览器行为:
先发一条 OPTIONS 请求(预检请求,Preflight)
服务端必须响应对应的 CORS 头信息
如果 OPTIONS 返回成功,再发真正的业务请求
3. Nginx 解决跨域的原理与配置
Nginx 本身不受跨域限制(因为它不是浏览器),但它可以作为中间层,通过添加 CORS 响应头或做代理来解决跨域。
4. Nginx 解决跨域的两种方式
方式一:添加 CORS 响应头(推荐)
场景:前端直接请求后端 API(后端已部署在 Nginx 上)
原理
添加以下 HTTP 响应头,使浏览器信任该请求的跨源访问:
Header 名称 | 说明 |
---|---|
Access-Control-Allow-Origin |
允许的跨域源(* 或具体域名) |
Access-Control-Allow-Methods |
允许的请求方法(如 GET,POST,PUT ) |
Access-Control-Allow-Headers |
允许的请求头(如 Authorization ) |
Access-Control-Allow-Credentials |
是否允许携带 Cookie(需具体域名,不能 * ) |
Access-Control-Max-Age |
预检请求缓存时间(秒) |
Nginx 配置示例:
server {
listen 80;
server_name api.example.com;
location /api/ {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
add_header Access-Control-Allow-Headers Authorization,Content-Type;
if ($request_method = OPTIONS ) {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
add_header Access-Control-Allow-Headers Authorization,Content-Type;
add_header Content-Length 0;
add_header Content-Type text/plain;
return 204;
}
proxy_pass http://backend_server;
}
}
注意事项:
如果前端要带 cookie,
Access-Control-Allow-Origin
不能是*
,必须指定具体域名,且加上:
add_header Access-Control-Allow-Credentials true;
方式二:通过 Nginx 反向代理解决跨域
场景:前端请求相同源的 Nginx,Nginx 再转发请求至后端不同源
这种方式从浏览器的角度看“没有跨域”,因为访问的地址始终是前端部署在同源的 Nginx。
原理
通过 Nginx 配置,将 /api/*
的请求转发到后端真实地址:
server {
listen 80;
server_name frontend.example.com;
location /api/ {
proxy_pass http://backend.example.com/;
proxy_set_header Host $host;
}
}
前端请求示例:
axios.get("/api/user");
前端以为在请求本域
/api/user
(不触发 CORS)实际 Nginx 代理到
http://backend.example.com/user
✅ 这种方案彻底绕开浏览器同源限制,适用于内网 API 服务或多服务聚合。
5. 浏览器跨域判断原理总结
请求行为 | 浏览器做什么 | 服务端需要响应什么 |
---|---|---|
简单请求 | 直接发请求 | 返回 Access-Control-Allow-Origin |
非简单请求 | 先发预检(OPTIONS),再发正式请求 | 返回 Access-Control-Allow-Origin 、Methods、Headers 等 |
withCredentials | 跨域携带 Cookie | 响应头中 Allow-Origin 不能是 * 且加 Allow-Credentials:true |
6. 实践场景推荐方案
场景 | 推荐方式 | 是否支持 Cookie |
---|---|---|
前端直连后端 API(已配置 CORS) | 添加 CORS 响应头 | 是 |
前端请求同域 Nginx,由其反代后端 | 反向代理 | 是 |
多服务统一网关接口聚合 | 网关反向代理统一出口 | 是 |
后端无法配置 CORS(第三方 API) | 通过 Nginx 转发或服务中转 | 是 |
7. 常见错误与排查建议
错误描述 | 可能原因 |
---|---|
No 'Access-Control-Allow-Origin' |
未设置 CORS 响应头或设置不正确 |
OPTIONS 请求返回 404/405 | 后端或 Nginx 未正确处理预检请求 |
cookie 不生效 | 使用了 * 而非具体域名;未设置 withCredentials |
Access-Control-Allow-Headers 缺失 |
设置了自定义头,但未在响应中声明允许 |
8. 补充:后端语言设置 CORS 的方式(参考)
后端语言 | 配置方式(示意) |
---|---|
Java (Spring Boot) | @CrossOrigin 或 CORS Filter |
Node.js (Express) | cors 中间件 |
Python Flask | flask-cors |
PHP | header('Access-Control-Allow-Origin: *') |
9. 小结
项目 | 原理 | Nginx 做法 |
---|---|---|
跨域限制 | 同源策略,浏览器拦截非同源请求 | 添加 CORS 响应头 or 使用反向代理绕开 |
简单请求 | 直接发请求,校验响应头 | 添加 Access-Control-Allow-Origin 等 |
非简单请求 | 发起预检(OPTIONS)再请求 | 对 OPTIONS 请求添加响应并返回 204 |
带 Cookie | 需指定域名 + Allow-Credentials: true |
Nginx 配置中同时设置域名和 credentials |
四、Nginx令牌桶限流
令牌桶(Token Bucket)算法是一种用于 限流(Rate Limiting) 的核心算法,广泛用于:
接口限流(如 Nginx、网关、API Server)
网络通信(如路由器 QoS)
服务熔断和拒绝策略(如 Sentinel、Envoy)
1. 令牌桶算法的核心思想
✅ 定义
令牌桶算法通过以固定速率往桶中放令牌(token),请求来临时从桶中取令牌,只有成功取到令牌的请求才被允许执行。
✅ 模型图解:
+------------------+ 请求到达 +--------------------+
| 令牌定时器 |--------------------->| 尝试从桶中取一个令牌 |
| 每隔 T 毫秒放 1 个 | | 若取成功 => 通过 |
| 令牌到桶中 | | 否则 => 拒绝/等待 |
+------------------+ +--------------------+
2. 核心参数
参数 | 含义 |
---|---|
rate (速率) |
每秒生成多少个令牌(如 10 token/sec) |
capacity (桶大小) |
桶中最多容纳多少令牌(避免 burst 时过载) |
tokens |
当前桶中实际令牌数量 |
lastRefillTime |
上次放令牌的时间,用于计算应补充多少令牌 |
3. 请求流程(标准流程)
每当有一个请求到来:
刷新令牌数量:根据当前时间 -
lastRefillTime
计算该放入多少令牌。更新令牌数:桶中现有令牌数 =
min(旧令牌 + 补充数, capacity)
检查令牌是否足够:
有令牌:取一个令牌,允许通过
无令牌:请求被限流(丢弃/等待/排队等策略)
4. 代码实现(简化版本,Java 示例)
class TokenBucket {
private final int capacity;
private final int rate;
private double tokens;
private long lastRefillTimestamp;
public TokenBucket(int capacity, int rate) {
this.capacity = capacity;
this.rate = rate;
this.tokens = capacity;
this.lastRefillTimestamp = System.currentTimeMillis();
}
public synchronized boolean allowRequest() {
long now = System.currentTimeMillis();
long deltaTime = now - lastRefillTimestamp;
// 计算该生成多少新令牌
double newTokens = deltaTime * rate / 1000.0;
tokens = Math.min(capacity, tokens + newTokens);
lastRefillTimestamp = now;
if (tokens >= 1) {
tokens -= 1;
return true;
} else {
return false;
}
}
}
5. 与漏桶算法的对比
项目 | 令牌桶 Token Bucket | 漏桶 Leaky Bucket |
---|---|---|
控流方式 | 请求需要令牌,按速率放令牌 | 请求入队,队列以固定速率“漏出” |
允许突发请求 | ✅(桶内有余令牌) | ❌(请求被固定速率漏出) |
限制速率 | 平均速率受限,最大速率受桶容量限制 | 严格平滑速率输出 |
典型用途 | Nginx 限流、API 限流、Redis 哨兵 | 视频流输出、网络 QoS |
6. 令牌桶的使用场景分析
1. Nginx 限流(使用漏桶 + 令牌桶思想)
limit_req_zone $binary_remote_addr zone=req_limit:10m rate=5r/s;
location /api/ {
limit_req zone=req_limit burst=10 nodelay;
}
rate=5r/s
:速率 = 5 个令牌/秒burst=10
:桶容量 = 10,允许突发 10 次nodelay
:有令牌立即放行(令牌桶特性)
本质是令牌桶思想:按速率补充令牌,有余量可突发请求。
2. 微服务 API 接口限流(如 Sentinel)
// 每秒最多允许 100 个请求
RateLimiter limiter = RateLimiter.create(100); // Guava 实现的令牌桶
if (limiter.tryAcquire()) {
// 执行请求逻辑
} else {
// 拒绝或降级
}
7. 常见扩展策略
1. 令牌时间过期控制
可以为每个 token 附加 TTL,避免长时间积累失控
2. 按 IP/接口限流
key = client_ip
或key = user_id + path
,每个维度单独维护一个桶
3. 集群令牌桶
分布式系统中,常结合 Redis 实现分布式令牌桶
8. 图解理解令牌桶
⏳ 时间轴演示(假设速率 5 r/s,容量 10)
t = 0s tokens = 10 (满)
t = 0.2s 请求1来 => ✅
t = 0.4s 请求2来 => ✅
...
t = 1s tokens += 5
t = 1.2s 请求10来 => ✅(用完 token)
t = 1.3s 请求11来 => ❌(没令牌)
9. 总结
关键点 | 说明 |
---|---|
控制平均速率 | 固定速率补充令牌,控制长期请求平均速率 |
支持突发流量 | 桶内积累的令牌可以一次性消耗,支持短时间高并发 |
高性能易实现 | 时间窗口 + 数学补偿,无需复杂调度器 |
使用广泛 | 应用于网关、限流器、API 请求控制、DDOS 防护等 |