NGINX
Nginx 学习笔记
一、Nginx 是什么
1.1 Nginx 的角色
Nginx 本质上是一个高性能 Web 服务器,但你在后端开发里最常见到它,通常是这三个角色:
- 反向代理
- 负载均衡
- 静态资源服务器
先把这三个词彻底搞明白。
反向代理是什么
所谓代理,就是“替别人转发请求”。
- 正向代理:代理的是客户端
比如你电脑不能直接访问外网,于是通过代理服务器出去。服务器看到的是代理,不是你。 - 反向代理:代理的是服务端
客户端以为自己在直接访问目标服务,实际上请求先到了 Nginx,再由 Nginx 转发给后端服务。
之所以叫反向,是因为它和正向代理站的位置反过来了。
正向代理站在客户端这边,反向代理站在服务端这边。
一个典型请求链路是这样:
flowchart LR
A[浏览器] --> B[Nginx]
B --> C[Tomcat / SpringBoot]
C --> B
B --> A
客户端一般不会直接暴露访问后端服务,而是统一先打到 Nginx:
- 统一入口
- 隐藏后端服务地址
- 转发请求
- 做负载均衡
- 托管静态资源
负载均衡是什么
负载均衡这词别被名字吓到,说白了就是:
一堆后端服务器一起干活,Nginx 负责把请求尽量合理地分过去。
比如你有 3 台 Java 服务:
- 192.168.1.10:8080
- 192.168.1.11:8080
- 192.168.1.12:8080
Nginx 收到请求后,不会永远只转发给第一台,而是按某种规则分发。
这样做的好处是:
- 抗并发更强
- 单机挂了不至于全站挂
- 机器可以横向扩容
静态资源服务器是什么
静态资源,就是内容不会经过后端业务计算,文件内容基本固定的资源,比如:
- html
- css
- js
- 图片
- 字体文件
这些东西如果都让 Tomcat 来处理,那纯属让厨师去扫地。
Nginx 处理静态文件非常擅长,效率高、占用低,所以一般会让它直接返回静态资源。
1.2 Nginx 的进程模型
Nginx 的经典进程模型是:
- master 进程
- worker 进程
结构大概是这样:
flowchart TD
M[master 进程]
M --> W1[worker 1]
M --> W2[worker 2]
M --> W3[worker 3]
M --> W4[worker 4]
master 干什么
master 不负责真正处理用户请求,它主要负责:
- 读取和校验配置文件
- 管理 worker
- 接收重载、停止等信号
- 拉起新的 worker,回收旧的 worker
你可以把它理解成管理者。
worker 干什么
真正处理连接和请求的是 worker。
客户端建立连接、读写请求、转发给后端、返回响应,都是 worker 在干。
1.3 为什么单线程也能高并发
很多人一听到 worker 是单线程就慌了,觉得单线程不就废了吗。
这恰恰说明没抓住本质。
Nginx 的高并发,靠的不是“线程多”,而是:
- 事件驱动
- 非阻塞 I/O
- epoll
事件驱动是什么意思
传统阻塞模型里,一个线程处理一个连接,线程大部分时间其实都在傻等:
- 等客户端发数据
- 等内核把数据准备好
- 等网络可写
线程没干活,但资源已经占着了。
Nginx 不这么玩。
它会把很多连接注册到事件机制里,谁准备好了,就处理谁。没准备好,就先放着,不死等。
这就叫事件驱动。
epoll 是什么
epoll 是 Linux 下非常重要的一种 I/O 多路复用机制。
名字可以拆开理解:
poll:轮询检查epoll:增强版的轮询事件通知机制
它的核心价值是:
一个 worker 可以同时监听大量连接,只在连接真正可读、可写时才去处理。
所以单线程不是弱,关键要看线程是不是在高效处理事件。
Nginx 的 worker 单线程模型反而有几个优势:
- 没有线程切换开销
- 没有大量锁竞争
- 逻辑更稳定
- 对高并发连接很友好
一句话:
Nginx 擅长的是高并发网络 I/O,不是复杂业务计算。
1.4 Nginx vs Tomcat
这俩东西很多新手容易混。
Nginx 主要处理什么
Nginx 主要干的是网络入口层的事:
- 接收 HTTP 请求
- 转发请求
- 做负载均衡
- 返回静态资源
Tomcat 主要处理什么
Tomcat 主要干的是Java Web 容器和业务执行的事:
- 接收转发过来的动态请求
- 调用 Servlet / SpringMVC / SpringBoot 业务逻辑
- 查数据库
- 计算结果
- 返回动态响应
两者如何配合
典型配合关系如下:
flowchart LR
A[浏览器] --> B[Nginx]
B -->|静态资源请求| C[直接返回静态文件]
B -->|动态请求| D[Tomcat / SpringBoot]
D --> B
B --> A
实际理解就一句话:
Nginx 负责站门口接客和分流,Tomcat 负责进屋干活。
二、配置文件结构
Nginx 配置文件是分层的,这一层级关系必须清楚:
1 | main |
也就是:
maineventshttpserverlocation
2.1 main → events → http → server → location
main
最外层就是主配置区域,也叫 main。
这里一般配置:
- worker 进程数
- 日志路径
- pid 文件
- 全局性参数
例如:
1 | worker_processes 4; |
这个表示开启 4 个 worker 进程。
events
events 块主要配置网络事件相关内容,比如:
1 | events { |
worker_connections 表示每个 worker 最多可同时处理多少个连接。
注意是每个 worker,不是整个 Nginx。
http
http 块是最重要的 HTTP 配置区域。
凡是和 HTTP 服务相关的内容,基本都在这里面,比如:
serverupstreamgzip- 日志格式
- 代理配置默认项
例如:
1 | http { |
server
server 块可以理解成一个虚拟主机。
为什么叫虚拟主机?
因为一台机器、一个 Nginx,可以通过不同域名、端口,虚拟出多个“网站入口”。
它们不一定是不同物理机器,但在逻辑上像多个独立站点,所以叫虚拟主机。
例如:
1 | server { |
同样监听 80 端口,但根据不同域名进入不同 server。
location
location 是在 server 里面继续做URI 路径级别匹配的。
比如:
1 | server { |
这表示:
/API/开头的请求,转发给后端- 其他请求,走静态资源目录
2.2 server 块:listen、server_name
listen
listen 表示这个 server 监听哪个端口。
例如:
1 | listen 80; |
表示监听 80 端口,也就是 HTTP 默认端口。
也可以写:
1 | listen 8080; |
表示监听 8080。
server_name
server_name 表示这个 server 对应哪个域名。
例如:
1 | server_name www.test.com; |
请求到来时,Nginx 会根据请求头里的 Host,选择匹配的 server。
比如:
- 访问
www.test.com,进这个server - 访问
API.test.com,可能进另一个server
你可以把它理解成:
listen 是按端口分入口,server_name 是按域名分入口。
2.3 location 块:匹配规则与优先级
这个地方是 Nginx 面试和实际工作里都很容易踩坑的点。
先记住几种常见写法:
=:精确匹配^~:前缀匹配,且一旦命中就不再看正则~:正则匹配,区分大小写- 普通前缀:直接写路径前缀
例如:
1 | location = /login { ... } |
1)= 精确匹配
1 | location = /login { |
只有请求路径完全等于 /login 才会命中。
/login命中/login/不命中/login?id=1路径部分还是/login,可命中
这是优先级最高的一种。
2)^~ 前缀匹配
1 | location ^~ /static/ { |
只要请求以 /static/ 开头,就命中。
而且一旦命中,后面的正则匹配不再参与。
这个非常适合静态资源目录。
3)~ 正则匹配
1 | location ~ \.png$ { |
表示匹配以 .png 结尾的请求。
这是正则匹配,适合做文件后缀类规则。
4)普通前缀匹配
1 | location /api/ { |
这就是最普通的前缀匹配。
只要 URI 以 /API/ 开头,就可以匹配上。
2.4 一定要记住的优先级
你先记这个就够了:
=精确匹配^~前缀匹配~正则匹配- 普通前缀匹配,选最长的那个
举个例子:
1 | location = /api/test { ... } |
请求 /API/test:
- 先看有没有
=精确匹配 - 有,就直接用
- 后面都不用看了
请求 /API/user/list.do:
- 没有
=精确匹配 - 如果有
^~ /API/,那它会直接命中,正则也不看了 - 如果没有
^~,那才会继续看~正则
这块别靠背,靠理解。
本质就是:
Nginx 先尽量找更明确的规则,越明确优先级越高。
三、反向代理
3.1 proxy_pass 的基本用法
Nginx 做反向代理最核心的指令就是:
1 | proxy_pass |
基本写法:
1 | location /api/ { |
这表示:
- 客户端访问
/API/xxx - Nginx 把请求转发给
127.0.0.1:8080
你可以先粗暴理解成:
location 负责拦请求,proxy_pass 负责把请求送给后端。
3.2 带斜杠 vs 不带斜杠的路径差异
这是经典坑,必须搞透。
很多人线上路径错乱,就是死在这里。
看两个配置:
写法一:proxy_pass 不带斜杠
1 | location /api/ { |
请求:
1 | /api/user/list |
转发后会变成:
1 | http://127.0.0.1:8080/api/user/list |
也就是说:
原始请求路径会整体带过去。
写法二:proxy_pass 带斜杠
1 | location /api/ { |
请求:
1 | /api/user/list |
转发后会变成:
1 | http://127.0.0.1:8080/user/list |
这里 /API/ 这一段被替换掉了。
3.3 为什么会这样
你可以把它理解成两种模式:
不带斜杠
1 | proxy_pass http://127.0.0.1:8080; |
意思更接近:
把原始 URI 原封不动拼到目标主机后面。
带斜杠
1 | proxy_pass http://127.0.0.1:8080/; |
意思更接近:
把当前 location 匹配掉的前缀部分替换成 /。
3.4 一眼看懂这个坑
1 | location /api/ { |
/API/user→http://127.0.0.1:8080/API/user
1 | location /api/ { |
/API/user→http://127.0.0.1:8080/user
这个地方的本质就是:
你到底想保留 /API 这段路径,还是想把它裁掉。
后端项目如果接口本身就是 /API/user,那你通常不裁。
后端项目如果接口其实是 /user,只是想对外统一暴露成 /API/user,那你就裁。
3.5 透传真实 IP
反向代理之后,后端服务看到的客户端 IP,往往会变成 Nginx 的 IP。
这就会导致:
- 日志里拿不到真实用户 IP
- 风控、限流、审计可能失效
所以通常要把真实客户端 IP 通过请求头传给后端。
常见写法:
1 | location /api/ { |
这里:
X-Real-IP是一个请求头字段名$remote_addr是 Nginx 变量,表示当前客户端 IP
这样后端就可以从请求头里拿到真实来源 IP。
这行的作用你必须知道:
1 | proxy_set_header X-Real-IP $remote_addr; |
意思就是:
别让后端只看到代理机地址,把用户真实 IP 一起带过去。
四、负载均衡
4.1 upstream 块的写法
Nginx 做负载均衡时,一般先定义一个后端服务器组,也就是:
1 | upstream |
这个词本身就很形象,意思可以理解成上游服务。
对 Nginx 来说,自己是入口,后面的 Tomcat、SpringBoot 服务就是它要转发过去的“上游”。
例如:
1 | upstream backend { |
这表示:
backend是一个服务器组名- Nginx 收到
/API/请求后,转发给这个组里的某一台机器
4.2 三种策略
1)轮询(默认)
如果你什么都不配,默认就是轮询。
1 | upstream backend { |
请求会大致按顺序分给各个服务器:
- 第一个给 8080
- 第二个给 8081
- 第三个再给 8080
- 第四个再给 8081
这是最基础的策略。
适合:
- 机器性能差不多
- 请求处理耗时也差不多
2)ip_hash
1 | upstream backend { |
这个策略会根据客户端 IP 做哈希,让同一个 IP 的请求尽量落到同一台机器。
它的价值在于:
让同一个用户更稳定地落到同一台后端。
适合一些希望“会话粘性”更强的场景。
但你也得知道它的问题:
- 某些 IP 请求特别多时,可能会压歪某台机器
- 负载不一定均匀
3)least_conn
1 | upstream backend { |
这个策略会优先把请求分给当前连接数更少的服务器。
适合:
- 请求处理时间差异较大
- 某些请求很慢,轮询不够聪明
因为轮询只看顺序,不看谁忙谁闲;least_conn 会更倾向把请求给当前没那么忙的机器。
4.3 weight、max_fails、backup 参数
weight
weight 表示权重。
1 | upstream backend { |
这表示 8080 分到的请求大约是 8081 的 3 倍。
适合:
- 机器性能不一样
- 想让配置更高的机器多扛点流量
一句话:
weight 越大,分到的请求通常越多。
max_fails
1 | upstream backend { |
它表示:
在一定失败判断周期内,允许最大失败次数。
你现阶段不用死抠底层判定细节,只要知道用途就行:
- 某台机器连续失败太多次
- Nginx 会认为它不太靠谱
- 暂时少给它甚至不给它分请求
实习阶段记住它是失败容忍阈值就够了。
backup
1 | upstream backend { |
backup 表示备用服务器。
正常情况下,请求主要打到普通服务器。
只有当主要服务器不可用时,才会启用 backup 机器。
这很好理解:
backup 不是主力,是替补。
五、静态资源
5.1 root vs alias 的路径拼接差异
这又是一个经典坑。
不搞懂,静态资源路径一定配错。
root
root 的逻辑是:
把 location 匹配到的 URI,直接拼到 root 指定目录后面。
例如:
1 | location /img/ { |
请求:
1 | /img/a.png |
最终找的文件路径是:
1 | /data/img/a.png |
注意,是把整个 URI /img/a.png 拼到 /data 后面。
alias
alias 的逻辑是:
用 alias 指定的目录,直接替换掉当前 location 前缀。
例如:
1 | location /img/ { |
请求:
1 | /img/a.png |
最终找的文件路径是:
1 | /data/images/a.png |
这里 /img/ 没被拼上去,而是被替换掉了。
5.2 为什么这俩老让人翻车
你看下面两个配置:
root
1 | location /static/ { |
请求:
1 | /static/app.js |
对应文件:
1 | /usr/share/nginx/html/static/app.js |
alias
1 | location /static/ { |
请求:
1 | /static/app.js |
对应文件:
1 | /usr/share/nginx/html/app.js |
5.3 一句话记忆
root:保留原 URI 再拼接alias:把 location 前缀替换掉
你只要一搞静态资源目录映射,就先问自己一句:
我想保留请求路径前缀,还是想把它替换掉。
5.4 try_files:SPA 前端路由的标准写法
SPA 是 Single Page Application,也就是单页应用。
比如前端是 Vue、React,前端路由通常长这样:
//login/user/profile/order/list
这些路径很多其实不是服务器上真实存在的文件路径,而是前端路由接管的。
如果你不做处理,用户直接访问:
1 | /user/profile |
Nginx 可能会去找真实文件,找不到就 404。
这时候就要用 try_files。
标准写法:
1 | location / { |
它的含义是:
- 先找当前 URI 对应的文件
- 再找当前 URI 对应的目录
- 还找不到,就返回
/index.html
为什么这样就行?
因为 SPA 的真正入口一般就是 index.html。
只要把它返回给浏览器,前端路由就能接管后续页面渲染。
所以这句:
1 | try_files $uri $uri/ /index.html; |
本质上是在说:
先看看有没有真文件;没有的话,别急着 404,交给前端应用自己处理。
5.5 gzip on 基本配置
gzip 就是响应压缩。
它的意义很直接:
减小传输体积,加快页面资源加载。
基础配置可以这样写:
1 | http { |
几个核心点你知道就够了:
gzip on;
开启压缩gzip_types
指定哪些类型参与压缩gzip_min_length 1024;
小于 1024 字节的不压缩,太小了压了也没啥意义
实习阶段,你只要知道 gzip 是减少传输体积的就够了。
六、常用命令
这几个命令是最常用的,必须会。
1 | nginx -t # 检查配置 |
6.1 nginx -t
1 | nginx -t |
作用:
检查配置文件是否正确。
你每次改完配置,别上来就 reload。
先 -t,这是规矩,不是礼貌。
它会帮你检查:
- 配置语法有没有写错
- 配置文件能不能正常加载
这是最基础、也最该养成习惯的一步。
6.2 nginx -s reload
1 | nginx -s reload |
作用:
平滑重载配置。
什么叫平滑?
就是:
- 让 Nginx 重新加载新配置
- 尽量不中断已有服务
这也是线上最常用的方式。
改完配置,检查通过,再 reload。
典型流程:
1 | nginx -t |
6.3 nginx -s quit
1 | nginx -s quit |
作用:
优雅停止 Nginx。
所谓优雅,就是:
- 不再接收新请求
- 已有请求尽量处理完
- 然后再退出
这比粗暴强停要稳得多。
不需要现在学的
下面这些内容,不是说不重要,而是你现在先别把脑子搞炸:
location rewrite- 限流
- HTTPS 证书配置
- 日志切割
原因很简单:
你现在最该先打牢的,是入口转发、路径匹配、代理规则、静态资源这套最基础的骨架。
连 proxy_pass 斜杠都没整明白,就去搞证书和限流,那属于还没学会走路就想漂移。
结尾
作为后端实习生,Nginx 这一阶段最该吃透的,不是背很多配置项,而是把下面几件事真正搞明白:
- Nginx 到底站在请求链路的哪一层
server和location到底怎么匹配proxy_pass带不带斜杠到底差在哪upstream怎么把请求分给多个后端root和alias为什么会把静态资源路径搞乱try_files为什么能兜住 SPA 路由
你把这些吃透,已经比一堆只会复制配置的人强一截了。
很多人配 Nginx,像在庙里摇签。你别学他们,你要知道每一行到底在干什么。