前面两篇,我们一直站在数据库的视角设计一个博客系统:
sql
user
post
comment
tag
file
...
但数据库设计完,并不代表用户就能使用这个网站。
一个真正上线的 Web 系统,还需要解决另一个问题:
用户在浏览器输入
juejin.cn以后,他的请求到底是怎么到达我们的后端程序的?
这个问题会牵涉:
DNS
IP
TCP
Nginx
反向代理
负载均衡
服务器集群
OSS
静态资源服务器
CDN
这些名词第一次一起出现时非常容易乱。
这一篇就把它们连成一条完整链路。
一、先想一个最简单的 Web 项目
假设我们使用 Nest.js 写了一个博客后端:
Nest.js
服务器上运行:
用户接口
文章接口
点赞接口
评论接口
数据库则存储:
用户
文章
评论
点赞关系
如果项目很小,最简单的情况可能是:
用户
↓
一台服务器
↓
Nest.js
↓
MySQL
但像掘金这样的线上服务不可能只靠一台服务器。
为什么?
因为如果同时来了:
yaml
10 个用户
1000 个用户
100000 个用户
单台服务器的 CPU、内存、连接数都有极限。
而且:
如果这台服务器挂了,整个网站就没了。
所以大型系统通常会演变成:
很多台后端服务器
二、但是用户访问的是域名,不是服务器代码
用户输入的是:
juejin.cn
而计算机真正进行网络通信时,需要:
IP 地址
所以第一个问题就是:
juejin.cn 对应哪个 IP?
这个过程由:
DNS
负责。
三、DNS 到底是什么?
DNS:
sql
Domain Name System
可以理解为:
域名和 IP 地址之间的查询系统。
例如:
juejin.cn
最终需要解析成类似:
xxx.xxx.xxx.xxx
这样的 IP。
所以:
域名
→ DNS 查询
→ IP
四、DNS 查询并不是每次都从头开始
浏览器准备访问域名时,会优先检查缓存。
例如可能依次查看:
浏览器 DNS 缓存
操作系统 DNS 缓存
本地网络配置
DNS 解析器
如果已经知道答案,就没必要重新查。
如果本地没有,则会向配置的 DNS 解析服务发起查询。
后续 DNS 系统会根据需要继续找到:
根 DNS
顶级域 DNS
权威 DNS
等信息,最终得到目标域名对应的地址。
因此把 DNS 简单理解为:
从域名找到对应服务器 IP。
已经足够建立第一层认识。
五、拿到 IP 以后发生什么?
浏览器知道目标 IP 后,就可以尝试与服务器建立网络连接。
如果是常见的基于 TCP 的 HTTP/1.1 或 HTTP/2 场景,会涉及 TCP 建立连接。
经典 TCP 建立连接过程就是:
三次握手。
所以可以先粗略理解:
juejin.cn
↓
DNS
↓
IP
↓
建立网络连接
↓
发送 HTTP 请求
如果使用 HTTPS,中间还会涉及:
TLS
建立安全连接。
六、这个 IP 一定是 Nest.js 服务器吗?
不一定。
大型网站通常不会直接暴露:
arduino
Nest Server A
Nest Server B
Nest Server C
让用户自己选择。
用户更可能首先访问的是:
入口层。
这个入口层可能使用:
Nginx
或者云厂商提供的:
Load Balancer
七、为什么需要很多后端服务器?
假设现在有:
arduino
Server A
Server B
Server C
Server D
每台机器都部署同一套 Nest.js 应用。
也就是说:
vbscript
Server A
能处理 GET /posts
Server B
也能处理 GET /posts
Server C
也能处理 GET /posts
现在同时来了很多用户。
就不能全部塞给 Server A。
我们希望:
arduino
请求 1 → Server A
请求 2 → Server B
请求 3 → Server C
请求 4 → Server A
...
于是需要一个角色:
帮忙分配请求。
这就是负载均衡。
八、Nginx 在这里做什么?
Nginx 可以放在后端服务器集群之前:
arduino
Nginx
Server A
Server B
Server C
Server D
用户并不知道后面到底有几台业务服务器。
用户只访问:
juejin.cn
Nginx 接到请求以后,再决定:
把这次请求交给哪台后端服务器。
例如:
bash
GET /api/posts
可能被转给:
arduino
Server B
下一次:
bash
POST /api/comment
可能被转给:
arduino
Server C
九、为什么叫"反向代理"?
先看用户眼中的世界。
用户以为自己访问的是:
juejin.cn
但实际上真正处理业务的可能是:
makefile
10.0.0.11:3000
10.0.0.12:3000
10.0.0.13:3000
这些服务器隐藏在 Nginx 后面。
Nginx:
替服务器接收请求
再把请求转发给真正服务器
最后把结果返回给用户
所以:
Nginx 在服务器这一侧帮后端服务器做代理。
这就是:
反向代理
十、反向代理和负载均衡有什么关系?
两者不是完全相同的概念。
反向代理强调的是:
用户请求先到代理服务器,代理服务器再访问真正的后端。
负载均衡强调的是:
当后面有多台服务器时,把流量合理分配出去。
所以 Nginx 可以:
只做代理
也可以:
代理 + 负载均衡
例如:
arduino
用户
↓
Nginx
├── Server A
├── Server B
└── Server C
这里两件事同时存在。
十一、Nginx 自己会处理 Nest.js 业务吗?
通常不会。
比如:
注册用户
创建文章
写评论
检查 JWT
查询数据库
这些逻辑仍然由:
Nest.js
完成。
Nginx 主要负责入口层工作,例如:
反向代理
负载均衡
TLS 终止
静态资源服务
请求限制
缓存
所以不要把 Nginx 和业务后端混为一谈。
十二、为什么服务器集群都运行同一套程序?
假设项目打包后同时部署到:
arduino
Server A
Server B
Server C
它们运行的是同一套 Nest.js 服务。
所以任何一个都可以处理:
bash
GET /posts/100
真正的数据通常在共享数据库、缓存或其他基础设施中。
例如:
arduino
Server A
Server B
Server C
↓
MySQL
因此请求被分配到哪台业务服务器,并不会意味着每台服务器拥有完全不同的数据。
十三、数据库通常放在哪里?
后端业务和数据库一般部署在服务器环境中。
应用服务器可能有多台:
css
Nest A
Nest B
Nest C
而数据库可能是:
MySQL 主从
数据库集群
云数据库
分片数据库
具体架构会随着规模不断变化。
但对于刚入门来说,先理解:
请求
→ 后端应用
→ 数据库
就足够了。
数据库负责:
用户
文章
评论
点赞
收藏
标签
这样的结构化业务数据。
十四、那头像和文章图片呢?
这里又出现一个非常重要的问题。
假设一张头像大小:
200 KB
一篇文章有:
20 张图片
如果所有图片都:
请求业务后端
→ Nest.js
→ 再从磁盘找文件
→ 返回
大量带宽和连接就会被静态资源占掉。
而图片本身根本没有什么复杂业务逻辑。
所以大型网站通常会把:
图片
CSS
JavaScript
字体
视频
附件
这类资源从核心业务服务中拆出来。
十五、什么叫静态资源?
静态资源可以简单理解为:
服务器不需要根据业务动态计算内容,直接把文件返回即可。
例如:
css
logo.png
avatar.webp
main.js
style.css
font.woff2
它们和:
bash
POST /login
POST /comment
GET /api/user
完全不是一种请求。
后者需要执行程序。
前者很多时候只是:
把文件给你。
十六、为什么头像链接常常不是主站域名?
你会发现很多大型网站的图片 URL 并不是:
bash
juejin.cn/avatar/xxx
而是来自单独的静态资源域名。
例如:
bash
xxx.byteacctimg.com/...
这是非常常见的设计。
因为:
juejin.cn
主要承担业务访问。
而:
静态资源域名
专门提供:
图片
头像
JS
CSS
等资源。
十七、OSS 又是什么?
例如用户上传头像:
avatar.png
后端接到上传请求以后,可以把文件保存到:
阿里云 OSS
AWS S3
腾讯云 COS
这一类服务统称:
对象存储。
数据库可能只保存:
arduino
filename
size
mimetype
userId
objectKey
而真正的:
图片二进制
存储在 OSS 中。
于是数据库负责:
文件是谁的?
OSS 负责:
文件本身放在哪里?
职责非常清晰。
十八、CDN 又是什么?
如果所有静态资源都只存在北京的一台服务器上:
北京静态服务器
北京用户访问可能很快。
但美国用户也要跨越很远的网络去取:
avatar.webp
就可能很慢。
CDN 的全称是:
css
Content Delivery Network
中文一般叫:
内容分发网络。
它会在不同地区部署大量边缘节点。
例如:
erlang
北京
上海
广州
东京
新加坡
洛杉矶
纽约
...
这样用户请求静态资源时,就可以尽量从离自己较近的节点获取。
十九、CDN 可以怎么理解?
假设原始图片保存在:
OSS
用户第一次请求:
bash
/avatar/abc.webp
某个 CDN 节点本地没有。
它可能去源站获取:
CDN
↓
OSS
然后缓存下来。
下一批附近用户继续请求:
bash
/avatar/abc.webp
就可以直接:
用户
↓
附近 CDN
↓
返回图片
不必每次都访问原始存储。
于是:
延迟降低
源站压力降低
带宽利用更合理
二十、动态请求和静态请求应该分开理解
现在访问一个文章页面。
页面可能需要:
文章正文
作者资料
点赞数量
评论数据
这些属于动态业务数据。
可能经过:
浏览器
↓
Nginx / 负载均衡
↓
Nest.js
↓
MySQL
但是页面里的:
头像
文章图片
JS
CSS
可能直接访问:
CDN
所以同一个网页,其实背后可能向很多不同服务器发请求。
二十一、现在重新走一次 juejin.cn
假设用户打开:
arduino
https://juejin.cn
可以先用下面这套简化模型理解。
第一步:DNS
浏览器需要知道:
juejin.cn
对应哪个网络地址。
于是进行 DNS 查询。
第二步:建立连接
得到入口服务器地址以后,建立网络连接。
HTTPS 还会进行 TLS 安全连接相关过程。
第三步:请求到达入口层
请求可能首先来到:
负载均衡器 / Nginx
它本身不负责主要业务逻辑。
第四步:挑选后端服务器
例如当前集群:
css
Nest A
Nest B
Nest C
负载均衡器选择:
css
Nest B
处理此次请求。
第五步:后端执行代码
Nest.js:
解析请求
验证用户
执行业务逻辑
查询数据库
例如:
ini
SELECT *
FROM post
WHERE id = 100;
第六步:返回业务数据
Nest.js 将:
文章
作者
评论
点赞数量
等信息返回。
第七步:浏览器继续加载静态资源
页面里可能还有:
头像
图片
JavaScript
CSS
字体
这些资源可能从:
CDN
获取。
最终浏览器把它们组合起来,呈现出完整网页。
二十二、可以把整个系统分成三层
现在回头看,会发现其实没那么乱。
第一层:业务数据
MySQL
负责:
用户
文章
点赞
评论
标签
第二层:业务程序
Nest.js
负责:
登录
鉴权
发布文章
发表评论
点赞
查询数据库
第三层:网络和基础设施
DNS
Nginx
负载均衡
OSS
CDN
负责:
找到服务器
分配流量
存储文件
分发静态资源
这样就不会把所有概念混在一起了。
二十三、Nginx 和 CDN 最容易混淆的地方
可以简单做一个区分。
Nginx
更偏向:
请求入口
反向代理
负载均衡
转发业务请求
例如:
arduino
/api/posts
→ Nest Server B
CDN
更偏向:
静态内容分发
就近访问
缓存
例如:
bash
/avatar/a.webp
→ 附近 CDN 节点
所以二者解决的核心问题不同。
二十四、DNS 也不是"分布式数据库"
DNS 本身确实是一个全球分布式、分层的系统,但学习初期最好不要简单把它记成:
DNS 就是一个分布式数据库。
更准确的理解是:
DNS 是负责域名解析的分布式、层次化命名系统。
它解决的问题是:
域名
→ 网络地址及其他 DNS 记录
而不是业务数据库中的:
sql
user
post
comment
二十五、真实大型系统会比这里复杂很多
真正的大型互联网应用还可能存在:
Redis
消息队列
服务注册
网关
微服务
数据库读写分离
分库分表
容器编排
Kubernetes
多地域部署
WAF
多级缓存
但现在完全没必要一次性全部学。
先把最核心的一条链路搞明白:
域名
↓
DNS
↓
服务器入口
↓
反向代理 / 负载均衡
↓
后端应用
↓
数据库
静态资源则是:
浏览器
↓
CDN
↓
OSS / 静态资源源站
这已经足够建立一个完整的 Web 系统认知。
二十六、数据库知识终于和部署知识连起来了
前面我们设计:
arduino
avatar
id
filename
mimetype
size
userId
为什么数据库里只保存这些信息?
现在就能解释了。
因为:
数据库
负责文件元数据
OSS
负责真正保存图片
CDN
负责高效分发图片
比如:
avatar 表
可能记录:
ini
userId = 7
filename = abc.webp
真正访问头像时:
vbnet
https://static.example.com/avatar/abc.webp
最终可能由 CDN 返回。
于是数据库设计和服务器架构终于串起来了。
总结
当用户在浏览器输入一个网站地址时,背后远不只是:
浏览器找到一个 HTML。
实际系统可能涉及:
DNS
找到服务入口
Nginx / Load Balancer
接收并分配请求
Nest.js
执行后端业务代码
MySQL
保存业务数据
OSS
保存图片等文件
CDN
将静态资源就近分发给用户
最重要的是,不要把这些东西混成一个概念。
可以分别记住:
DNS
我该去哪里?
Nginx / Load Balancer
这次请求该交给谁?
Nest.js
具体业务怎么办?
MySQL
业务数据存哪里?
OSS
真正的文件放哪里?
CDN
怎样让各地用户更快拿到静态文件?
理解了这六个问题,一个完整 Web 应用从数据库设计到线上访问的基本轮廓,也就真正建立起来了。