一条请求是如何穿过 DNS、Nginx、网关与微服务的:从域名到接口的完整链路讲透

作者:用户提供草稿 整理时间: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-serviceorder-service
  • 它只负责告诉客户端“先去敲哪一扇大门”

也就是说:

  • DNS 解决的是外部域名到入口 IP 的映射
  • 它不解决内部微服务之间的发现问题

2. Nginx:统一接入层

请求到达入口 IP 之后,通常先进入 Nginx。 在这层里,最常见的职责包括:

  • TLS / SSL 终止
  • 统一域名入口
  • 静态资源处理
  • 反向代理
  • 按域名或路径分流
  • 不同业务、不同环境的配置隔离

它更像系统的一楼总前台:负责接待、安检、基础分流,但不适合承载过多业务治理逻辑。

3. 微服务网关:统一治理与路由

Nginx 把请求转到网关之后,请求才真正进入微服务世界。 网关通常负责:

  • 统一鉴权
  • 日志记录
  • 限流熔断
  • 灰度发布
  • 路由匹配
  • 服务发现
  • 负载均衡转发

Nginx 负责“把请求带进来”,网关负责“把请求分配到正确的微服务,并做统一治理”。

4. 微服务:真正处理业务

到了具体服务,例如 user-service,才会进入真正执行业务逻辑的阶段。 此时命中的通常是:

  • Controller
  • Service
  • Repository
  • 缓存
  • 数据库

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-serviceorder-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.com
  • www.example.com
  • admin.example.com

2. 按业务模块拆成多个 include 文件

例如:

  • api.conf
  • web.conf
  • admin.conf

这样做的价值很实际:

  • API、网站、后台的职责天然分开
  • 改某一块配置时不容易影响另一块
  • 可以为不同域名配置不同安全策略
  • 后续扩展 file.example.comopenapi.example.commonitor.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/**

请求到来后,网关会依次完成:

  1. 匹配路由,确认这条请求要找 user-service
  2. 向注册中心查询 user-service 的可用实例列表
  3. 按负载均衡策略选一个实例
  4. 把请求转发到选中的地址

例如:

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 负责接入,网关负责治理,微服务负责真正办事。