作者:用户提供草稿 整理时间:2026-04-21 11:05:42 CST 来源:ChatGPT 分享链接 主题:把一次 HTTP 请求从 DNS 解析、Nginx 入口层、微服务网关、服务发现到具体 Controller 接口的完整流转过程串成一条可落地、可排错、可讲解的主线。
核心问题
假设用户在浏览器里访问:
https://api.example.com/api/user/profile?id=1001
我们希望它最终到达:
user-service 的 /api/user/profile 接口
一个企业里最常见、也最值得真正讲明白的请求链路,通常就是这样一条主线:
客户端
-> DNS
-> Nginx 入口层
-> 微服务网关
-> 目标微服务
-> Controller 接口
-> 返回结果
如果只记一句话,可以记成:
DNS 负责找入口,Nginx 负责接入与基础转发,网关负责治理与服务路由,微服务负责真正执行业务。
先建立整体视角:一次请求到底经过哪些层
把这条链路拆开看,大致分成五层:
1. DNS:把域名指向统一入口
浏览器先做 DNS 查询,例如:
api.example.com -> 10.10.10.20
这里的 10.10.10.20 往往不是某个具体业务服务,而是:
- Nginx 所在机器
- 负载均衡 VIP
- 某个统一接入层地址
所以 DNS 的职责非常明确:
- 它不关心后面有多少微服务
- 它不知道
user-service或order-service - 它只负责告诉客户端“先去敲哪一扇大门”
也就是说:
- DNS 解决的是外部域名到入口 IP 的映射
- 它不解决内部微服务之间的发现问题
2. Nginx:统一接入层
请求到达入口 IP 之后,通常先进入 Nginx。 在这层里,最常见的职责包括:
- TLS / SSL 终止
- 统一域名入口
- 静态资源处理
- 反向代理
- 按域名或路径分流
- 不同业务、不同环境的配置隔离
它更像系统的一楼总前台:负责接待、安检、基础分流,但不适合承载过多业务治理逻辑。
3. 微服务网关:统一治理与路由
Nginx 把请求转到网关之后,请求才真正进入微服务世界。 网关通常负责:
- 统一鉴权
- 日志记录
- 限流熔断
- 灰度发布
- 路由匹配
- 服务发现
- 负载均衡转发
Nginx 负责“把请求带进来”,网关负责“把请求分配到正确的微服务,并做统一治理”。
4. 微服务:真正处理业务
到了具体服务,例如 user-service,才会进入真正执行业务逻辑的阶段。 此时命中的通常是:
ControllerServiceRepository- 缓存
- 数据库
5. 响应按原路返回
结果会沿原路返回:
user-service -> gateway -> nginx -> 浏览器
这也是为什么排查链路问题时,不能只盯着某一层,而要顺着整条返回路径去看。
下面这张图,可以把“入口层、治理层、注册中心、业务服务层、资源层”之间的关系一次看清:
flowchart TB
A[客户端 Browser / App]
B[DNS<br/>api.example.com → Nginx VIP]
subgraph Access[接入层]
C[Nginx 集群<br/>多 server 配置<br/>SSL终止 / 反向代理 / 域名分流]
end
subgraph Gateway[网关治理层]
D[API Gateway<br/>统一鉴权 / 限流 / 日志 / 路由转发]
E[服务发现 + 负载均衡<br/>lb://user-service<br/>lb://order-service]
end
subgraph Registry[注册中心]
F[Nacos / Eureka / Consul]
end
subgraph Services[业务服务层]
G[UserService<br/>Controller: /api/user/profile]
H[OrderService<br/>Controller: /api/order/detail]
end
subgraph Resource[资源层]
I[(MySQL)]
J[(Redis)]
end
A --> B --> C --> D --> E
E -.查询实例列表.-> F
F -.返回可用实例.-> E
E --> G
E --> H
G --> I
G --> J
H --> I
H --> J
一次完整请求的时序演示
现在把整条链路按时间顺序串起来。
第一步:用户发起请求
https://api.example.com/api/user/profile?id=1001
浏览器发起一个 HTTPS 请求:
GET /api/user/profile?id=1001 HTTP/1.1
Host: api.example.com
第二步:DNS 解析域名
浏览器先查 DNS:
api.example.com -> 10.10.10.20
这一步只说明: 客户端应该先访问 10.10.10.20 这个统一入口。
第三步:请求到达 Nginx
Nginx 根据 Host: api.example.com 命中对应的 server 配置。 如果 API 域名对应的配置是把流量转给网关,那么请求会被转发到类似:
http://gateway_cluster
例如被分配到:
10.10.20.11:8080
第四步:网关匹配路由
网关收到路径:
/api/user/profile?id=1001
然后按路由规则匹配,例如:
- id: user-service-route
uri: lb://user-service
predicates:
- Path=/api/user/**
这里说明两件事:
- 这条请求应该交给
user-service - 交付方式不是写死某个 IP,而是走服务发现与负载均衡
第五步:网关通过服务发现找到实例
网关看到:
lb://user-service
含义可以直接理解成:
去找名叫
user-service的服务,并从它的可用实例里选一个。
如果注册中心里登记了两个实例:
user-service:
- 10.10.50.21:8081
- 10.10.50.22:8081
网关会先拿到这个实例列表,再按负载均衡策略选出其中一个,例如:
10.10.50.21:8081
第六步:微服务处理具体接口
假设 user-service 中有这样一个接口:
@RestController
@RequestMapping("/api/user")
public class UserController {
@GetMapping("/profile")
public Map<String, Object> profile(@RequestParam("id") Long id) {
Map<String, Object> result = new HashMap<>();
result.put("code", 0);
result.put("msg", "success");
Map<String, Object> data = new HashMap<>();
data.put("id", id);
data.put("name", "Alice");
data.put("level", "VIP");
result.put("data", data);
return result;
}
}
那么完整路径就是:
/api/user/profile?id=1001
返回结果可能是:
{
"code": 0,
"msg": "success",
"data": {
"id": 1001,
"name": "Alice",
"level": "VIP"
}
}
第七步:响应回传给客户端
最后结果按原路返回:
user-service -> gateway -> nginx -> 浏览器
浏览器最终看到的就是上面的 JSON。
如果想把这条请求放到更接近真实企业环境的时序里看,可以对应到下面这张图:
sequenceDiagram
autonumber
participant Client as 客户端
participant DNS as DNS
participant Nginx as Nginx入口层
participant Gateway as API Gateway
participant Registry as 注册中心(Nacos/Eureka)
participant Trace as 链路追踪(SkyWalking/Zipkin)
participant LB as 负载均衡器
participant UserSvc as UserService
participant OrderSvc as OrderService
participant Redis as Redis
participant DB as MySQL
participant MQ as Kafka
participant CB as 熔断/限流(Sentinel)
participant Mesh as 灰度发布(Istio/网关灰度)
Client->>DNS: 查询 api.example.com
DNS-->>Client: 返回 Nginx VIP / IP
Client->>Nginx: HTTPS请求 /api/user/profile?id=1001
Nginx->>Gateway: 反向代理转发请求\n透传Host/X-Forwarded-For
Gateway->>Trace: 上报入口Span
Gateway->>CB: 鉴权 / 限流 / 熔断检查
CB-->>Gateway: 通过
Gateway->>Gateway: 路由匹配 /api/user/**
Gateway->>Registry: 按服务名查询 user-service
Registry-->>Gateway: 返回可用实例列表
Gateway->>LB: 根据策略选择实例
LB-->>Gateway: 返回目标实例
Gateway->>Mesh: 判断是否命中灰度规则
alt 命中灰度
Mesh-->>Gateway: 路由到 UserService-v2
else 未命中灰度
Mesh-->>Gateway: 路由到 UserService-v1
end
Gateway->>UserSvc: 转发请求 /api/user/profile?id=1001
UserSvc->>Trace: 上报服务Span
UserSvc->>Redis: 查询缓存 user:1001
alt 缓存命中
Redis-->>UserSvc: 返回用户缓存数据
else 缓存未命中
Redis-->>UserSvc: miss
UserSvc->>DB: 查询用户表
DB-->>UserSvc: 返回用户数据
UserSvc->>Redis: 回写缓存 user:1001
end
opt 异步事件通知
UserSvc->>MQ: 发送用户访问事件 / 审计日志
end
UserSvc-->>Gateway: 返回业务响应
Gateway->>Trace: 上报出口Span
Gateway-->>Nginx: 返回响应
Nginx-->>Client: 返回最终结果 JSON
Note over Gateway,Registry: 服务发现:根据服务名查找实例
Note over Gateway,LB: 负载均衡:轮询/随机/权重/一致性哈希
Note over UserSvc,Redis: 优先读缓存,减少数据库压力
Note over UserSvc,MQ: 非核心链路异步化,削峰解耦
把这条链路画成一张结构图
浏览器
-> DNS
api.example.com -> 10.10.10.20
-> Nginx 入口层
1. SSL 终止
2. 域名接入
3. 反向代理到网关
-> 微服务网关
1. 统一鉴权
2. 限流 / 日志 / 灰度
3. 路由到目标服务
-> user-service
处理 /api/user/profile
-> 数据库 / 缓存
如果用更偏企业架构的比喻来记:
- DNS 像地图导航,告诉你公司大楼在哪
- Nginx 像一楼总前台,负责接待和分流
- 网关像总调度台,负责身份校验和业务路由
- 微服务像具体办事部门
- Controller 像部门里的某个具体窗口
一个最小可落地的示例
下面用一套最小配置,把这条链路真正落到配置文件里。
1. DNS 示例
api.example.com A 10.10.10.20
www.example.com A 10.10.10.20
admin.example.com A 10.10.10.20
含义是:
api.example.com:API 请求入口www.example.com:前端网站入口admin.example.com:管理后台入口
它们都先指向同一个 Nginx 入口层。
2. Nginx 主配置
worker_processes auto;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
upstream gateway_cluster {
server 10.10.20.11:8080;
server 10.10.20.12:8080;
}
upstream web_cluster {
server 10.10.30.11:3000;
server 10.10.30.12:3000;
}
include /etc/nginx/conf.d/*.conf;
}
这里最关键的是两点:
- 把上游服务集群抽象成
upstream - 用
include /etc/nginx/conf.d/*.conf把具体业务拆成多个配置文件
把它翻成人话就是:
gateway_cluster代表“API 请求先去的那一组网关机器”web_cluster代表“前端页面请求先去的那一组 Web 服务”conf.d/*.conf则决定“不同域名到底走哪一条路由”
3. API 域名配置
/etc/nginx/conf.d/api.conf
server {
listen 80;
server_name api.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name api.example.com;
ssl_certificate /etc/nginx/ssl/api.example.com.crt;
ssl_certificate_key /etc/nginx/ssl/api.example.com.key;
access_log /var/log/nginx/api_access.log main;
location / {
proxy_pass http://gateway_cluster;
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;
}
}
这个配置的作用是:
- 对
api.example.com做 HTTPS 接入 - 把所有 API 请求统一转发到网关
- 保留客户端真实 IP 与原始 Host
这一点很关键: api.conf 这一层通常还不直接区分 user-service 和 order-service,而是先把整个 api.example.com 的流量都送到 gateway_cluster。
也就是说:
https://api.example.com/api/user/profile?id=1001
-> 命中 api.conf
-> proxy_pass http://gateway_cluster
-> 再由网关判断去 user-service
以及:
https://api.example.com/api/order/detail?id=90001
-> 命中 api.conf
-> proxy_pass http://gateway_cluster
-> 再由网关判断去 order-service
所以 Nginx 在这里按“域名入口”做第一层分流,网关再按“业务路径”做第二层分流。
4. 网站域名配置
/etc/nginx/conf.d/web.conf
server {
listen 80;
server_name www.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name www.example.com;
ssl_certificate /etc/nginx/ssl/www.example.com.crt;
ssl_certificate_key /etc/nginx/ssl/www.example.com.key;
location / {
proxy_pass http://web_cluster;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
这个配置说明:
- 前端页面入口和 API 入口是分开的
- 即使都经过同一台 Nginx,也可以用不同域名拆开职责
对应到真实请求就是:
https://www.example.com/index.html
-> 命中 web.conf
-> proxy_pass http://web_cluster
-> 直接交给前端服务
这里不会先经过微服务网关,因为它处理的是站点页面流量,不是统一 API 入口流量。
5. 管理后台配置
/etc/nginx/conf.d/admin.conf
server {
listen 443 ssl;
server_name admin.example.com;
ssl_certificate /etc/nginx/ssl/admin.example.com.crt;
ssl_certificate_key /etc/nginx/ssl/admin.example.com.key;
location / {
proxy_pass http://gateway_cluster;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
后台同样可以走网关,这样统一权限控制和治理能力会更清晰。
例如:
https://admin.example.com/user/list
-> 命中 admin.conf
-> proxy_pass http://gateway_cluster
-> 再由网关继续做权限和路由控制
这也是很多企业系统里后台域名单独拆一个 Nginx 配置的原因。
把 Nginx 多配置和真实路由关系直接对上
如果把上面三份配置和真实请求一一对上,可以更直观看出它们各自负责哪一段路由:
1. API 域名走网关
请求:
https://api.example.com/api/user/profile?id=1001
命中:
api.conf
Nginx 动作:
proxy_pass http://gateway_cluster
下一跳:
Gateway 再根据 /api/user/** 路由到 user-service
2. 网站域名走前端集群
请求:
https://www.example.com/index.html
命中:
web.conf
Nginx 动作:
proxy_pass http://web_cluster
下一跳:
前端服务直接返回页面
3. 后台域名也可以先走网关
请求:
https://admin.example.com/user/list
命中:
admin.conf
Nginx 动作:
proxy_pass http://gateway_cluster
下一跳:
Gateway 再做统一鉴权、权限控制和服务路由
所以这里最容易看清的一点是:
- Nginx 先按
server_name做域名级分流 - 网关再按
Path做业务级分流 - 最终微服务按
Controller路径命中具体接口
这正是原始链路里最核心的三层路由关系。
为什么 Nginx 往往会拆成多个配置
这类拆分一般有两种维度:
1. 按不同域名拆成多个 server
例如:
api.example.comwww.example.comadmin.example.com
2. 按业务模块拆成多个 include 文件
例如:
api.confweb.confadmin.conf
这样做的价值很实际:
- API、网站、后台的职责天然分开
- 改某一块配置时不容易影响另一块
- 可以为不同域名配置不同安全策略
- 后续扩展
file.example.com、openapi.example.com、monitor.example.com也更顺手
网关配置应该怎么理解
下面用 Spring Cloud Gateway 举例。
一种更直观的路由写法
server:
port: 8080
spring:
application:
name: gateway-service
cloud:
gateway:
routes:
- id: user-service-route
uri: lb://user-service
predicates:
- Path=/api/user/**
- id: order-service-route
uri: lb://order-service
predicates:
- Path=/api/order/**
这种写法的好处是:
- 外部路径是什么,内部服务路径也是什么
- 排查问题时更直接
- 少了前缀剥离带来的路径错位问题
为什么有些人会被 StripPrefix 绕晕
如果网关写成:
- id: user-service-route
uri: lb://user-service
predicates:
- Path=/user/**
filters:
- StripPrefix=1
那么外部请求:
/user/profile?id=1001
转发到服务时会变成:
/profile?id=1001
这并不是错,但要求服务内部接口也按这个路径组织。 如果你的服务实际上暴露的是:
/api/user/profile
那就很容易在这里出 404。
因此,对很多团队来说,更稳定的做法是:
- 外部路径直接使用
/api/user/** - 服务内部同样保持
/api/user/** - 尽量减少额外的前缀改写
user-service 最小接口示例
服务配置:
server:
port: 8081
spring:
application:
name: user-service
接口示例:
@RestController
@RequestMapping("/api/user")
public class UserController {
@GetMapping("/profile")
public String profile() {
return "user profile ok";
}
}
这意味着只要请求最终到达 user-service,并保持路径为:
/api/user/profile
它就会被正确命中。
网关到底是怎么按“名字”找到微服务的
这是很多人第一次接触微服务时最容易混淆的一点。
在网关里你通常写的不是:
uri: http://10.10.20.31:8081
而是:
uri: lb://user-service
这里的 user-service 是服务名。 lb:// 的含义不是一个普通协议,而是:
通过服务发现拿到这个服务的实例列表,再通过负载均衡选一个实例转发过去。
把它理解成“通讯录查询”
这个过程很像企业通讯录:
- 服务名 = 人名
- 注册中心 = 通讯录
- 实例地址 = 电话号码
- 负载均衡 = 这次决定联系谁
网关手里只有名字:
我要找 user-service
注册中心里记录着:
user-service:
- 10.10.20.31:8081
- 10.10.20.32:8081
网关拿到列表后,再决定这次转发给谁。
注册中心解决的是什么问题
它解决的是:
服务名 -> 实例地址列表
也就是说,它处理的是内部服务发现。 这和 DNS 处理的:
域名 -> 入口 IP
根本不是一回事。
所以最简洁的区分是:
- DNS:找入口
- 注册中心:找服务实例
服务是如何注册进去的
比如在 Spring Cloud + Nacos 体系里,服务启动时会用自己的服务名向注册中心登记:
server:
port: 8081
spring:
application:
name: user-service
cloud:
nacos:
discovery:
server-addr: 10.10.10.100:8848
其中最关键的是:
spring.application.name: user-service
这个名字就是注册中心看到的服务名。
网关如何发现它
网关自身也要接入注册中心,例如:
spring:
application:
name: gateway-service
cloud:
nacos:
discovery:
server-addr: 10.10.10.100:8848
然后路由里写:
spring:
cloud:
gateway:
routes:
- id: user_route
uri: lb://user-service
predicates:
- Path=/api/user/**
请求到来后,网关会依次完成:
- 匹配路由,确认这条请求要找
user-service - 向注册中心查询
user-service的可用实例列表 - 按负载均衡策略选一个实例
- 把请求转发到选中的地址
例如:
http://10.10.20.32:8081/api/user/profile?id=1001
一次真实链路的完整串联
把所有概念拼回一条链路,就是:
https://api.example.com/api/user/profile?id=1001
-> DNS 解析到 10.10.10.20
-> Nginx 命中 api.example.com 对应配置
-> Nginx 转发到 gateway_cluster
-> 网关匹配 Path=/api/user/**
-> 网关根据 lb://user-service 查询注册中心
-> 拿到 user-service 实例列表
-> 负载均衡选择一个实例
-> 请求进入 user-service
-> 命中 /api/user/profile
-> 返回 JSON
-> gateway -> nginx -> 浏览器
这才是“从域名到接口”的完整链路。
为什么不是只有 Nginx,而是 Nginx + 网关
很多人都会问:
既然 Nginx 也能按路径转发,为什么不让它直接转到各个微服务?
例如:
location /api/user/ {
proxy_pass http://user_service_cluster;
}
location /api/order/ {
proxy_pass http://order_service_cluster;
}
这样当然能跑,但企业里通常不会只停在这一步。 核心原因在于:Nginx 适合做入口接入,网关更适合做微服务治理。
Nginx 更擅长的事情
- 域名接入
- HTTPS 终止
- 基础反向代理
- 静态文件服务
- 简单路径转发
网关更擅长的事情
- 微服务路由
- JWT / Token 鉴权
- 用户身份透传
- 限流熔断
- 灰度发布
- 服务发现
- 统一日志与链路追踪
所以常见架构是:
DNS -> Nginx -> Gateway -> Services
而不是:
DNS -> Nginx -> 每个微服务
这不是为了“多一层而多一层”,而是为了把接入层职责和微服务治理职责分开。
三个最典型的请求示例
1. 用户服务请求
https://api.example.com/api/user/profile?id=1001
链路:
DNS
-> api.example.com = 10.10.10.20
Nginx(api.conf)
-> gateway_cluster
Gateway
-> user-service
user-service
-> /api/user/profile
2. 订单服务请求
https://api.example.com/api/order/detail?id=90001
链路:
DNS
-> api.example.com = 10.10.10.20
Nginx(api.conf)
-> gateway_cluster
Gateway
-> order-service
order-service
-> /api/order/detail
3. 前端网站请求
https://www.example.com/index.html
链路:
DNS
-> www.example.com = 10.10.10.20
Nginx(web.conf)
-> web_cluster
前端服务
-> 返回页面
这正是多个 Nginx 配置文件有价值的地方: 不同域名可以进入完全不同的流量通道。
真实工作里最常见的排错点
这部分往往比“会配”更重要。
1. DNS 正常,但页面打不开
可能原因:
- 域名解析对了,但 Nginx 没监听
- 防火墙没开
80/443 - HTTPS 证书配置错误
2. Nginx 正常,但返回 502 / 504
可能原因:
proxy_pass指向错了- 网关没启动
- upstream 后端不可达
- 网络不通
3. 到了网关,但返回 404
可能原因:
Path路由没匹配到- 前缀剥离错了
- 网关转发路径和服务内部接口路径不一致
最典型的例子就是:
- 外部请求是
/api/user/profile - 网关转发后变成
/profile - 但服务内部只提供
/api/user/profile
结果自然就是 404。
4. 网关能转发,但服务报权限错误
可能原因:
- token 没带
- Nginx 没透传关键请求头
- 网关过滤器把认证信息丢了
在一些场景里,Nginx 至少要保留这些头:
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 Authorization $http_authorization;
其中 Authorization 这一条在涉及 JWT、Bearer Token 或网关鉴权时尤其关键。
最后用两句标准表述收束全文
如果要在汇报或讲解里用一句比较标准的话来概括整条链路,可以这样说:
一个完整的外部请求首先通过 DNS 将业务域名解析到 Nginx 入口节点,Nginx 作为统一接入层负责 HTTPS 终止、反向代理和基础流量分发。随后,请求被转发至微服务网关,由网关完成统一鉴权、路由匹配、限流和日志追踪等治理能力,再根据请求路径将流量分发到对应的微服务实例。最终,微服务内部的 Controller 接口处理具体业务逻辑,并将结果按原链路返回给客户端。
如果只保留一句更“人话”的总结,可以记成:
网关根据“名字”找到微服务,不是靠 DNS,而是靠注册中心。DNS 负责找系统入口,注册中心负责找服务实例,Nginx 负责接入,网关负责治理,微服务负责真正办事。