从浏览器输入 URL 到页面显示:DNS、Nginx、后端与 CDN 全链路解析
- 前言
- [1. 一次请求的整体地图](#1. 一次请求的整体地图)
- [2. DNS:把域名变成可访问地址](#2. DNS:把域名变成可访问地址)
-
- [2.1 为什么浏览器不能只拿域名建立连接](#2.1 为什么浏览器不能只拿域名建立连接)
- [2.2 DNS 查询如何逐层找到答案](#2.2 DNS 查询如何逐层找到答案)
- [3. TCP 与 HTTPS:先连通,再安全传输](#3. TCP 与 HTTPS:先连通,再安全传输)
-
- [3.1 TCP 三次握手解决什么问题](#3.1 TCP 三次握手解决什么问题)
- [3.2 TLS 为 HTTP 加上一层保护](#3.2 TLS 为 HTTP 加上一层保护)
- [4. Nginx:统一入口与反向代理](#4. Nginx:统一入口与反向代理)
-
- [4.1 为什么这叫反向代理](#4.1 为什么这叫反向代理)
- [4.2 Nginx 在请求链路中还会做什么](#4.2 Nginx 在请求链路中还会做什么)
- [5. 服务器集群与负载均衡](#5. 服务器集群与负载均衡)
-
- [5.1 反向代理与负载均衡不是一回事](#5.1 反向代理与负载均衡不是一回事)
- [6. NestJS:真正处理业务请求的地方](#6. NestJS:真正处理业务请求的地方)
-
- [6.1 两个典型 API 请求](#6.1 两个典型 API 请求)
- [7. MySQL 与 Redis:一个保存事实,一个加速访问](#7. MySQL 与 Redis:一个保存事实,一个加速访问)
-
- [7.1 以文章详情为例理解缓存](#7.1 以文章详情为例理解缓存)
- [8. 静态资源为何应走独立链路](#8. 静态资源为何应走独立链路)
- [9. OSS:让大体积对象有合适的存放位置](#9. OSS:让大体积对象有合适的存放位置)
-
- [9.1 上传与访问的常见分工](#9.1 上传与访问的常见分工)
- [10. CDN:把静态资源更快地送到用户附近](#10. CDN:把静态资源更快地送到用户附近)
-
- [10.1 缓存命中与回源](#10.1 缓存命中与回源)
- [11. 两条完整请求链路](#11. 两条完整请求链路)
-
- [11.1 动态 API 请求:需要业务判断与数据访问](#11.1 动态 API 请求:需要业务判断与数据访问)
- [11.2 静态资源请求:优先由 CDN 与对象存储服务](#11.2 静态资源请求:优先由 CDN 与对象存储服务)
- 总结
前言
刚开始接触 Web 开发时,一个接口看起来往往很直接:浏览器调用 GET /api/posts/42,后端返回一段 JSON。但把域名放进地址栏后,请求并不是瞬间抵达某个 Controller。它会先找到可连接的地址,建立安全连接,经过接入层转发,再由业务服务读取数据;如果页面还包含图片、脚本和字体,这些资源通常走的是另一条更适合分发的链路。
理解这条路径的价值,不在于背下每个网络术语,而在于建立一张清晰的"分工图":谁负责定位、谁负责接入、谁负责业务、谁负责保存数据、谁负责分发大体积资源。排查"页面打不开""接口很慢""图片加载慢"时,也就知道该从哪一层入手。
可以把一次访问理解为一次协作:DNS 负责找到入口,Nginx 负责接待并分流,NestJS 负责办理业务,MySQL 和 Redis 提供数据,OSS 与 CDN 负责高效送达静态资源。
1. 一次请求的整体地图
先看最常见的动态请求主干。这里的"服务器/接入层 IP"不一定是某台 NestJS 机器的地址,也可能是负载均衡器或 CDN 的入口地址;这是现代部署中非常常见的设计。
text
浏览器输入域名
↓
DNS 解析
↓
获得服务器/接入层 IP
↓
TCP 三次握手
↓
HTTPS 场景下进行 TLS 握手
↓
Nginx / 负载均衡层
↓
NestJS 后端服务器集群
↓
MySQL / Redis
↓
返回数据
以访问 https://api.example.com/api/posts/42 为例:浏览器先通过 DNS 找到 api.example.com 对应的可访问地址;连接建立后,请求到达 Nginx;Nginx 选择一台健康的 NestJS 实例;该实例读取文章数据,必要时优先从 Redis 获取缓存,未命中再查询 MySQL,最后将 JSON 沿原路返回浏览器。
| 层次 | 主要职责 | 初学者最应记住的一点 |
|---|---|---|
| DNS | 域名解析为可连接地址 | 域名是便于记忆的名称,网络连接需要地址 |
| TCP / TLS | 建立可靠、安全的传输通道 | 先连通,再安全地传数据 |
| Nginx | 统一接入、转发、分流 | 它通常不处理具体业务规则 |
| NestJS | API 与业务编排 | Controller、Service、鉴权通常在这里发生 |
| MySQL / Redis | 数据持久化与高速访问 | 两者解决的问题不同,常常配合使用 |
| OSS / CDN | 存储与加速静态资源 | 大资源不应挤进业务接口链路 |
2. DNS:把域名变成可访问地址
2.1 为什么浏览器不能只拿域名建立连接
人更擅长记住 www.example.com 这样的名称,网络设备建立连接时却需要知道目标在哪里。IP 地址就是网络中的寻址信息。**DNS(Domain Name System,域名系统)**承担的工作,正是将便于记忆的域名转换为可用于连接的 IP 地址,或先转换为另一个域名记录再继续查询。
这个机制还带来一个实际好处:应用可以迁移机器、扩容接入层或切换服务商,而用户继续访问同一个域名即可。变更的是 DNS 记录及后端部署,用户使用的入口名称不必改变。
2.2 DNS 查询如何逐层找到答案
一次查询不一定每次都要从最上层开始。浏览器、操作系统和递归 DNS 都可能保存过往结果,并且记录带有 TTL(缓存有效期)。缓存仍有效时,查询可以很快结束;缓存失效或没有记录时,才需要继续向下查找。
text
浏览器 / 操作系统缓存
↓
递归 DNS
↓
根 DNS
↓
顶级域 DNS
↓
权威 DNS
↓
IP 地址
其中各角色的关系可以这样理解:
- 本地缓存:浏览器和操作系统会暂存近期结果,减少重复查询。
- 递归 DNS:通常由网络运营商、公共 DNS 服务或企业网络提供。它替用户完成后续查询,并会缓存答案。
- 根 DNS:知道顶级域 DNS 的入口在哪里,不直接保存所有具体域名的最终地址。
- 顶级域 DNS :管理如
.com、.cn这类顶级域的委派信息,告诉查询方某个域名应去找哪组权威 DNS。 - 权威 DNS:保存某个域名实际配置的 DNS 记录,是最终答案的权威来源。
例如查询 api.example.com 时,递归 DNS 可能先向根 DNS 询问 .com 的去向,再向 .com 的顶级域 DNS 询问 example.com 的权威 DNS,最后从权威 DNS 获取 api.example.com 的 A 或 AAAA 记录。A 通常对应 IPv4,AAAA 通常对应 IPv6。实际服务也可能通过 CNAME 指向另一个域名,递归 DNS 会继续完成解析。
DNS 的职责是"找到可访问的入口",并不等同于承诺"必然返回物理距离最近的一台业务机器"。是否就近、如何调度,取决于 DNS 配置、CDN、负载均衡和网络策略等多种因素。
| 常见现象 | 更可能优先检查的位置 |
|---|---|
| 刚改域名解析,部分用户仍访问旧地址 | TTL 与各级缓存是否尚未过期 |
| 域名无法解析 | 权威 DNS 记录、域名委派、拼写与记录类型 |
| 同一域名解析出多个地址 | 可能用于高可用、流量分配或多入口部署 |
3. TCP 与 HTTPS:先连通,再安全传输
拿到 IP 后,浏览器才知道该向哪里发起连接。对于传统的 HTTPS 网站,可以先把连接过程压缩成下面三步:
text
DNS
↓
TCP 三次握手
↓
TLS 握手
↓
HTTP 请求
3.1 TCP 三次握手解决什么问题
TCP 是面向连接的传输协议。所谓三次握手,可以先理解为客户端和服务端在正式传数据之前,彼此确认"我能发到你""我能收到你""我们准备好了"。这个过程帮助双方建立一条可靠的连接状态,之后 HTTP 请求和响应才能稳定地在这条连接上收发。
不必在初学阶段纠结序列号、窗口大小等实现细节。工程上更有用的认识是:DNS 成功只说明找到了地址;TCP 成功才说明目标端口可以建立连接。 如果握手失败,可能是服务未监听、端口被防火墙拦截,或者网络路由不可达。
3.2 TLS 为 HTTP 加上一层保护
当地址以 https:// 开头时,TCP 建立后通常还要进行 TLS 握手。浏览器会校验证书,并与服务端协商后续通信所需的加密参数。握手完成后,HTTP 请求内容才在加密通道中传输。
TLS 的关键收益是三件事:验证访问对象的身份、保护传输内容不被轻易窥视、降低内容在传输中被篡改的风险。证书过期、域名与证书不匹配、接入层未正确配置证书,都会让浏览器在 HTTP 请求前就提示安全问题。
| 阶段 | 解决的问题 | 失败时常见表现 |
|---|---|---|
| DNS 解析 | 域名对应什么地址 | 域名找不到、解析超时 |
| TCP 握手 | 能否与目标端口建立连接 | 连接超时、连接被拒绝 |
| TLS 握手 | 能否建立可信的加密通道 | 证书告警、HTTPS 连接失败 |
| HTTP 传输 | 请求能否被正确处理 | 4xx、5xx 或响应慢 |
4. Nginx:统一入口与反向代理
浏览器通常不直接访问某台具体的 NestJS 实例。因为业务服务可能有多台,会扩缩容、发布和故障切换;把它们的真实地址直接暴露给客户端,管理和安全都会变得困难。实践中,外部请求经常先到 Nginx 或云负载均衡等接入层,再由接入层将请求转发给内部业务服务。
text
用户
↓
Nginx
↓
NestJS Server
4.1 为什么这叫反向代理
代理的核心是"代替一方发起或接收请求"。正向代理靠近客户端:客户端把访问外部站点的请求先交给代理,由代理代为访问目标。它主要服务于客户端侧的访问控制、网络出口等场景。
反向代理靠近服务端:用户只知道一个统一入口,反向代理在后面代表服务端接收请求,并将请求交给合适的业务服务。用户无需知道 NestJS 实例的数量、地址和变化。
| 类型 | 代理站在哪一侧 | 客户端通常是否知道真实目标 |
|---|---|---|
| 正向代理 | 客户端一侧 | 通常知道要访问的外部站点 |
| 反向代理 | 服务端一侧 | 通常只需要知道统一服务入口 |
4.2 Nginx 在请求链路中还会做什么
Nginx 常见职责包括 TLS 证书终止、域名与路径路由、请求转发、压缩、访问日志、限流,以及静态资源直接响应。例如 /api/ 前缀转发给 NestJS,而 /assets/ 则可以由 Nginx 或 CDN 直接服务。这种划分避免所有资源请求都进入业务框架。
下面是便于理解的配置片段:
nginx
server {
listen 443 ssl;
server_name api.example.com;
location /api/ {
proxy_pass http://nestjs_cluster;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
这段配置表达的重点是:收到 api.example.com 的 /api/ 请求后,Nginx 将其转发给名为 nestjs_cluster 的后端集群。真实项目还会补充超时、跨域、限流和日志等配置,但"统一入口再转发"的主线不变。
5. 服务器集群与负载均衡
单台后端服务容易遇到两个问题:访问量上来后性能有限;机器故障或发布重启时,服务可能整体不可用。因此,大型网站通常会让多台机器运行同一套 NestJS 程序,形成后端集群。对外仍保持一个域名和一个接入入口,对内则把请求分配给多个实例。
text
→ NestJS A
用户 → Nginx → NestJS B
→ NestJS C
→ NestJS D
5.1 反向代理与负载均衡不是一回事
二者经常都由 Nginx 承担,因此容易混淆。反向代理回答的是"请求由接入层代为转给后端";负载均衡进一步回答"这一次具体转给哪一台后端"。
text
反向代理:请求转发给真正后端
负载均衡:决定具体转发给哪一台后端
| 能力 | 核心问题 | 例子 |
|---|---|---|
| 反向代理 | 请求是否经过统一入口转发 | /api/ 转给 NestJS 服务 |
| 负载均衡 | 多个后端中选择哪一个 | 本次转给 A,下次转给 B |
| 健康检查 | 某个实例是否还能接收流量 | B 异常后暂时不再分配请求 |
常见分配策略包括:
- 轮询:按顺序依次分配,适合实例规格和请求耗时比较接近的场景。
- 加权轮询:性能更高的实例获得更高权重,从而承担更多流量。
- 最少连接:优先选择当前连接数较少的实例,对请求耗时差异较大的场景更有帮助。
健康检查是可用性的另一层保障。接入层会通过探测接口、连接状态或云平台健康检查判断实例是否异常;某个实例无法正常服务时,会被暂时摘除,恢复健康后再加入流量分配。需要注意,负载均衡并不能自动让业务变正确:登录状态、临时数据和幂等等问题,仍需在应用设计中处理。
6. NestJS:真正处理业务请求的地方
Nginx 关心的是"把请求交给谁",NestJS 关心的是"这个请求该怎么处理"。NestJS 是 Node.js 生态中的后端框架,常用于构建结构化 API。它通常负责接收请求、校验参数、登录鉴权、执行业务逻辑、调用数据层,最后返回 JSON。
text
Nginx:决定请求交给谁
NestJS:决定这个请求具体怎么处理
6.1 两个典型 API 请求
登录接口通常是一个写操作。NestJS 接收到 POST /api/login 后,先校验账号和密码是否完整,再查询用户信息、比对凭证,成功后生成访问令牌或建立会话,最后返回登录结果。文章详情接口则通常是读操作:GET /api/posts/:id 先从路径中读取 id,校验格式并查询文章,再返回相应 JSON。
text
POST /api/login
GET /api/posts/:id
下面用简化的 NestJS Controller 展示路由入口。重点不是记住装饰器,而是看清每个 HTTP 请求会被路由到对应的业务方法。
typescript
@Controller('api')
export class AppController {
constructor(private readonly authService: AuthService) {}
@Post('login')
async login(@Body() dto: LoginDto) {
return this.authService.login(dto);
}
@Get('posts/:id')
async getPost(@Param('id') id: string) {
return this.postService.findById(id);
}
}
一次请求在 NestJS 中常见的执行顺序是:路由匹配到 Controller,Pipe 完成参数转换与校验,Guard 判断是否有权限,Service 组织业务规则并调用 Repository 或其他服务,最终由 Controller 返回结果。出现错误时,Exception Filter 可以把异常转换成统一的 HTTP 响应格式。
NestJS 不应承担所有工作。连接接入、静态资源分发和海量二进制对象传输交给更擅长的组件,业务服务才能保持专注、易扩展。
7. MySQL 与 Redis:一个保存事实,一个加速访问
NestJS 处理业务时通常需要数据支撑。MySQL 是关系型数据库,适合保存需要长期保留、结构明确、可关联查询的业务事实,例如:
- 用户
- 文章
- 评论
- 点赞
- 收藏
Redis 是内存型键值存储,读写很快,常用于缓存、Session、验证码、热点数据和计数器。它的价值在于减少重复计算和重复查询,快速应对高频访问;但它通常不能替代 MySQL 作为关键业务事实的长期唯一来源。
text
Nginx
↓
NestJS
↓
MySQL / Redis
7.1 以文章详情为例理解缓存
当大量用户同时浏览热门文章时,如果每次都查询 MySQL,数据库会承担大量相同读取。一个常见做法是:NestJS 先查询 Redis;若缓存命中,直接返回;若未命中,再查询 MySQL,把结果写入 Redis 并设置合适过期时间。
text
GET /api/posts/42
↓
NestJS 查询 Redis
├─ 命中:直接返回文章详情
└─ 未命中:查询 MySQL → 写入 Redis → 返回文章详情
| 存储 | 更适合保存 | 设计时要注意 |
|---|---|---|
| MySQL | 用户、订单、文章等核心业务数据 | 表结构、索引、事务与一致性 |
| Redis | 缓存、会话、验证码、计数器 | 过期策略、缓存更新与内存容量 |
缓存并非"加上就一定更快"。数据更新后如何让缓存同步失效,是常见难点。例如文章编辑成功后,可以删除对应缓存,让下一次读取重新从 MySQL 构建缓存。对于点赞数这样的热点计数器,也要结合业务容忍度设计异步汇总和最终一致性策略。
8. 静态资源为何应走独立链路
页面中的图片、CSS、JavaScript、字体和视频等都属于静态资源。它们的共同特点是:内容通常不会因每个用户而频繁变化,且单个对象可能很大、请求数量也多。若每一次请求都进入 NestJS 的 Controller、鉴权和业务逻辑,会消耗本不必要的计算资源,也让业务服务承担了不擅长的传输工作。
更合理的分工是:动态 API 走 Nginx 与 NestJS;静态资源由 Nginx 直接响应,或进一步交给 OSS 与 CDN。浏览器仍然只是在请求一个 URL,但服务端内部走的是更合适的通道。
| 请求类型 | 示例 | 通常处理者 |
|---|---|---|
| 动态请求 | GET /api/posts/42 |
Nginx → NestJS → 数据存储 |
| 静态资源 | GET /assets/logo.svg |
CDN / Nginx / OSS |
| 上传请求 | POST /api/uploads |
NestJS 负责鉴权与生成上传凭证,对象存储承接内容 |
这种拆分还会让扩容更精准:接口压力高时扩 NestJS;图片访问量高时提高 CDN 覆盖和缓存策略;大体积对象存储增长时扩展 OSS 容量,而不必让所有压力都落在同一类机器上。
9. OSS:让大体积对象有合适的存放位置
图片、附件、音视频等二进制对象通常不直接存进 MySQL 的业务表。对象存储服务,如 OSS、S3、COS,更适合保存这类内容:容量扩展方便,可通过 URL 访问,也能和 CDN 更自然地协同。
MySQL 更适合保存与业务相关的元数据,例如对象名称、访问 URL、大小、媒体类型、上传者和关联文章。一个概念上的记录可以是:
text
filename
URL
size
mimetype
userId
postId
9.1 上传与访问的常见分工
上传时,NestJS 可以先验证用户身份、校验类型和大小限制,再把内容写入对象存储,或签发短时有效的直传凭证让浏览器直接上传。上传完成后,业务服务将 URL 和关联信息写入 MySQL。读取文章详情时,NestJS 返回图片 URL;浏览器随后根据该 URL 从 CDN 或对象存储获取资源实体。
这种设计避免让 MySQL 承担大对象传输,也让数据库备份、查询和迁移更可控。对于私有资源,还可以使用签名 URL 或访问控制策略,避免仅凭一个公开地址就能长期访问。
OSS 的重点是可靠存放对象;MySQL 的重点是保存对象与业务之间的关系。二者不是替代关系,而是分层协作。
10. CDN:把静态资源更快地送到用户附近
CDN(Content Delivery Network,内容分发网络的核心作用是缓存和就近分发静态资源。CDN 在不同地区部署边缘节点,用户请求图片、脚本等资源时,DNS 或 CDN 调度系统会引导请求到合适的节点。若节点已缓存所需内容,就可以直接返回,减少回到源站的距离与压力。
这里的"合适"不只是地理距离,还会综合网络质量、节点健康和调度策略。因此可以把 CDN 理解为提升资源获取效率的分发网络,而不是一个简单的"永远最近节点"承诺。
10.1 缓存命中与回源
当 CDN 节点中已经有当前版本的资源且仍在有效期内,称为缓存命中 ,节点直接响应用户。当节点没有该资源、缓存已过期,或策略要求校验源站内容时,称为需要回源:CDN 向 OSS 或源站请求资源,拿到后按策略缓存,再返回用户。
text
用户
↓
CDN
↓
缓存命中 → 直接返回
↓
未命中
↓
OSS / 源站
↓
CDN 缓存
↓
返回用户
| 概念 | 含义 | 对用户与源站的影响 |
|---|---|---|
| 缓存命中 | 边缘节点已有可用资源 | 返回更快,源站压力更小 |
| 回源 | 节点从 OSS 或源站获取资源 | 首次或失效后可能更慢,会增加源站请求 |
| 缓存过期 | 缓存控制时间结束 | 需要按策略重新校验或获取资源 |
资源更新时,CDN 缓存策略尤为重要。工程上常用带版本号或内容哈希的 URL,例如 app.8f3a2c.js。内容改变时 URL 同时改变,浏览器与 CDN 可以安全地长时间缓存旧版本,而新版本通过新 URL 获取,减少"用户看到旧资源"的问题。紧急更新时,也可以使用 CDN 刷新或预热能力,但要了解其生效范围与成本。
11. 两条完整请求链路
11.1 动态 API 请求:需要业务判断与数据访问
以用户查看文章详情为例,浏览器访问 https://api.example.com/api/posts/42。DNS 将域名解析为接入层地址;浏览器完成 TCP 和 TLS 握手;Nginx 接收请求并根据负载策略选择一台 NestJS 实例;NestJS 校验参数后先查 Redis,未命中则查询 MySQL,最后组装 JSON 返回。
text
浏览器
↓
DNS
↓
TCP / TLS
↓
Nginx
↓
NestJS
↓
MySQL / Redis
这个过程中,每一层都可以独立观测:DNS 看解析结果与 TTL,Nginx 看访问日志和上游耗时,NestJS 看接口日志和错误率,MySQL 看慢查询,Redis 看命中率。这样定位性能问题时,不必笼统地说"后端慢"。
11.2 静态资源请求:优先由 CDN 与对象存储服务
同一篇文章返回的 JSON 中可能包含封面图 URL。浏览器随后请求该图片时,一般不会再进入文章详情的 NestJS 业务流程,而是通过 CDN 获取;节点缓存未命中时,CDN 再向 OSS 或源站回源。
text
浏览器
↓
DNS
↓
CDN
↓
OSS / 源站
这两条链路相互配合:动态 API 保证内容和权限正确,静态资源链路保证图片、脚本等高效交付。它们不是谁取代谁,而是在不同类型的请求上各司其职。
总结
从地址栏输入域名到页面呈现内容,是一条由多个组件接力完成的路径。DNS 将好记的名称转换为可访问地址;TCP 与 TLS 让通信通道可靠且安全;Nginx 提供统一入口,并将流量代理、分配给健康的 NestJS 实例;NestJS 承载鉴权、校验和业务规则;MySQL 保存长期业务事实,Redis 缓解热点访问;OSS 存放大体积对象,CDN 通过边缘缓存加速静态资源。掌握这种边界感,比孤立记住某个组件的命令更重要:当请求出现异常或性能下降时,可以顺着链路分层定位,也能在系统增长时有针对性地扩展。
text
DNS:域名 → IP
Nginx:代理 + 负载均衡
NestJS:业务逻辑
MySQL:持久化业务数据
Redis:缓存和高速数据
OSS:存对象
CDN:加速静态资源访问