Docker + Nginx 负载均衡部署实战:从概念拆解到 7 个真实踩坑复盘
本文记录了我把自己的 Spring Boot 项目从「单体直连 8080」改造为「Nginx 统一入口 + 三实例负载均衡」的完整过程。前半部分用大白话讲清 Nginx 三大核心概念并逐行拆解真实配置;后半部分是部署中真实踩过的 7 个坑及排查思路。所有配置均已验证可用,可直接复用。
〇、项目背景与目标
项目名称: 超市后台管理系统(Spring Boot)
技术栈: Spring Boot + MySQL + Redis + RabbitMQ + Nginx + Docker
部署环境: VMware Ubuntu 虚拟机(桥接模式)
初始状态:
- 单体 Spring Boot 容器直接暴露 8080 端口
- 用户直接访问
http://IP:8080
目标状态:
- Nginx 作为唯一入口(80 端口)
- 静态资源(HTML/CSS/JS/图片)由 Nginx 直接返回
- 动态请求通过 Nginx 负载均衡分发到 3 个 Spring Boot 实例
- 支持局域网内多设备访问
架构演进对比:
【改造前】
用户浏览器 → Spring Boot:8080 → MySQL/Redis/RabbitMQ
【改造后】
用户浏览器 → Nginx:80
├── 静态页面/图片 → Nginx 直接返回(不走 Java)
└── 动态接口 → upstream 负载均衡
├── market-app1:8080
├── market-app2:8080
└── market-app3:8080
↓
MySQL + Redis + RabbitMQ
一、三大核心概念(大白话版)
1.1 反向代理:酒店前台 vs 代购
既然有反向代理,那就有正向代理,它俩到底啥区别?先从正向代理说起。
正向代理 = 代购。 你想买国外的高档货,但自己出不了国,于是找渠道商:你把购买请求发给代购,代购去找国外商家下单,买到后再交付到你手上。
关键点: 是你主动找的代购。国外商家根本不知道你是谁,它只认识代购。
用技术话说:正向代理代理的是客户端。目标服务器看到的「买家」是代理服务器,而不是你。
反向代理 = 酒店前台。 你节假日去酒店,问前台有没有空房。前台转身去查后台系统、问经理,然后给你答复。
关键点: 你以为前台就是「酒店本身」,根本不知道后台有几台电脑在干活。前台只是门面,真正处理请求的是它背后的系统。
用技术话说:反向代理代理的是服务器。你以为自己在直连目标服务器,其实是 Nginx 接住请求,再分发给后端的真实服务器。
1.2 负载均衡:网红餐馆的调度员
节假日你来到一家网红餐馆,食客爆满。为了维持生意,餐馆不可能只雇一个厨师——不然早累崩盘了——必须多招几个分摊压力。
但问题来了:客人来了,谁去安排给哪个厨师?这时候就需要一个「前台调度员」统筹安排。这个调度员,就是 Nginx。
说白了,负载均衡就是把一大堆请求像分菜一样合理分给后端多台服务器:每台都干活,但又不至于把某一台累死。
关键点: 没有负载均衡,所有请求怼同一台服务器,访问量一大,服务器直接躺平给你看 502。
1.3 动静分离:KFC 的保温柜
你走进 KFC 说要一份薯条。前台小哥头也不回,从旁边保温柜抽出一包早就炸好的薯条递给你——全程不惊动后厨,秒出餐。
你又说还要一个香辣鸡腿堡。前台一看保温柜里没有,得现做,于是把订单甩给后厨:烤面包、炸鸡排、挤沙拉酱……做好后再端到你手上。
- 薯条 = 静态资源(图片、CSS、JS、HTML):「预制」好的,不需要后厨再加工,Nginx 自己从硬盘里拿出来就给你,速度飞快。
- 汉堡 = 动态资源(查数据库、跑业务逻辑的接口):必须后厨现做,Nginx 只负责把订单转交给后厨。
关键点: 为什么要动静分离?如果每个人买包薯条前台都要跑去后厨问一句,后厨得疯,前台得瘫。该前台自己搞定的事,别去烦后厨——整家店效率直接起飞。
二、配置文件逐块拆解
概念懂了,现在上实战。下面是我项目里真实的 nginx.conf,逐块拆开讲,可以直接改成自己的。
2.1 events 块:决定能抗多少并发
events {
worker_connections 1024; # 每个 worker 进程最多同时处理 1024 个连接
use epoll; # Linux 下最高效的 I/O 多路复用模型
multi_accept on; # 高并发:来一堆连接,一次性全接收
}
2.2 MIME 类型:文件类型字典
include /etc/nginx/mime.types;
default_type application/octet-stream;
浏览器请求文件时,Nginx 要告诉它「这是 CSS」「这是图片」。mime.types 就是一本文件类型字典,查不到就按默认二进制流处理。
关键点: 没有这一行,浏览器可能把 CSS 当纯文本显示,页面直接崩给你看。
2.3 日志格式:门口的安检登记本
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
一长串变量看着头大,一个一个来:
| 变量 | 含义 |
|---|---|
$remote_addr |
谁来的(IP 地址) |
$remote_user |
HTTP 认证的用户名 |
$time_local |
什么时候来的 |
$request |
干了啥(GET /api/xxx HTTP/1.1) |
$status |
结果咋样(200 成功 / 404 找不到 / 500 崩了) |
$body_bytes_sent |
传了多少字节 |
$http_referer |
从哪个页面跳转来的 |
$http_user_agent |
用的啥浏览器 |
$http_x_forwarded_for |
前面有 CDN 时,这才是用户真实 IP |
2.4 日志路径与错误级别
access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log warn; # 只记录警告及以上
Nginx 错误级别从低到高排排坐:
debug → info → notice → warn → error → crit → alert → emerg
2.5 性能优化四件套
sendfile on; # 零拷贝:内核直接发文件,数据不经过 Nginx 进程,省一次拷贝
tcp_nopush on; # 攒一波数据再发,减少网络包数量,降低网络堵塞
tcp_nodelay on; # 小数据立即发,不等着攒包,降低延迟
keepalive_timeout 65; # TCP 长连接保持 65 秒
nopush 和 nodelay 看着矛盾,其实互补:前者在传大文件时攒够了再发,后者在连接建立后有小数据就立马发。一个管「省」,一个管「快」。
2.6 Gzip 压缩
gzip on;
gzip_vary on;
gzip_min_length 1k;
gzip_types text/plain text/css application/json application/javascript text/xml;
关键点: 上面「加速四件套 + Gzip」属于万能模板,自己写 conf 时直接抄即可。
2.7 upstream 块:负载均衡落地
还记得第一章说的「前台调度员」吗?这里就是它的工位:
upstream backend {
server market-app1:8080 weight=3;
server market-app2:8080 weight=3;
server market-app3:8080 weight=3;
}
market-app1:8080:market-app1是 Docker Compose 里的服务名。Nginx 和后端在同一个 Docker 网络里,服务名可以直接解析到容器 IP;8080 是 Spring Boot 的端口。weight=3:三台权重相同,等于平均分配:每来 9 个请求,1、2、3 号各分 3 个。
后端变成多台之后,登录状态会打架——所以我在 Spring Boot 里用 Redis 共享 Session(详见踩坑 4)。
2.8 server 块:虚拟主机
server {
listen 80;
server_name localhost;
server_name 指定这个虚拟主机响应哪个域名。现在是 localhost,以后买了域名改成你的域名就行。
如果有多个 server 块,Nginx 收到请求后先看 Host 头里的域名去匹配 server_name;都匹配不上,就走第一个 server 块(默认服务器)。
2.9 安全响应头:抄上就行
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
防止点击劫持和 MIME 嗅探攻击。不用理解太深,先抄上,安全感 +1。
2.10 location 匹配优先级(重点!)
这是整篇配置里最容易踩坑的地方,先背下来再上菜:
=精确匹配(优先级最高,命中即停)^~前缀匹配(匹配成功后,跳过所有正则 location)~和~\*正则匹配(~区分大小写,~\*不区分)- 普通前缀匹配(无符号,如
location /images/,优先级最低)
2.11 用户上传的图片:动静分离现场
location ^~ /images/ {
alias /data/market/images/;
expires 30d;
access_log off;
}
为什么必须用 ^~: 假设写成普通前缀匹配,用户请求 /images/logo.svg 时,Nginx 先命中 /images/,但还会继续检查正则,发现 .svg 匹配后面的正则 location,结果被「截胡」——而正则里的 root 路径根本不是图片目录,最终喜提 404。^~ 就是防截胡的。
alias 和 root 的区别: alias 是替换,root 是拼接。请求 /images/avatar.jpg 时:
alias /data/market/images/ → 找 /data/market/images/avatar.jpg ✅
root /data/market/images/ → 找 /data/market/images/images/avatar.jpg ❌
2.12 静态资源 + 兜底路由
location ~* \.(css|js|woff|woff2|ttf|eot|svg|ico)$ {
root /usr/share/nginx/html;
expires 7d;
access_log off;
}
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ @backend; # 兜底
}
try_files 按顺序检查:请求的文件在不在?目录在不在?都不在?那就跳转到命名 location @backend,交给后端处理。
2.13 @backend:反向代理本体
location @backend {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_connect_timeout 30s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
}
proxy_pass 后面的 backend 就是 2.7 里 upstream 的名字,请求由此分流到三台后端。重点讲讲 proxy_set_header 三连——不传的后果很严重:
| 请求头 | 不传的后果 |
|---|---|
Host $host |
后端不知道用户访问的是哪个域名,虚拟主机匹配失败 |
X-Real-IP $remote_addr |
后端看到的全是 Nginx 内网 IP,日志全废,IP 黑名单失效 |
X-Forwarded-For |
前面还有 CDN 时,后端无法追溯原始用户 IP |
至于超时三件套,看单词名就知道场景:connect 是后端挂了或网络不通,send 是上传大文件接收慢,read 是处理太久(比如复杂 SQL)。30 秒可按需调整。
2.14 健康检查:给监工留个门
location /nginx-health {
access_log off;
return 200 "Nginx is running\n";
add_header Content-Type text/plain;
}
给「监工」们留的门:云负载均衡器(SLB/ELB)探活、Docker 的 HEALTHCHECK、运维监控都靠它。
2.15 配置改完怎么生效?
不用进容器改配置,关键是挂载:把宿主机的配置文件挂进容器。改完后 Nginx 支持热重载,不用重启、丝滑生效:
docker exec my-nginx nginx -s reload # 热重载,推荐
docker compose restart my-nginx # 或者直接重启容器
2.16 docker-compose 完整配置
注意 volumes 里的挂载关系,和 nginx.conf 里的路径是一一对应的:
market-app1:
build: .
image: market-app:latest # 构建时打标签,供 app2/app3 复用
container_name: market-app1
networks:
- market-db
- market-web
volumes:
- ./images:/app/images
- ./remove:/app/remove
environment:
- SPRING_PROFILES_ACTIVE=docker
market-app2:
image: market-app:latest # 直接复用已构建的镜像,不再重复构建
container_name: market-app2
networks:
- market-db
- market-web
market-app3:
image: market-app:latest
container_name: market-app3
networks:
- market-db
- market-web
nginx:
image: nginx:alpine
container_name: market-nginx
ports:
- "80:80"
volumes:
- ./nginx/conf/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/html:/usr/share/nginx/html:ro
- ./nginx/logs:/var/log/nginx
- ./images:/data/market/images:ro
networks:
- market-web
depends_on:
- market-app1
- market-app2
- market-app3
三、七个真实踩坑复盘(按遇到顺序)
这部分是全文最有价值的部分。概念可以背,但排查问题的思路只能在坑里练出来。每个坑我都按「现象 → 原因 → 解决 → 认知」记录。
坑 1:多实例部署时,app2/app3 构建失败
【现象】
docker compose up -d --build 时,market-app2 和 market-app3 报错:failed to resolve reference "docker...."
【原因】
market-app1配置了build: .和image: market-app:latestmarket-app2只配置了image: market-app:latest- 但 Docker Compose 并行启动时,app2/app3 在 app1 构建完成前就尝试拉取镜像,此时镜像还不存在。
【核心认知】
同一个项目跑多个实例,不需要构建多次镜像。Docker 镜像是「模板」,容器是「实例」。先构建一次镜像,后面多个服务复用这个镜像即可。
【修改对比】
# 修改前(错误):三个服务都 build,重复构建浪费时间
market-app1: { build: . }
market-app2: { build: . }
market-app3: { build: . }
# 修改后(正确):只构建一次,其余复用
market-app1: { build: ., image: market-app:latest } # 构建时打标签
market-app2: { image: market-app:latest } # 直接复用
market-app3: { image: market-app:latest }
【延伸知识点】
- 容器内部端口默认互相隔离,不冲突
- 只有需要「从外部访问容器」时才用
ports: "8080:8080" - app2/app3 不需要暴露端口,Nginx 通过 Docker 内部网络访问
坑 2:页面能打开,但所有图片 404
【现象】
浏览器 F12 Network 面板全是红色,default.png 等图片报 404。直接访问 8080 端口时图片正常,通过 Nginx 80 端口时图片消失。
【原因】
Nginx 的 location 匹配优先级导致请求被「抢错路」:
= 精确匹配 > ^~ 前缀匹配(匹配后停止搜索正则) > ~ / ~* 正则匹配 > 普通前缀匹配
修改前的配置:
location /images/ { ... } # 普通前缀匹配(优先级最低)
location ~* \.(png|jpg|...)$ { root /usr/share/nginx/html; } # 正则匹配(优先级更高)
当浏览器请求 /images/default.png 时,正则优先级更高,最终用了第二个 location,但 root 指向 /usr/share/nginx/html,而图片实际在 /data/market/images/,结果 404。
【修改对比】
# 修改前
location /images/ { ... }
# 修改后:加 ^~,匹配后不再检查正则;同时正则里去掉图片后缀
location ^~ /images/ { alias /data/market/images/; }
location ~* \.(css|js|woff|woff2|ttf|eot|svg|ico)$ { root /usr/share/nginx/html; }
【大白话】 ^~ 就是「这条路我包了,后面的正则别来抢」。图片请求统一走 /images/ 这个入口,CSS/JS 走正则匹配。
坑 3:改了 nginx.conf,容器内看到的还是旧配置
【现象】
宿主机上 cat nginx.conf 已经是最新内容,但进入容器 docker exec 后 cat /etc/nginx/nginx.conf 还是旧的。
【原因】Bind Mount 的 inode 问题。
很多编辑器保存文件时,底层操作是:
- 把新内容写入临时文件
- 删除原文件
- 把临时文件重命名为原文件名
这个操作导致原文件的 inode(文件在磁盘上的唯一标识)变了。Docker 的 bind mount 挂载的是「旧的 inode」,所以容器里看到的还是旧内容。
【解决方案】
# 方案 A:重启容器(重建挂载关系)
docker compose restart nginx
# 方案 B:彻底重建
docker compose down && docker compose up -d
# 方案 C:用 echo 直接覆盖(不创建新 inode)
echo "新配置内容" > nginx.conf
【教训】 修改挂载的配置文件后,如果 reload 不生效,先检查容器内是否真的是最新内容。
坑 4:登录成功,刷新后变回「游客」⭐(多实例必考)
【现象】
输入账号密码登录 → 显示登录成功 → 按 F5 刷新 → 右上角变回「游客」 → 无法进入 admin 后台。
【原因】Session 不共享——多实例部署的致命问题。
Spring Boot 默认把 Session 存在自己内存里(JVM 堆内存)。配了 3 个 market-app 实例后:
- 登录请求 → 落到 app1 → Session 存在 app1 内存里
- 刷新请求 → 落到 app2 → app2 没有这个 Session → 认为没登录
直接访问 8080 端口时只有一台 app,所以没问题。通过 Nginx 80 端口时,请求被轮询分到 3 台,Session 丢了。
【临时解决方案】ip_hash(粘性会话):
upstream backend {
ip_hash; # 同一 IP 固定落到同一台后端
server market-app1:8080;
server market-app2:8080;
server market-app3:8080;
}
原理:Nginx 根据用户 IP 做哈希计算,同一个 IP 永远访问同一台后端,这样 Session 就不会丢了。
【缺点】
- 不是真正的负载均衡(流量可能分配不均)
- 如果某台 app 挂了,对应用户也受影响
- 同一局域网内多个用户可能共享一个公网 IP,都被分到同一台
【最终解决方案】Spring Session Redis:
让 Session 不再存在各自内存里,而是统一存到 Redis:
@EnableRedisHttpSession // 启动类加这个注解
@SpringBootApplication
public class MarketApplication { ... }
- 登录请求 → 落到 app1 → Session 存到 Redis
- 刷新请求 → 落到 app2 → 从 Redis 读取 Session → 认识用户
三台实例共享同一个 Session 存储,彻底解决。
坑 5:本地能登录的账号,Docker 环境报密码错误
【现象】
本地 IDEA 运行项目,用 lzh/123456 能正常登录。Docker 环境访问,同样的账号报 Bad credentials。
【原因】Docker MySQL 和本地 MySQL 是两个独立数据库。
- 本地开发时连的是
localhost:3306(Windows 上的 MySQL) - Docker 环境里连的是
market-mysql:3306(容器内的 MySQL)
这两个数据库表结构可能一样,但数据完全独立!本地注册的 lzh 用户,Docker 数据库里可能不存在,或者密码不同。
【验证方法】
docker exec -it market-mysql mysql -uroot -proot supermarket
SELECT username, password FROM sys_user;
【解决】 在 Docker 环境里重新注册账号,或把本地数据导出导入。
坑 6:NAT 改桥接后,Docker 构建报错代理拒绝连接
【现象】
把 VMware 从 NAT 改成桥接后,docker compose build 报错:
proxyconnect tcp: dial tcp 192.168.50.1:7890: connect: connection refused
【原因】代理地址变了。
- NAT 模式下: 虚拟机通过 VMnet8 网卡访问 Windows,代理地址是
192.168.50.1(VMnet8 网关) - 桥接模式下: 虚拟机直接接入物理网络,和 Windows 是平等关系,不再经过 VMnet8,原来的
192.168.50.1就访问不到了
【解决】 把 proxy.sh 里的 HOST_IP 改成 Windows 在桥接网络下的实际 IP:
# 修改前
HOST_IP="192.168.50.1"
# 修改后:Windows 以太网实际 IP
HOST_IP="192.168.1.151"
# 验证代理是否通
curl -x http://192.168.1.151:7890 https://www.google.com -I
坑 7:桥接模式只能同网关访问
【现象】
同 WiFi 的朋友能访问 192.168.1.71,隔壁宿舍的朋友访问不了。
【原因】内网 IP 的局限性。
192.168.x.x 是私有地址,只在当前路由器范围内有效。跨路由器(不同网关)时,对方的路由器不认识这个 IP,直接丢弃。
【认知升级】 这也是为什么要买云服务器:
- 云服务器有公网 IP(如
123.45.67.89),全世界任何网络都能访问 - 配合域名 + HTTPS,就是完整的线上服务
四、常见状态码排查手册(Nginx 排障 Tips)
部署这套架构后,遇到问题不要慌——状态码就是 Nginx 给你的第一条线索。下面 5 个高频状态码按「含义 → 排查步骤 → 解决」整理,基本覆盖了反向代理场景 90% 的报错。
413:上传文件太大
含义: 请求体超过了允许的最大体积,常见于上传图片/文件。
解决: 要同时改两层——Nginx 和应用各自都有一道闸门,只改一层没用。
# Nginx 层:http / server / location 块里都可以加
client_max_body_size 20m;
# Spring Boot 层:application.yml
spring:
servlet:
multipart:
max-file-size: 20MB # 单个文件上限
max-request-size: 100MB # 单次请求总上限
记忆点: 改完 Nginx 要
nginx -s reload,改完 Spring Boot 要重启应用。
502:后端挂了 / 连不上
含义: Nginx 作为代理,连不上后端服务(连接被拒绝/后端宕机),拿到的是无效响应。
排查步骤:
# 1. 看容器是不是还活着
docker ps # 看不到就用 docker ps -a 看是否已退出
# 2. 看后端容器日志,找崩溃原因
docker logs market-app1 --tail 100
# 3. 检查 Nginx 和后端是否在同一个 Docker 网络
docker network inspect market-web
# 4. 在 Nginx 容器里直接测试后端通不通
docker exec market-nginx wget -qO- http://market-app1:8080
常见原因:
- 后端容器挂了或根本没启动(镜像构建失败、启动报错)
- Nginx 和后端不在同一个 Docker 网络,
upstream里的服务名解析不到 - 后端端口写错(容器内是 8080,不是宿主机映射端口)
504:后端太慢,Nginx 等待超时
含义: Nginx 连上了后端,但等响应等到超时。和 502 的区别:502 是「连不上」,504 是「等不及」。
解决: 双管齐下——先放宽 Nginx 的等待时间止血,再回头优化后端治本。
location @backend {
proxy_connect_timeout 30s;
proxy_send_timeout 30s;
proxy_read_timeout 120s; # 后端有慢接口就调大这个
}
后端优化方向:慢 SQL 加索引、大循环改批量、耗时操作异步化(如丢给 RabbitMQ)。
记忆点: 只调大 timeout 不优化后端,等于把闹钟往后调——问题还在,只是报得晚了。
403:没有权限
含义: 请求合法,但服务器拒绝提供服务。静态资源场景下,十有八九是文件系统权限问题。
排查与解决:
# 1. 查看文件/目录权限和属主
ll /data/market/images/
# 2a. 权限位不够:给读权限(Nginx worker 默认以 nobody/nginx 用户运行)
chmod -R 755 /data/market/images/
# 2b. 属主不对:直接改属主
chown -R nginx:nginx /data/market/images/
常见原因:
- 宿主机挂载进容器的目录,宿主机上属主是 root,容器内 Nginx worker 用户读不了
- 目录缺少
x(执行)权限——目录的执行权限代表「可进入」,只给r不给x照样 403
404:前端路由丢失 / 文件找不到
含义: 请求的资源不存在。分两种情况排查:
情况一:文件真的不存在
# 直接进容器确认文件在不在、路径对不对
docker exec market-nginx ls /usr/share/nginx/html/
# 重点检查 alias/root 拼接出的最终路径(区别见 2.11)
情况二:SPA 前端路由丢失(刷新 404)
单页应用(Vue/React)用 history 路由时,/user/list 这种路径是前端路由,服务器上根本没有这个文件——直接访问或刷新就会 404。
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html; # 文件/目录都找不到?返回首页,交给前端路由处理
}
注意区分: 转发给后端的兜底写法是
try_files $uri $uri/ @backend(见 2.12),SPA 首页兜底是try_files $uri $uri/ /index.html,按项目实际情况二选一或组合使用。
速查总表
| 状态码 | 一句话定位 | 第一反应 |
|---|---|---|
| 413 | 文件太大 | client_max_body_size + Spring Boot multipart 配置,两层都要改 |
| 502 | 后端挂了/连不上 | docker ps 看容器 → docker logs 看日志 → docker network inspect 看网络 |
| 504 | 后端太慢 | 调大 proxy_read_timeout 止血 + 优化后端治本 |
| 403 | 没有权限 | ll 看权限 → chmod / chown |
| 404 | 文件不存在/路由丢失 | 进容器确认文件存在;SPA 项目加 try_files $uri $uri/ /index.html |
五、核心知识点速查清单
- 容器端口隔离: 不
-p映射就不会和宿主机冲突 - 镜像 vs 容器: 镜像构建一次,多个服务复用
- Nginx location 优先级:
=>^~>~\*> 普通前缀 - Bind mount inode 问题: 编辑器保存可能改变 inode,需重启容器
- Session 共享: 多实例必须解决,
ip_hash是临时方案,Redis Session 是最终方案 - Docker 数据库独立: 容器化后数据环境完全隔离,需重新初始化或同步
- 桥接 vs NAT: 桥接获得局域网 IP,NAT 只能本机访问
- 代理配置随网络模式变化: 桥接后代理地址要改为宿主机实际 IP
六、面试高频问题自查(由本次实战延伸)
如果把这次部署写进简历,以下问题大概率会被问到,都可以从本文找到答案:
| 面试问题 | 对应本文位置 |
|---|---|
| 正向代理和反向代理的区别? | 1.1 |
| 为什么做动静分离?好处是什么? | 1.3 |
Nginx location 匹配优先级?^~ 干嘛的? |
2.10 / 坑 2 |
alias 和 root 的区别? |
2.11 |
proxy_set_header 为什么要传 Host 和 X-Real-IP? |
2.13 |
| 负载均衡有哪些策略?(轮询 / weight / ip_hash) | 2.7 / 坑 4 |
| 多实例下 Session 怎么共享? | 坑 4 |
sendfile 零拷贝是什么? |
2.5 |
| Docker 多实例怎么复用镜像? | 坑 1 |
| 容器间怎么通信?为什么要自定义 network? | 2.7 / 2.16 |
| 502 和 504 有什么区别?怎么排查? | 第四章 |
| 上传文件报 413 怎么办? | 第四章 |
七、后续计划
- 接入 Spring Session Redis(
@EnableRedisHttpSession)✅(已完成,见坑 4) - 购买云服务器 + 域名,部署到公网
- Jenkins CI/CD 自动化构建部署
- Prometheus + Grafana 监控体系
写在最后
不要只看教程,亲手部署一次比看十遍都有用。
Nginx 的配置语法不难,难的是理解请求流转路径和各种边界情况。建议拿自己的真实项目练手,踩的坑才是自己的。每个 404、每个连接超时、每次 Session 丢失,都是理解系统架构的机会。
如果这篇笔记对你有帮助,欢迎点赞收藏;有讲得不对的地方,求路过的大佬评论区指点方向 orz(万分感谢)。