nginx从大学到现在一直都在用,前前后后也写过很多了,这里来一篇尽可能全面的(大言不惭)
🧱 一、Nginx ------一个"超级前台小哥"
像咱们程序员我也不代表所有了,大部分程序员朋友对于未来的设想基本上都是开个店,那么如果你开了一家超火爆的火锅店(你的网站/应用),每天成千上万食客(用户请求)蜂拥而至。
- 如果没有 Nginx:每个食客都直接冲进后厨(你的应用服务器,比如 Node.js、Python Flask、Java Tomcat),对着厨师大喊:"我要毛肚、宽粉!快点上!"
→ 厨师崩溃了,锅都烧穿了,还被投诉上菜慢。 - 有了 Nginx:门口站了个冷静、高效、记忆力超群的前台小哥 (Nginx)。
- 食客来了,先找他。
- 他会快速判断:"哦,你要的是菜单(静态文件)?我这儿就有,给你!"(直接返回 HTML/CSS/JS/图片)
- 如果你要的是现炒菜(动态内容),他就说:"稍等,我帮你叫后厨。"然后把请求转给后厨(反向代理)。
- 如果后厨忙不过来,他还知道有多个后厨(负载均衡),自动分配任务,绝不让一个累死。
- 有人想捣乱(DDoS攻击)?他一眼识破,直接轰出去(限流、安全防护)。
SO, Nginx是咱们高性能的"请求调度员 + 静态资源快递员"
why
大多数服务器(常用的店Apache)用的是 "多进程/多线程"模型 :(单进程/单线程现在哪有市场,根本吃不开呀!)
→ 来一个请求,就fork 一个新进程或线程去处理。(redis还有谁是不是也有fork,知识的思想都是相通的)
→ 10000 个请求?那就开 10000 个线程!结果:内存爆炸、上下文切换累死 CPU,像开了 10000 个微信窗口同时聊天。
这也太不智能了,这可不用,卡卡的
Nginx 用的是 事件驱动 + 异步非阻塞 I/O 模型(基于 epoll/kqueue 等系统调用)
想象前台小哥不是一个人干所有事,而是一个主控大脑 + 一堆小纸条。
-
主线程master process咱们的大脑:只管招人、发工资(只读配置,管理 worker)
-
真正干活的是几个 worker 进程 (通常 = CPU 核心数);真正处理网络 I/O(每个独立进程,避免锁竞争,没在线程切换上浪费)CPU 利用率高
nginx.conf
worker_processes auto; # 通常设为 CPU 核心数(auto = 自动检测)
worker_rlimit_nofile 65535; # 每个 worker 能打开的最大文件描述符(FD) -
每个worker 进程内部 :重点point!
-
不为每个请求开新线程!
-
用一个事件循环(event loop),盯着一堆"待办事项"(文件描述符 FD)
events {
use epoll; # 强制使用 epoll(Linux 高效 I/O 多路复用)
worker_connections 10240; # 每个 worker 最大并发连接数
multi_accept on; # 一次 accept() 尽可能接收多个连接 减少系统调用
}
比如:
// Nginx 内部简化逻辑:accept()、read()都是非阻塞的 → 不会卡住 event loop
epoll_fd = epoll_create(1);
listen_sock = socket(); bind(); listen();
set_nonblocking(listen_sock); // 关键:设为非阻塞!
//只通知"有事件的 FD",不像 select/poll 遍历全部
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_sock, EPOLLIN);while (1) {
int n = epoll_wait(epoll_fd, events, max_events, timeout);
for (i=0; i<n; i++) {
if (events[i].data.fd == listen_sock) {
while (conn = accept4(listen_sock, ..., SOCK_NONBLOCK)) {
// 接收新连接(非阻塞!一次收完所有 pending 连接)
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, conn, EPOLLIN|EPOLLET);
}
} else {
// 处理已连接 socket 的读写(非阻塞 read/write)
handle_io(events[i].data.fd);
}
}
}
-
-
- 当某个请求的数据从网卡到了(可读事件),它就处理一下;处理完要发回去,就注册"可写事件",然后立刻去处理下一个请求。
- 全程不等!不睡!不卡! 就像闪电侠在多个任务间瞬移。
- Linux 进程间内存隔离,一个worker 崩了不影响其他,避免全局解释器锁(GIL)类问题。性能更稳定
咱们Nginx 利用 Linux 的 epoll(或 BSD 的 kqueue)实现高效的 I/O 多路复用。它把 socket 设置为 非阻塞模式,配合事件通知机制,避免了传统"阻塞 read() 卡住整个线程"的问题。
📦 三、模块化设计 ------ "乐高式"组装功能:可嵌套、可继承
Nginx 本身很小,但通过模块可以变身全能战士:灵活得像乐高
- 核心模块:进程管理、配置解析、事件驱动(地基)
Nginx 配置文件(nginx.conf)不是命令,而是声明"当遇到什么情况,就做什么":
server {
listen 80;
server_name example.com;
location /static/ {//像if-else规则,匹配 URL 路径
root /data; # 静态文件,直接返回
}
//每个请求按阶段处理:rewrite → access → content → log ...
location /api/ {
proxy_pass http://backend; # 动态请求,转发给后端
}
}
修改配置后 nginx -s reload,平滑重启:
master 启动新 worker,老 worker 处理完手头请求自动退出 ------ 用户无感知!
-
HTTP 模块:处理 Web 请求(主力部队)
-
Mail 模块:还能当邮件代理(隐藏技能)
-
第三方模块:Lua 脚本(OpenResty)、gRPC 代理、WAF 防火墙......
//原生Nginx 不支持 Lua,但 OpenResty = Nginx + LuaJIT + 模块,广泛用于 API 网关
//Lua 代码在 Nginx 的 access 阶段执行
location /private/ {
access_by_lua_block {
local token = ngx.req.get_headers()["Authorization"]
if not token or token ~= "Bearer secret123" then
ngx.exit(401)//API 直接操作请求/响应
end
}
proxy_pass http://app;
}如果不想装 OpenResty,也可用 nginx-lua-module 编译进 Nginx。
比如你想加个"用户登录验证"?写个 Lua 脚本挂到
access_by_lua阶段就行 ------ 像给前台小哥戴个智能眼镜,实时识别 VIP。
模块在编译时静态链接 or 运行时动态加载(1.9.11+),灵活又高效。
🔄 四、反向代理 & 负载均衡 ------ "甩锅大师"
Nginx 最常用的功能之一:反向代理(Reverse Proxy)。
-
用户以为在和 Nginx 说话,其实 Nginx 转头就去找后端服务器(Tomcat/Node.js 等)。
-
对外隐藏了真实服务器 IP,安全又方便。
upstream backend {
server 10.0.0.10:8080 weight=3 max_fails=2 fail_timeout=30s;
server 10.0.0.11:8080 weight=1;
server 10.0.0.12:8080 backup; # 备用节点keepalive 32; # 每个worker维护最多32个空闲后端连接:避免频繁建连,与后端建独立TCP连接}
//Nginx 先收完客户端请求体(或流式转发),再发给后端
server {
listen 80;
location / {
proxy_pass http://backend; # 注意:这里用 upstream 名# 透传客户端信息 后端看到的来源IP是 Nginx 的内网 IP。 proxy_set_header Host $ host; #通过 X-Real-IP 传递真实用户 IP proxy_set_header X-Real-IP $ remote_addr; proxy_set_header X-Forwarded-For $ proxy_add_x_forwarded_for; # 超时设置(防后端卡死) proxy_connect_timeout 5s; proxy_send_timeout 10s; proxy_read_timeout 10s; }}
负载均衡算法(把请求分给多个后端):
round-robin(默认):轮流来,公平!least_conn:谁连接少给谁,照顾弱鸡服务器ip_hash:同一个 IP 永远打给同一台,解决 session 问题weight:给性能强的服务器多分点活("壮汉多搬砖"原则)
Nginx 维护一个后端服务器列表,根据算法选一个,然后用
upstream模块建立新连接转发数据。健康检查(
max_fails,fail_timeout),自动踢掉宕机的后端 ------ 像个无情的 HR。
📁 五、静态文件服务 ------ "本地快递员"
Nginx 发静态文件快到飞起?拷贝 sendfile
location /static/ {
root /var/www;
sendfile on; # 启用零拷贝
tcp_nopush on; # 等待包满再发(减少小包)
tcp_nodelay off; # 与 nopush 配合,优化吞吐
open_file_cache max=1000 inactive=60s; # 缓存文件元数据
}
-
直接调用
sendfile()系统调用(零拷贝技术)!- 传统方式:磁盘 → 内核缓冲区 → 用户缓冲区 → socket 缓冲区 → 网卡(3次拷贝)
sendfile:磁盘 → 内核缓冲区 → 网卡(1次拷贝,CPU 几乎不参与)
-
支持
gzip压缩、ETag缓存、expires过期头,减少带宽和重复请求。磁盘 → 内核页缓存 ────[DMA]───→ 网卡
↑ 只传指针,无 CPU 拷贝
strace -e trace=sendfile,read,write -p $ (pgrep nginx | head -1)
访问 /static/test.jpg
sendfile(10, 9, NULL, 123456) = 123456用了零拷贝,如看到 read+write配置没生效
🔒 六、安全与限流 ------ "保安队长"
-
限流(Rate Limiting) :
limit_req模块基于漏桶算法,防止恶意刷接口。
→ "每人每秒只能点一次单,插队?滚!" -
访问控制 :
allow/denyIP,auth_basic密码保护。 -
防 CC 攻击 :
结合limit_conn限制单 IP 并发连接数。 -
TLS/SSL 终止 :
HTTPS 解密在 Nginx 做,后端用 HTTP,减轻应用负担。http {
// binary_remote_addr:用二进制 IP 作 key(比字符串省内存) //zone=perip:10m:分配 10MB 共享内存存状态(所有 worker 共享) //rate 漏桶(Leaky Bucket),水滴入速度 = rate limit_req_zone binary_remote_addr zone=perip:10m rate=10r/s;server { location /api/ { limit_req zone=perip burst=20 nodelay; # burst=20:桶容量:允许突发 20 个请求 漏桶(Leaky Bucket) # nodelay:突发时不延迟,直接处理(否则排队) } }}
测试:用 ab -n 100 -c 10 http://yoursite/api/,观察是否限流
💡 总结:Nginx 的底层哲学:用不同维度思考网络 I/O
| 特性 | 传统服务器(如 Apache) | Nginx |
|---|---|---|
| 并发模型 | 多线程/多进程(C10K 问题) | 事件驱动 + 异步非阻塞(轻松 C100K+) |
| 资源消耗 | 高内存、高上下文切换 | 低内存、CPU 高效 |
| 架构 | 单体,功能耦合 | 模块化,按需组装 |
| 角色 | 全能但笨重 | 专注 I/O 调度,做"最靓的仔" |
| 要素 | 配置指令 | 内核机制 |
|---|---|---|
| 高并发 | worker_processes, worker_connections |
epoll/kqueue + 非阻塞 I/O |
| 低延迟 | tcp_nodelay, keepalive |
Nagle 算法控制 + 连接复用 |
| 高吞吐 | sendfile, tcp_nopush |
零拷贝 + 聚合发送 |
| 高可用 | upstream, max_fails |
健康检查 + 故障转移 |
Nginx 真言:做人就要敢说敢做!
"我不生产内容,我只是请求的搬运工;
但我搬运得比谁都快、都稳、都省电。"