双 upstream 架构
这一课你将能做到
- 读懂并口述 CodeRoadMap「双 upstream」:
coderoadmap_backend 与 coderoadmap_platform 的端口与职责
- 对照
prod.conf.example,把浏览器 URL 映射到「哪个进程、改写后哪条路径」
- 说明本地直连、Docker 服务名、单机
127.0.0.1 三种写法如何替换 upstream,而不改前端 /api 前缀
为什么要学
单看 proxy_pass 还不够:生产是 两个命名上游 + 多个 server(www / 裸域跳转 / admin) 的拓扑。
platform 用相对路径 /api 调后端,靠 同源 避开 CORS;同源成立的前提就是 Nginx 把两个进程合成一个对外站点。
本课把拓扑、请求表、与 Docker/本机差异一次对齐,SSL 与安全头是在此骨架上长出来的叶子。
先建立图像
https://www.coderoadmap.cn
│
┌────┴────┐
│ Nginx │ :443 TLS
└────┬────┘
│
┌────────┴────────┐
│ │
/api/* 其他
│ │
▼ ▼
upstream upstream
backend:8080 platform:3000
Spring Boot Next.js
│
└──► MySQL / Redis …
| 浏览器 URL | 命中 location | 上游 | 上游 URI |
|---|
/tracks/nginx-essentials | / | :3000 | 同左 |
/api/tracks | /api/ | :8080 | /tracks |
/api/auth/login | /api/ | :8080 | /auth/login |
admin 的 /index.html | admin / | 磁盘 root | — |
admin 的 /api/... | admin /api/ | :8080 | 剥前缀 |
人话:对外一个域名,对内两栋楼;门岗按「路径工牌」分楼。
跟着做
步骤 1:朗读两段 upstream
deploy/nginx/prod.conf.example 开头:
upstream coderoadmap_platform {
server 127.0.0.1:3000;
keepalive 64; # 复用到 Next 的连接
}
upstream coderoadmap_backend {
server 127.0.0.1:8080;
keepalive 64; # 复用到 Spring 的连接
}
笔记:「名字是给 location 用的逻辑名;IP/端口可随环境替换。」
步骤 2:对照 www 主 server
server {
listen 443 ssl http2;
server_name www.coderoadmap.cn;
client_max_body_size 20m;
# add_header … 见安全头课
location /api/ {
proxy_pass http://coderoadmap_backend/;
proxy_http_version 1.1;
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_set_header X-Forwarded-Proto $scheme;
}
location / {
proxy_pass http://coderoadmap_platform;
proxy_http_version 1.1;
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_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
口头走查:GET /tracks/redis-essentials 从 Nginx 到 MySQL 的链路(页面在 Next,数据经浏览器再打 /api/... 或服务端 fetch)。
步骤 3:理解「同源 /api」
platform 侧 API 客户端通常以 /api 为前缀(如 platform/src/lib/api.ts 一类封装)。浏览器只看见 www 一个源:
- 无浏览器 CORS 预检烦恼(同站)
- Cookie / 凭证策略更简单
- 换机器部署时,前端不必写死
http://ip:8080
应看到:双 upstream 是产品体验与安全边界的共同选择。
步骤 4:域名与跳转块
listen 80:http → https://www…$request_uri
server_name coderoadmap.cn 的 443:裸域 → www
统一 canonical,避免 cookie 域与 SEO 分裂。细节加强在 SSL 课。
步骤 5:Docker 网络替换演练(笔记)
Compose 内 Nginx 与应用同网时,upstream 常改为:
upstream coderoadmap_backend {
server backend:8080; # 服务名,非 127.0.0.1
keepalive 64;
}
upstream coderoadmap_platform {
server platform:3000;
keepalive 64;
}
宿主机 Nginx + 容器映射端口时,则仍可能是 127.0.0.1:宿主映射端口。
关键:改 upstream,不改浏览器路径约定。
步骤 6:若删掉 /api/ 会怎样
- 请求
/api/tracks 落入 location / → Next
- Next 无此 API 路由 → 404 或 HTML
- Spring 完全收不到
结论:/api/ location 不是装饰,是分流开关。
讲清楚要点
- 因为页面与 API 分属不同运行时,所以用两个 upstream 而不是一个万能端口——职责清晰、可独立扩缩。
- 因为要对浏览器保持同源,所以对外只暴露 Nginx 域名,路径内分流——前端继续用
/api。
- 因为 admin 也要调同一套 REST,所以 admin server 复用
coderoadmap_backend——静态壳 + 同一 Java。
- 因为 keepalive 在 upstream 声明,所以高 QPS 下减少对 8080/3000 的握手——配合
proxy_http_version 1.1。
- 因为环境不同(本机/Compose/K8s),所以变的是 server 地址,不是 location 语义——配置可移植。
- 因为 80/裸域跳转与主站分离成多个 server,所以改证书或跳转不影响 location 逻辑——块职责单一。
- 因为官方 upstream 就是为反代与负载均衡设计,所以先单实例双上游,再学多实例——下一课之后扩展。
你可能会懵的地方
-
Next 服务端组件自己请求 API,还走 Nginx 吗?
取决于你怎么配:有的用集群内 http://backend:8080 直连;浏览器侧仍走公网 /api。两种可以并存,注意别把内网 URL 泄露到浏览器包。
-
为什么不把 API 放到 api.coderoadmap.cn 子域?
可以,但是 CORS 与 cookie 域要额外设计。本站选择 同域路径分流,配置与前端更简单。
-
keepalive 64 是不是越大越好?
过大占连接与文件描述符;过小失去复用。先与官方示例同量级,再按监控调(见缓存性能课)。
-
test.conf 与 prod.conf 差在哪?
域名、证书、跳转复杂度、keepalive 数值等;双 upstream + /api/ + / 结构一致。见 deploy/nginx/test.conf.example。
错法对照
| 错法 | 问题 | 正解 |
|---|
前端写死 http://127.0.0.1:8080 | 生产跨域/错误主机 | 浏览器只用相对 /api |
| upstream 写成公网 IP 且开放 8080 | 绕过 Nginx 安全边界 | 应用绑内网/回环 |
只配 platform 不配 /api/ | API 全 404/HTML | 保留剥前缀的 /api/ |
Compose 仍用 127.0.0.1 | 容器内指到自己 | 用服务名 DNS |
| www 与 admin 各起一套重复 Java | 运维翻倍 | 共享 coderoadmap_backend |
自测三问
coderoadmap_backend 与 coderoadmap_platform 默认端口各是多少?
- 浏览器请求
/api/tracks 与 /tracks/foo 分别由谁处理?上游 URI 有何不同?
- 为什么说双 upstream 能减少浏览器 CORS 问题?
小结一句话
双 upstream 是 CodeRoadMap 核心拓扑:API 8080、页面 3000,同源路径分流写在 prod.conf.example。
官方延伸