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
2
3
4
5
6
7
main
├── events
└── http
├── server
│ └── location
└── server
└── location

也就是:

  • main
  • events
  • http
  • server
  • location

2.1 main → events → http → server → location

main

最外层就是主配置区域,也叫 main
这里一般配置:

  • worker 进程数
  • 日志路径
  • pid 文件
  • 全局性参数

例如:

1
worker_processes  4;

这个表示开启 4 个 worker 进程。


events

events 块主要配置网络事件相关内容,比如:

1
2
3
events {
worker_connections 1024;
}

worker_connections 表示每个 worker 最多可同时处理多少个连接

注意是每个 worker,不是整个 Nginx。


http

http 块是最重要的 HTTP 配置区域。
凡是和 HTTP 服务相关的内容,基本都在这里面,比如:

  • server
  • upstream
  • gzip
  • 日志格式
  • 代理配置默认项

例如:

1
2
3
4
5
6
7
8
9
http {
include mime.types;
default_type application/octet-stream;

server {
listen 80;
server_name localhost;
}
}

server

server 块可以理解成一个虚拟主机

为什么叫虚拟主机?

因为一台机器、一个 Nginx,可以通过不同域名、端口,虚拟出多个“网站入口”。
它们不一定是不同物理机器,但在逻辑上像多个独立站点,所以叫虚拟主机

例如:

1
2
3
4
5
6
7
8
9
server {
listen 80;
server_name www.a.com;
}

server {
listen 80;
server_name www.b.com;
}

同样监听 80 端口,但根据不同域名进入不同 server


location

location 是在 server 里面继续做URI 路径级别匹配的。

比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
server {
listen 80;
server_name localhost;

location /api/ {
proxy_pass http://127.0.0.1:8080;
}

location / {
root html;
index index.html;
}
}

这表示:

  • /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
2
3
4
location = /login { ... }
location ^~ /static/ { ... }
location ~ \.jpg$ { ... }
location / { ... }

1)= 精确匹配

1
2
3
location = /login {
return 200 "login";
}

只有请求路径完全等于 /login 才会命中。

  • /login 命中
  • /login/ 不命中
  • /login?id=1 路径部分还是 /login,可命中

这是优先级最高的一种。


2)^~ 前缀匹配

1
2
3
location ^~ /static/ {
root /usr/share/nginx/html;
}

只要请求以 /static/ 开头,就命中。
而且一旦命中,后面的正则匹配不再参与

这个非常适合静态资源目录。


3)~ 正则匹配

1
2
3
location ~ \.png$ {
root /data/images;
}

表示匹配以 .png 结尾的请求。

这是正则匹配,适合做文件后缀类规则。


4)普通前缀匹配

1
2
3
location /api/ {
proxy_pass http://127.0.0.1:8080;
}

这就是最普通的前缀匹配。
只要 URI 以 /API/ 开头,就可以匹配上。


2.4 一定要记住的优先级

你先记这个就够了:

  1. = 精确匹配
  2. ^~ 前缀匹配
  3. ~ 正则匹配
  4. 普通前缀匹配,选最长的那个

举个例子:

1
2
3
4
5
location = /api/test { ... }
location ^~ /api/ { ... }
location ~ ^/api/.*\.do$ { ... }
location /api/ { ... }
location / { ... }

请求 /API/test:

  • 先看有没有 = 精确匹配
  • 有,就直接用
  • 后面都不用看了

请求 /API/user/list.do:

  • 没有 = 精确匹配
  • 如果有 ^~ /API/,那它会直接命中,正则也不看了
  • 如果没有 ^~,那才会继续看 ~ 正则

这块别靠背,靠理解。
本质就是:

Nginx 先尽量找更明确的规则,越明确优先级越高。


三、反向代理


3.1 proxy_pass 的基本用法

Nginx 做反向代理最核心的指令就是:

1
proxy_pass

基本写法:

1
2
3
location /api/ {
proxy_pass http://127.0.0.1:8080;
}

这表示:

  • 客户端访问 /API/xxx
  • Nginx 把请求转发给 127.0.0.1:8080

你可以先粗暴理解成:

location 负责拦请求,proxy_pass 负责把请求送给后端。


3.2 带斜杠 vs 不带斜杠的路径差异

这是经典坑,必须搞透。
很多人线上路径错乱,就是死在这里。

看两个配置:

写法一:proxy_pass 不带斜杠

1
2
3
location /api/ {
proxy_pass http://127.0.0.1:8080;
}

请求:

1
/api/user/list

转发后会变成:

1
http://127.0.0.1:8080/api/user/list

也就是说:

原始请求路径会整体带过去。


写法二:proxy_pass 带斜杠

1
2
3
location /api/ {
proxy_pass http://127.0.0.1:8080/;
}

请求:

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
2
3
location /api/ {
proxy_pass http://127.0.0.1:8080;
}
  • /API/userhttp://127.0.0.1:8080/API/user
1
2
3
location /api/ {
proxy_pass http://127.0.0.1:8080/;
}
  • /API/userhttp://127.0.0.1:8080/user

这个地方的本质就是:

你到底想保留 /API 这段路径,还是想把它裁掉。

后端项目如果接口本身就是 /API/user,那你通常不裁。
后端项目如果接口其实是 /user,只是想对外统一暴露成 /API/user,那你就裁。


3.5 透传真实 IP

反向代理之后,后端服务看到的客户端 IP,往往会变成 Nginx 的 IP。
这就会导致:

  • 日志里拿不到真实用户 IP
  • 风控、限流、审计可能失效

所以通常要把真实客户端 IP 通过请求头传给后端。

常见写法:

1
2
3
4
location /api/ {
proxy_pass http://127.0.0.1:8080;
proxy_set_header X-Real-IP $remote_addr;
}

这里:

  • 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
2
3
4
5
6
7
8
9
10
11
12
13
14
upstream backend {
server 127.0.0.1:8080;
server 127.0.0.1:8081;
server 127.0.0.1:8082;
}

server {
listen 80;
server_name localhost;

location /api/ {
proxy_pass http://backend;
}
}

这表示:

  • backend 是一个服务器组名
  • Nginx 收到 /API/ 请求后,转发给这个组里的某一台机器

4.2 三种策略


1)轮询(默认)

如果你什么都不配,默认就是轮询。

1
2
3
4
upstream backend {
server 127.0.0.1:8080;
server 127.0.0.1:8081;
}

请求会大致按顺序分给各个服务器:

  • 第一个给 8080
  • 第二个给 8081
  • 第三个再给 8080
  • 第四个再给 8081

这是最基础的策略。

适合:

  • 机器性能差不多
  • 请求处理耗时也差不多

2)ip_hash

1
2
3
4
5
upstream backend {
ip_hash;
server 127.0.0.1:8080;
server 127.0.0.1:8081;
}

这个策略会根据客户端 IP 做哈希,让同一个 IP 的请求尽量落到同一台机器。

它的价值在于:

让同一个用户更稳定地落到同一台后端。

适合一些希望“会话粘性”更强的场景。

但你也得知道它的问题:

  • 某些 IP 请求特别多时,可能会压歪某台机器
  • 负载不一定均匀

3)least_conn

1
2
3
4
5
upstream backend {
least_conn;
server 127.0.0.1:8080;
server 127.0.0.1:8081;
}

这个策略会优先把请求分给当前连接数更少的服务器。

适合:

  • 请求处理时间差异较大
  • 某些请求很慢,轮询不够聪明

因为轮询只看顺序,不看谁忙谁闲;
least_conn 会更倾向把请求给当前没那么忙的机器。


4.3 weightmax_failsbackup 参数


weight

weight 表示权重。

1
2
3
4
upstream backend {
server 127.0.0.1:8080 weight=3;
server 127.0.0.1:8081 weight=1;
}

这表示 8080 分到的请求大约是 8081 的 3 倍。

适合:

  • 机器性能不一样
  • 想让配置更高的机器多扛点流量

一句话:

weight 越大,分到的请求通常越多。


max_fails

1
2
3
4
upstream backend {
server 127.0.0.1:8080 max_fails=3;
server 127.0.0.1:8081;
}

它表示:

在一定失败判断周期内,允许最大失败次数。

你现阶段不用死抠底层判定细节,只要知道用途就行:

  • 某台机器连续失败太多次
  • Nginx 会认为它不太靠谱
  • 暂时少给它甚至不给它分请求

实习阶段记住它是失败容忍阈值就够了。


backup

1
2
3
4
upstream backend {
server 127.0.0.1:8080;
server 127.0.0.1:8081 backup;
}

backup 表示备用服务器

正常情况下,请求主要打到普通服务器。
只有当主要服务器不可用时,才会启用 backup 机器。

这很好理解:

backup 不是主力,是替补。


五、静态资源


5.1 root vs alias 的路径拼接差异

这又是一个经典坑。
不搞懂,静态资源路径一定配错。


root

root 的逻辑是:

把 location 匹配到的 URI,直接拼到 root 指定目录后面。

例如:

1
2
3
location /img/ {
root /data;
}

请求:

1
/img/a.png

最终找的文件路径是:

1
/data/img/a.png

注意,是把整个 URI /img/a.png 拼到 /data 后面。


alias

alias 的逻辑是:

用 alias 指定的目录,直接替换掉当前 location 前缀。

例如:

1
2
3
location /img/ {
alias /data/images/;
}

请求:

1
/img/a.png

最终找的文件路径是:

1
/data/images/a.png

这里 /img/ 没被拼上去,而是被替换掉了。


5.2 为什么这俩老让人翻车

你看下面两个配置:

root

1
2
3
location /static/ {
root /usr/share/nginx/html;
}

请求:

1
/static/app.js

对应文件:

1
/usr/share/nginx/html/static/app.js

alias

1
2
3
location /static/ {
alias /usr/share/nginx/html/;
}

请求:

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
2
3
4
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
}

它的含义是:

  1. 先找当前 URI 对应的文件
  2. 再找当前 URI 对应的目录
  3. 还找不到,就返回 /index.html

为什么这样就行?

因为 SPA 的真正入口一般就是 index.html
只要把它返回给浏览器,前端路由就能接管后续页面渲染。

所以这句:

1
try_files $uri $uri/ /index.html;

本质上是在说:

先看看有没有真文件;没有的话,别急着 404,交给前端应用自己处理。


5.5 gzip on 基本配置

gzip 就是响应压缩。
它的意义很直接:

减小传输体积,加快页面资源加载。

基础配置可以这样写:

1
2
3
4
5
http {
gzip on;
gzip_types text/plain text/css application/javascript application/json;
gzip_min_length 1024;
}

几个核心点你知道就够了:

  • gzip on;
    开启压缩
  • gzip_types
    指定哪些类型参与压缩
  • gzip_min_length 1024;
    小于 1024 字节的不压缩,太小了压了也没啥意义

实习阶段,你只要知道 gzip 是减少传输体积的就够了。


六、常用命令

这几个命令是最常用的,必须会。

1
2
3
nginx -t          # 检查配置
nginx -s reload # 平滑重载
nginx -s quit # 优雅停止

6.1 nginx -t

1
nginx -t

作用:

检查配置文件是否正确。

你每次改完配置,别上来就 reload。
-t,这是规矩,不是礼貌。

它会帮你检查:

  • 配置语法有没有写错
  • 配置文件能不能正常加载

这是最基础、也最该养成习惯的一步。


6.2 nginx -s reload

1
nginx -s reload

作用:

平滑重载配置。

什么叫平滑?

就是:

  • 让 Nginx 重新加载新配置
  • 尽量不中断已有服务

这也是线上最常用的方式。
改完配置,检查通过,再 reload。

典型流程:

1
2
nginx -t
nginx -s reload

6.3 nginx -s quit

1
nginx -s quit

作用:

优雅停止 Nginx。

所谓优雅,就是:

  • 不再接收新请求
  • 已有请求尽量处理完
  • 然后再退出

这比粗暴强停要稳得多。


不需要现在学的

下面这些内容,不是说不重要,而是你现在先别把脑子搞炸:

  • location rewrite
  • 限流
  • HTTPS 证书配置
  • 日志切割

原因很简单:

你现在最该先打牢的,是入口转发、路径匹配、代理规则、静态资源这套最基础的骨架。

proxy_pass 斜杠都没整明白,就去搞证书和限流,那属于还没学会走路就想漂移。


结尾

作为后端实习生,Nginx 这一阶段最该吃透的,不是背很多配置项,而是把下面几件事真正搞明白:

  • Nginx 到底站在请求链路的哪一层
  • serverlocation 到底怎么匹配
  • proxy_pass 带不带斜杠到底差在哪
  • upstream 怎么把请求分给多个后端
  • rootalias 为什么会把静态资源路径搞乱
  • try_files 为什么能兜住 SPA 路由

你把这些吃透,已经比一堆只会复制配置的人强一截了。
很多人配 Nginx,像在庙里摇签。你别学他们,你要知道每一行到底在干什么。