深色模式
00 · 一张图串起全部:一次请求的完整旅程
这是全库的总纲。如果只能记一篇,记这篇。 它把四个模块(OS / 网络 / 数据结构 / 数据库)挂在同一条时间线上。
怎么用这篇
- 先通读一遍,不求记住。
- 学完四个模块后回来,合上资料,自己画一遍。
- 画不出来的地方,就是还没真懂的模块。
总图:从敲下回车到页面出现
[0] 你按下回车
|
v
+-- [1] 应用层(浏览器)
| · 解析 URL:协议、域名、端口、路径
| · 查缓存:强缓存命中就直接用,不发请求
| · 查 DNS 缓存(浏览器 -> 系统 -> hosts)
|
+-- [2] DNS 解析 【网络 05】
| 本地 DNS -> 根 -> 顶级域 -> 权威(迭代查询)
| 拿到 IP:93.184.216.34
| 走 UDP 53,因为报文小、要快
|
+-- [3] TCP 三次握手 【网络 02】
| SYN -> SYN+ACK -> ACK
| 内核协议栈完成,不经过应用
| 要分配 socket、进半连接队列 / 全连接队列
| 进程此时是"阻塞"或"异步等待" 【OS 01/04】
|
+-- [4] TLS 握手(HTTPS) 【网络 04】
| ClientHello -> ServerHello + 证书 -> 密钥交换
| 非对称协商密钥,对称加密传数据
| 证书链验证可能又是一次网络请求
|
+-- [5] 发 HTTP 请求 【网络 04】
| GET /index.html HTTP/1.1 + Host + Cookie
|
+-- [6] 封装:应用 -> TCP -> IP -> 链路 【网络 01】
| TCP 头:源端口、目的端口 443、序号、窗口
| IP 头:源 IP、目的 IP、TTL=64
| MAC 头:源 MAC、下一跳(网关)的 MAC
| 查路由表决定下一跳,查 ARP 拿网关 MAC 【网络 05】
|
+-- [7] 内核发送数据 【OS 04】
| 应用调 write() -> 系统调用(用户态转内核态)【OS 05】
| 数据从用户缓冲区拷到 socket 发送缓冲区
| 网卡 DMA 取走,发到链路上
| 大文件可能用零拷贝(sendfile)绕过用户态
|
+-- [8] 路由转发 【网络 01】
| 每跳:拆 MAC 头 -> TTL 减 1 -> 查路由表 -> 重写 MAC 头
| IP 头不变,MAC 每跳都变
| 丢包了 TCP 重传(超时 / 3 个重复 ACK) 【网络 03】
| 拥塞控制决定发多快(cwnd)
|
+-- [9] 服务端收包 【OS 04】
| 网卡中断 -> 内核协议栈逐层解封装
| 数据进 socket 接收缓冲区
| epoll 检测到可读 -> 唤醒工作线程
| 应用 read() -> 系统调用 -> 拷到用户缓冲区
|
+-- [10] 查数据库 【数据库】
| a. 连接池取连接(可能又要一次 TCP 握手)
| b. 解析 SQL -> 优化器选索引 -> 执行计划 【数据库 05】
| c. 走 B+ 树找数据 【数据库 01】
| Buffer Pool 命中 -> 内存里直接返回 【OS 02/04】
| 未命中 -> 从磁盘读 16KB 页(一次 IO)
| d. 读操作:用 MVCC 判断版本可见性 【数据库 03】
| e. 写操作:加锁 -> 改 Buffer Pool -> 写 undo
| -> 写 redo -> 写 binlog -> 两阶段提交 【数据库 02】
| 没走索引 -> 锁全表 -> 别人全等着 【数据库 04】
|
+-- [11] 返回响应 【网络 04】
| 200 OK + Cache-Control + ETag + HTML
| 原路返回(7~9 反过来走一遍)
|
+-- [12] 浏览器渲染
HTML -> DOM -> CSSOM -> 渲染树 -> 布局 -> 绘制
发现 CSS/JS/图片 -> 再发请求(复用连接)四条横向对照:同一个概念在四个模块里的样子
这张表是全库最值钱的部分。面试时能把一个概念跨模块对照,就是"真懂"的信号。
| 抽象概念 | 操作系统 | 计算机网络 | 数据结构 | 数据库 |
|---|---|---|---|---|
| 分层/封装 | 用户态 / 内核态 | TCP/IP 四层 | — | 存储引擎 / Server 层 |
| 缓存 | CPU 缓存、TLB、页缓存 | DNS 缓存、ARP 缓存、HTTP 缓存 | — | Buffer Pool、查询缓存 |
| 淘汰策略 | 页面置换(Clock) | DNS TTL、ARP 超时 | LRU 缓存 | Buffer Pool 的改良 LRU |
| 队列 | 就绪队列、阻塞队列 | 半连接队列、全连接队列 | BFS、优先队列 | 锁等待队列 |
| 锁 | 互斥锁、自旋锁、读写锁 | — | — | 行锁、表锁、间隙锁 |
| 死锁 | 四条件 + 银行家算法 | — | — | 等待图检测 + 回滚 |
| 多版本/乐观 | COW(写时复制) | — | 持久化数据结构 | MVCC |
| 索引 | 页表(虚拟>物理) | 路由表(目的>下一跳) | B+ 树、哈希表 | B+ 树索引 |
| 分片/局部性 | 分页、NUMA | 子网、CDN 就近 | 数组 vs 链表(缓存行) | 分库分表、分区表 |
| 日志/重放 | journal(文件系统) | — | — | redo log、binlog |
| 地址翻译 | 虚拟地址 > 物理地址 | 域名 > IP > MAC | — | 逻辑行 > 页 > 磁盘偏移 |
怎么用这张表:随便挑一行,试着讲清"这四列分别是什么、为什么这么像"。讲得通,说明你的知识是网状的。
三个"横切问题":一个现象牵出四个模块
问题 1:我的程序为什么卡住了?
程序卡住
+- 在等 CPU? > 就绪队列太长,负载过高 【OS 调度】
+- 在等锁? > 互斥锁/自旋锁,或者数据库行锁 【OS 并发 / 数据库锁】
+- 在等 IO? > 阻塞 IO,对端没回,磁盘慢 【OS IO / 网络】
+- 在等数据库? > 慢 SQL、锁等待、连接池打满 【数据库】
+- 在等 GC? > 内存不够,频繁回收 【语言运行时】
+- 在等网络? > RTT 大、丢包重传、拥塞窗口太小 【网络】排查顺序:先看 CPU 使用率(高 > 计算密集/自旋;低 > 在等)> 看线程栈(在哪一行阻塞)> 看数据库慢查询 > 看网络 RTT。
问题 2:为什么我的接口 P99 是 2 秒,但平均只有 50ms?
P99 远高于平均值 = 有"长尾"
长尾的常见来源:
+- 缓存击穿/雪崩 > 少量请求打到数据库 【数据库 + 缓存】
+- 锁等待 > 少数请求排队 【OS / 数据库】
+- 慢 SQL > 个别查询没走索引 【数据库】
+- GC 停顿 > 周期性 STW 【语言运行时】
+- TCP 重传 > 丢包导致等待 RTO(几百 ms) 【网络】
+- 连接池耗尽 > 请求排队等连接 【数据库 + 网络】关键:平均值会掩盖长尾。优化 P99 往往比优化平均值更有价值。
问题 3:系统能扛多少并发?
并发能力 = 最窄的那个瓶颈
① CPU 够吗? > 计算密集型的上限 = 核数 【OS】
② 内存够吗? > Buffer Pool / 缓存 / 线程栈 【OS】
③ 连接数够吗? > 文件描述符、端口数、连接池大小 【OS / 网络】
④ 数据库扛得住吗? > QPS、锁竞争、连接数 【数据库】
⑤ 带宽够吗? > 出口带宽 / 每请求大小 【网络】
⑥ 有没有串行点? > 全局锁、单点写入、自增 ID 【全模块】"能扛多少并发"这个问题,答案永远是"最窄的那一环"。找到它,比盲目扩容有用得多。
一张极简版总图(贴在墙上那种)
用户
|
v
浏览器 -------- DNS 查 IP -------------------- 【网络 05】
|
| TCP 握手 / TLS 握手 --------------------- 【网络 02/04】
v
内核协议栈 ---- 封装 / 解封装 ---------------- 【网络 01】
|
| socket 缓冲区 + epoll ------------------- 【OS 04】
v
应用进程 ------ 线程调度 / 锁 ----------------- 【OS 01/03】
|
| SQL
v
数据库 -------- 优化器 -> B+ 树 -> Buffer Pool - 【数据库 01/05】
| 事务 / MVCC / 锁 --------------- 【数据库 02/03/04】
| 缺页 / 刷盘
v
磁盘 ---------- 虚拟内存 / 页缓存 ------------- 【OS 02】和其他知识的连接
- 这篇是
00-导读各模块地图的"总装" - 所有细节都在
01-操作系统/、02-计算机网络/、03-数据结构与算法/、04-数据库原理/里
自测 3 问
Q1:不看资料,把"输入 URL 到页面出现"讲一遍,能讲到几层?
点开看答案
能讲到 8 层以上算过关,关键节点: DNS > TCP 握手 > TLS 握手 > 发 HTTP > 封装 > 路由转发 > 服务端收包 > 查数据库 > 返回 > 渲染。
加分点:
- 每一步能说出"属于哪个模块"
- 能说出"这一步如果失败,会看到什么现象"(比如 DNS 失败 > 域名解析错误;TCP 失败 > 连接超时)
减分点:
- 只会说"发请求、收响应",中间全是黑的
- 说不清封装/解封装
Q2:为什么"能扛多少并发"的答案永远是"最窄的那一环"?
点开看答案
因为系统是串行依赖的:请求必须依次经过 CPU、内存、网络、数据库。
木桶效应:整个系统的吞吐量 = 所有环节里最小的那个。
举例:
- 应用层能处理 10 万 QPS,但数据库只能 5000 QPS > 系统上限 5000。
- 数据库能 5 万 QPS,但出口带宽只够 1 万请求 > 上限 1 万。
- 都够,但有个全局锁 > 上限就是锁的吞吐。
所以扩容不能盲目:加机器但数据库是瓶颈,只会让数据库更早崩。先找到瓶颈,再针对性优化。
Q3:为什么说"P99 比平均值更值得优化"?
点开看答案
因为用户感知的是最慢的那部分。
假设 100 个请求:99 个 50ms,1 个 2 秒。
- 平均值 = 69.5ms,看起来很好。
- 但那个 2 秒的请求,用户会明显感觉"卡了"。
而且长尾往往有系统性原因(锁等待、GC、丢包重传、缓存击穿),不是随机的。不解决,它会周期性出现,且随流量增长而恶化。
工程实践:监控要看 P95 / P99 / P999,而不是只看平均值。平均值会掩盖问题。
类比失效处 / 易错点
- "一条流水线"类比会让人以为请求是严格串行的。实际上服务端有连接池、线程池,多个请求是并发处理的,图里画的是"单个请求的路径"。
- 不要以为"图里的顺序"就是"真实的时序"。比如 TLS 握手和 DNS 查询可能并行(HTTP/3 甚至合并),浏览器会预连接、预解析。
- "四层对照表"不是严格对应。比如"锁"在网络层就没有对应物——不要为了凑对称硬编。
- 这张图是"典型路径",不是"所有路径"。有 CDN 时请求可能根本不打到源站;有缓存时可能不查数据库。
- 能背图 ≠ 懂原理。图是索引,真正的理解在每个模块的"为什么这么设计"里。
✅ 验收清单
- 能合上资料,自己画出这张总图(至少 8 个节点,标出所属模块)
- 能口述"输入 URL 到页面出现",每个环节说出"属于哪个模块、关键机制是什么"
- 能挑"四层对照表"里任意 3 行,讲清四列的对应关系
- 能用"程序卡住了"这个问题,牵出至少 4 个模块的原因
- 能解释"为什么 P99 比平均值更值得关注"
- 能解释"系统并发上限 = 最窄的一环",并举一个具体例子
- 能画出极简版总图(浏览器 > 协议栈 > 进程 > 数据库 > 磁盘)
- 合上书,用自己的话讲 5 分钟,中途不卡壳