Web 请求链路与微服务架构

从 DNS、Nginx、Gateway 到 Tomcat

Posted by Ekko on November 18, 2025

这篇笔记围绕典型 Java Web / 微服务部署链路展开,目标是把一次请求从域名解析、入口流量接入、网关转发、服务调用到 Tomcat 与 Spring MVC 处理的完整路径串起来,重点说明每一层到底负责什么。

文中主要聚焦传统 Web 集群与 Spring Cloud 微服务体系下的分层关系、请求主路径和常见性能参数,不展开 Kubernetes Ingress、Service Mesh、容器编排等更高一层的平台侧能力。

参考资料:

官方文档:Nginx Reverse ProxySpring Cloud Gateway ReferenceSpring Cloud Commons LoadBalancer

组件文档:Apache Tomcat HTTP ConnectorSpring Framework DispatcherServletNacos Spring Cloud

[TOC]


graph TD
    A[用户] --> B[DNS轮询/全局负载均衡]
    B --> C[负载均衡器集群<br>云LB/F5/HAProxy]
    C --> D[Nginx 集群<br>10.0.1.10 ~ 10.0.1.12]
    D --> E[Gateway 集群<br>192.168.1.10 ~ 192.168.1.12]

    subgraph F [服务治理层]
        G[服务注册中心集群<br>Nacos/Eureka]
    end
    
    E -. 服务发现 .-> G
    
    subgraph H [业务微服务集群]
        I[用户服务集群<br>3节点]
        J[订单服务集群<br>5节点]
        K[商品服务集群<br>4节点]
        L[支付服务集群<br>3节点]
    end
    
    E --> I
    E --> J
    E --> K
    E --> L
    
    subgraph M [分布式数据层]
        N[MySQL集群<br>主从复制]
        O[Redis集群<br>分片]
        P[Elasticsearch集群<br>分布式]
    end

    I --> N
    J --> N
    J --> O
    K --> O
    K --> P

这里最容易混淆的点,是“请求经过哪些层”和“哪些组件只是提供治理信息”:

  • Nginx、Gateway、业务服务实例通常处在主请求链路上,真实流量会穿过它们。
  • 注册中心负责服务注册、服务发现和健康状态同步,通常不承载每个业务请求的数据转发
  • Tomcat 是业务服务内部的 Web 容器,请求到达某个服务实例之后,才会进入 Tomcat 和 Spring MVC。

常见角色分工

概念 典型实现 主要职责 与其他层的边界
硬件服务器 Dell PowerEdge、HP ProLiant 提供 CPU、内存、磁盘、网卡等物理资源 属于基础设施,通常不直接承载业务语义
云服务器 ECS、EC2、虚拟机 在 IaaS 层提供可弹性分配的计算实例 本质上仍是资源载体,不等于 Web 服务器或应用服务器
Web 服务器 Nginx、Apache 处理 HTTP/HTTPS 接入、反向代理、静态资源分发、SSL 终止 更偏流量入口层,不承载主要业务逻辑
网关 Spring Cloud Gateway、Kong 鉴权、限流、路由、灰度、统一入口治理 位于应用层入口,负责把请求转到正确的后端服务
应用服务器 Tomcat、Jetty、Undertow 运行 Java Web 应用,遵循 Servlet 规范 处在业务服务内部,负责接住已经路由到本实例的请求
服务注册中心 Nacos、Eureka 维护服务实例元数据、健康状态与发现信息 提供治理信息,不直接执行业务请求

请求到响应

flowchart TD
    A[用户浏览器] --> B[1. 点击按钮/提交表单]
    B --> C[2. DNS 解析域名]
    C --> D[3. 请求到达云 LB / Nginx]

    D --> E[4. Gateway<br>认证/路由/限流]
    E --> F[5. 匹配路由规则与服务名]
    F --> G[6. LoadBalancer 选择实例]

    subgraph H [Spring Cloud 微服务集群]
        G --> I[7. 路由到具体服务实例<br>Service A]
        G --> J[Service B]
        G --> K[Service C]
    end
    
    I --> L[8. Tomcat 接收请求]
    L --> M[9. Spring MVC 调度]
    M --> N[10. Controller / Service 执行业务]
    N --> O[11. 返回 JSON 或页面]
    O --> P[12. 响应沿原路返回]
    P --> A
    
    subgraph Q [服务治理]
        R[注册中心<br>Nacos/Eureka]
    end

    E -. 周期拉取或订阅实例信息 .-> R

这条链路里有一个容易误解的地方:Gateway 或服务消费者不一定会在每次请求时实时访问注册中心。更常见的实现方式,是通过 DiscoveryClient 周期刷新或订阅服务列表,再由 LoadBalancer 基于本地缓存的实例信息做负载均衡决策。


Nginx 工作

graph TD
    A[外部请求<br>120.79.100.201:443] --> B[服务器网络接口]
    B --> C[操作系统内核<br>TCP/IP协议栈]
    C --> D[端口分发器]
    
    D --> E{Nginx Worker进程<br>正在监听 0.0.0.0:443}
    E --> F[接受连接]
    F --> G[处理HTTPS请求]
    
    G --> H[SSL解密]
    H --> I[根据nginx.conf路由]
    I --> J[反向代理到localhost:8080]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# /etc/nginx/conf.d/api.shop.com.conf

# HTTP服务 - 监听80端口
server {
    # 关键配置:监听所有IP的80端口
    listen 80;
    # 指定这个server块处理哪个域名的请求
    server_name api.shop.com;
    
    # 将HTTP请求重定向到HTTPS
    return 301 https://$server_name$request_uri;
}

# HTTPS服务 - 监听443端口  
server {
    # 关键配置:监听所有IP的443端口,并启用SSL
    listen 443 ssl;
    server_name api.shop.com;
    
    # SSL证书配置
    ssl_certificate /etc/ssl/certs/api.shop.com.crt;
    ssl_certificate_key /etc/ssl/private/api.shop.com.key;
    
    # SSL协议配置
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:...;
    
    # 业务路由配置
    location /api/ {
        # 反向代理到 Spring Cloud Gateway
        proxy_pass http://localhost:8080;
        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 /static/ {
        root /var/www/html;
        expires 30d;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 1. 客户端发起HTTPS请求
curl https://api.shop.com/api/users/123

# 2. 服务器网络层面
请求到达 120.79.100.201:443
↓
操作系统TCP栈接收数据包
↓
检查哪个进程在监听443端口 → 找到Nginx Worker进程
↓
将连接交给Nginx

# 3. Nginx处理阶段
Nginx接受SSL连接
↓
SSL握手、证书验证、密钥交换
↓
解密HTTPS数据,得到原始HTTP请求
↓
根据nginx.conf路由到对应的location块
↓
反向代理到 http://localhost:8080/api/users/123

# 4. 内部转发
建立到localhost:8080的新HTTP连接
↓
Spring Cloud Gateway 接收请求
↓
后续微服务处理...

proxy_pass http://localhost:8080; 这里只是示意 Nginx 可以把入口流量反向代理到本机 Gateway。在多节点部署中,更常见的写法是把上游配置成 upstream gateway_cluster,由 Nginx 在多个 Gateway 节点之间继续做反向代理或健康检查。


负载均衡分层

graph LR
    A[客户端] --> B[第一层: 全局负载均衡<br>DNS/云LB]
    B --> C[第二层: 反向代理负载均衡<br>Nginx/HAProxy]
    C --> D[第三层: 网关动态路由<br>Spring Cloud Gateway]
    D --> E[第四层: 微服务客户端负载均衡<br>Ribbon/LoadBalancer]
    E --> F[具体服务实例]
层级 技术 作用 特点
第一层 DNS轮询、云LB 流量分发到不同机房/区域 基于地理位置的负载均衡
第二层 Nginx、HAProxy 反向代理,分发到网关集群 7层负载,支持SSL终止、静态缓存
第三层 Spring Cloud Gateway 微服务路由、认证、限流 基于服务名的动态路由
第四层 Ribbon、LoadBalancer 服务实例级别的负载均衡 客户端负载均衡,更灵活

Nginx 和 Gateway 的边界

组件 主要关注点 常见能力 典型边界
Nginx 网络入口与 HTTP 接入 SSL 终止、反向代理、静态资源、连接复用、限速 更靠近流量入口,通常不理解具体微服务业务
Gateway 应用层入口与服务路由 鉴权、路由、限流、熔断、灰度、统一过滤链 更靠近业务服务发现,理解服务名、路由规则和应用级策略

如果只有单体应用,Nginx 后面可以直接转发到 Tomcat;如果是微服务体系,Nginx 与 Gateway 经常同时存在,前者解决入口接入问题,后者解决服务治理问题。


Gateway 路由与客户端负载均衡

sequenceDiagram
    participant C as 客户端
    participant G as Gateway<br/>(路由决策)
    participant L as LoadBalancer<br/>(实例选择)
    participant D as DiscoveryClient 缓存
    participant E as 服务注册中心
    participant U1 as user-service<br/>实例A
    participant U2 as user-service<br/>实例B

    Note over U1,U2: 服务实例已注册到注册中心
    U1->>E: 注册 user-service
    U2->>E: 注册 user-service

    Note over G,D: Gateway 本地维护路由规则与服务发现缓存
    C->>G: GET /api/users/1
    Note over G: 1. 路由决策<br/>Path=/api/users/** → user-service
    G->>L: 2. 选择 user-service 实例
    L->>D: 3. 读取 user-service 实例列表
    opt 首次加载或缓存刷新
        D->>E: 订阅/拉取最新实例列表
        E-->>D: 返回 [实例A, 实例B]
    end
    D-->>L: 4. 返回 [实例A, 实例B]
    Note over L: 5. 负载均衡<br/>选择实例A
    L-->>G: 返回实例A地址
    G->>U1: 6. 转发请求到实例A
    U1-->>G: 返回响应
    G-->>C: 返回响应

Ribbon 属于较早期的 Netflix 技术栈,在较新的 Spring Cloud 体系中更常见的是 Spring Cloud LoadBalancer。如果项目仍在使用 Ribbon,可以把这一层理解成“客户端侧的实例选择器”;如果项目已升级,只是具体实现从 Ribbon 切换到了新的 LoadBalancer。


Tomcat 和 Spring MVC

sequenceDiagram
    participant C as 客户端 (Browser)
    participant T as Tomcat
    participant D as DispatcherServlet (Spring MVC 核心)
    participant H as HandlerMapping
    participant Co as Controller
    participant S as Service
    participant V as ViewResolver
    participant J as JSP/HTML

    Note over T, D: 1. Tomcat 接收并解析请求
    C->>T: HTTP Request (GET /users/1)
    T->>D: 生成 HttpServletRequest/Response<br>并传递给 DispatcherServlet

    Note over D, H: 2. Spring MVC 处理请求
    D->>H: 查询处理请求的 Controller 方法
    H-->>D: 返回 @GetMapping("/users/{id}") 方法

    Note over D, Co: 3. 执行业务逻辑
    D->>Co: 调用 Controller 方法,并传递参数
    Co->>S: 调用 Service 层业务逻辑
    S-->>Co: 返回业务数据 (User对象)
    Co-->>D: 返回视图名或对象

    alt 页面渲染场景
        Note over D, V: 4A. 渲染视图
        D->>V: 解析视图名 "userDetail"
        V-->>D: 返回对应的 JSP/HTML 页面
        D->>J: 将模型数据填充到视图
        J-->>D: 生成最终的 HTML
        D-->>T: 返回 HTML 内容
    else REST API 场景
        Note over D: 4B. HttpMessageConverter 序列化对象
        D-->>T: 返回 JSON / XML 响应体
    end

    Note over T, C: 5. 返回响应
    T-->>C: HTTP Response

连接桥梁 - DispatcherServlet

这是连接 Tomcat 和 Spring MVC 的关键组件

Tomcat 是一个容器,属于基础设施,认Servlet规范

DispatcherServlet 就是 Spring MVC 按照Servlet标准工作的代表

对于现在更常见的 Spring Boot REST 服务,这条链路通常不会经过 ViewResolver,而是直接由 HttpMessageConverter 把对象序列化成 JSON 返回。只有在 JSP、Thymeleaf 这类页面渲染场景下,视图解析器才会成为主路径上的核心环节。


Tomcat 参数

graph TD
    A[客户端请求] --> B{连接数检查}
    B -->|当前连接 < max-connections| C[建立TCP连接]
    B -->|当前连接 >= max-connections| D[拒绝连接或等待]
    
    C --> E{线程池检查}
    E -->|有空闲线程| F[分配线程处理请求]
    E -->|无空闲线程| G[请求在连接中等待]
    
    F --> H[业务处理]
    H --> I[返回响应]
    I --> J[释放线程]
    J --> K[保持或关闭连接]
参数名 默认值 推荐值 说明
max-threads 200 根据 CPU 核数与请求类型评估,常见范围 200 到 800 Tomcat 工作线程池的最大线程数。值越大并不一定越好,线程过多会带来上下文切换与内存开销
min-spare-threads 10 常见可设为 50 到 100 最小空闲线程数,用于应对突发流量,减少临时创建线程带来的抖动
max-connections 8192 常见可设为 10000+ Tomcat 可同时维护的连接数上限,连接可以处于空闲 keep-alive 状态,不等于同时执行业务的线程数
accept-count 100 常见可设为 500 到 1000 当工作线程已满、请求暂时来不及处理时,连接在操作系统与 Connector 之间等待接入处理的队列长度。超过该值的新连接可能被拒绝
connection-timeout 20000 ms 常见保持在 20000 ms 左右 连接建立后等待请求数据的超时时间。过短会误伤慢连接,过长会占用连接资源

常见配置示例:

1
2
3
4
5
6
7
8
server:
  tomcat:
    threads:
      max: 800
      min-spare: 100
    max-connections: 10000
    accept-count: 1000
    connection-timeout: 20000

max-connections 与 max-threads 的关系

核心结论:不是 1 个线程对应 1 个连接,两者是解耦的。

概念 作用 层面
max-connections (8192) TCP 连接数上限 网络层(操作系统维护)
max-threads (200) 工作线程数上限 应用层(线程池)

为什么连接数可以远大于线程数?

  1. HTTP Keep-Alive(长连接):一个 TCP 连接上可以发送多个请求,大部分时间连接是空闲的(等待下一个请求),此时不需要占用线程。

  2. NIO 模型(Tomcat 8+ 默认):Tomcat 使用 NIO(非阻塞 I/O)模型,少量的 Acceptor 线程和 Poller 线程负责监听所有连接上的事件。只有当某个连接上真正有数据需要处理时,才从线程池里分配一个工作线程。处理完毕后线程归还线程池,连接继续保持。

1
2
3
4
5
6
7
8192个连接(大部分空闲)
    ↓ 有数据到达
Poller 检测到就绪事件
    ↓
从200个线程的线程池中取一个线程处理
    ↓ 处理完毕
线程归还线程池,连接继续保持
  1. 类比理解
    • max-connections = 餐厅的座位数(8192个座位)
    • max-threads = 餐厅的服务员数(200个服务员)
    • 不是每个坐着的客人每时每刻都需要服务员,只有在点菜、上菜时才需要
  2. BIO 模式下的例外:老版本 Tomcat 使用 BIO(阻塞 I/O)模型时,确实是一个连接占用一个线程(线程在 read 时阻塞等待数据),这也是 BIO 效率低的原因——大量线程在空等而不干活。

总结:NIO 模式下,少量线程可以服务大量连接,因为线程只在真正需要执行业务逻辑时才会被占用。所以 max-connections 可以设到 8192 甚至 10000+,而 max-threads 往往只需要按 CPU 与请求耗时做更克制的配置。

常见故障与定位线索

现象 优先怀疑的层 常见原因
域名解析慢、偶发无法访问 DNS / 全局负载均衡 DNS 记录异常、权威解析延迟、跨地域网络抖动
HTTPS 握手失败、证书报错 Nginx / 入口 LB 证书过期、域名不匹配、TLS 协议或密码套件不兼容
入口返回 502 / 504 Nginx / Gateway / 上游服务 上游实例不可达、Gateway 超时、后端响应过慢
Gateway 正常但单个接口特别慢 业务服务 / 数据层 SQL 慢查询、缓存未命中、下游依赖阻塞
连接数很多但线程数不高 Tomcat NIO 正常现象 大量 keep-alive 连接空闲,并不代表线程池已经耗尽
线程池打满、吞吐上不去 Tomcat / 业务代码 业务逻辑阻塞、线程数设置过小、连接池或远程调用耗尽

定位时可以按“DNS -> 入口 LB / Nginx -> Gateway -> 服务实例 -> 数据层”这条主链路逐层排查,先确认请求到底卡在接入层、路由层还是业务执行层,再决定是调网络参数、扩实例还是优化代码与 SQL。