<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>计算机基础 on 安橙的博客</title><link>https://blog.ans20xx.com/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/</link><description>Recent content in 计算机基础 on 安橙的博客</description><generator>Hugo -- 0.161.1</generator><language>zh</language><lastBuildDate>Wed, 18 Feb 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.ans20xx.com/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%9F%BA%E7%A1%80/index.xml" rel="self" type="application/rss+xml"/><item><title>计算机组成原理</title><link>https://blog.ans20xx.com/posts/infra/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BB%84%E6%88%90/</link><pubDate>Wed, 18 Feb 2026 00:00:00 +0000</pubDate><guid>https://blog.ans20xx.com/posts/infra/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BB%84%E6%88%90/</guid><description>&lt;h1 id="全局框架"&gt;全局框架&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-64815732"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-64815732" style="display:none;"&gt;
- 全局框架
- 系统层次
- ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/18/20260218105815855.png,424,537)
- 核心矛盾：速度差
- CPU 运算快、内存慢、外设更慢
- 系统用层次结构（寄存器/Cache/内存/磁盘）和并行性（流水线/乱序/多核/DMA）来隐藏或者摊薄延迟
- 指令在 CPU 里按“取值-译码-执行-访存-写回“的数据通道运行
- 为了加速，做流水线把这 5 步重叠
- 内存访问通过 Cache 利用局部性降低平均延迟
- 虚拟内存通过 MMU/TLB 把程序看到的地址映射到物理内存
- I/O 慢是由于设备与总线带宽/延迟远弱于 CPU，因此依赖中断、缓冲与 DMA 来减少 CPU 参与
&lt;/textarea&gt;
&lt;h1 id="指令系统与数据表示"&gt;指令系统与数据表示&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-65742138"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-65742138" style="display:none;"&gt;
- 指令系统与数据表示
- 整数与位运算的数据表示
- 位、字节、字
- bit：0/1
- Byte：8bit
- Word（字）：CPU 自然处理的宽度，与寄存器/ALU/地址宽度相关
- 为什么 byte 是最小编址单位：大多数现代机器按照字节编址，方便表示字符/结构体/网络数据等
- 原码/反码/补码
- 原码
- 表示方法：最高位符号位，剩下是绝对值
- 问题：
- 有 &amp;#43;0 和 -0 两种零
- 加减法需要额外处理符号，硬件复杂
- 反码
- 正数同原码
- 负数：对正数位取反
- 问题
- 仍然有 &amp;#43;0 和 -0
- 加法需要回卷进位
- 补码
- 正数：与无符号相同
- 负数：按位取反 &amp;#43; 1
- 关键性质：
- 只有一个 0（没有 -0）
- 加减法统一为同一套加法器：a-b=a&amp;#43;(~b&amp;#43;1)
- 符号扩展简单：高位补符号位即可
- 范围不对称 [-2^{n-1},2^{n-1}-1]
- 快速转换
- 补码-&amp;gt;十进制
- 最高位 0：按无符号直接算
- 最高位 1：取反 &amp;#43;1 得到绝对值，再加负号
- 十进制-&amp;gt;补码
- 正数：直接写二进制，左侧补 0
- 负数：先写绝对值二进制 -&amp;gt; 取反&amp;#43;1
- 有符号/无符号加法与溢出判断
- 无符号溢出(Unsigned overflow)
- 本质：结果超出 [0, 2^n-1]
- 判定方法：看进位 Carry-out
- 有符号溢出
- 本质：结果超出 [-2^{n-1},2^{n-1}-1]
- 判断口径统一
- 同号相加，异号结果 =&amp;gt; 溢出
- 异号相加不会溢出
- 等价位级判定
- 符号位的进位与最高位进位输出不同=&amp;gt;溢出
- 字节序：大端 vs 小端
- 对 32-bit 只 0x12 34 56 78 高位在左
- 大端
- 低地址存高字节
- 内存从低地址到高地址 12 34 56 78
- 直觉：与人类书写一致
- 小端
- 低地址存低字节
- 内存从低地址到高地址：78 56 34 12
- 直觉：低地址就是低位，做逐字节扩展/截断更自然
- x86 等主流架构长期使用小端
- 对齐与 Padding
- 对齐
- 要求：某类型对象的地址必须是某个中的倍数(4/8)
- 为什么要对齐
- 硬件访问效率：CPU/总线按照字/双字读写；未对齐可能需要两次内存读写再拼接
- Cache line 与内存访问粒度：对齐能减少跨行/跨 cache line 的概率
- Padding（填充）
- 为了满足对齐，编译器会在结构体字段之间或末尾插入空字节
- 结构体大小通常是其最大对齐需求的倍数
- 浮点数(IEEE 754) 与误差本质
- IEEE 754 基本格式
- 浮点数表示
- S（符号位）：0 正 1 负
- E（指数/阶码）：带拍支 bias
- F（尾数/小数部分）：有效数 significand 的小数部分
- ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/18/20260218112914998.png,218,39)
- 单精度 float（32-bit）
- S: 1 位
- E: 8 位，bias = 127
- F: 23 位
- 双精度 double（64-bit）
- S：1 位
- E: 11 位，bias=1023
- F: 52 位
- 规格化、非规格、0、INF、NaN
- 规格化
- 指数 E 既不是全 0 也不是全 1
- 有隐含的 leading 1：1.F
- 非规格化
- 指数 E 全 0，尾数 F 非 0
- 没有隐含 1：有效数是 0.F
- 目的：渐进下溢，让非常接近 0 的数仍然能表示
- 0
- E 全 0，F 全 0
- 有 &amp;#43;0 和 -0
- Inf 无穷
- E 全 0，F 全 0
- 有 &amp;#43;0 和 -0
- NaN（非数）
- E 全 1，F 非 0
- 来自非法操作：0/0
- NaN 会传播
- 0.1 &amp;#43; 0.2 != 0.3
- 十进制小数在二进制中往往是无线不循环小数
- 0.1 的二进制是 0.0001100111
- float/double 只能保存有限位尾数
- 存储时发生舍入，计算时再舍入，误差叠加
- 比较时用 ==，就可能不相等
&lt;/textarea&gt;
&lt;h1 id="cpu-执行与控制"&gt;CPU 执行与控制&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-38745126"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-38745126" style="display:none;"&gt;
- CPU 执行与控制
- 单周期 Datapath
- 必备组件
- ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/18/20260218153017482.png,429,229)
- PC 当前指令地址寄存器
- Instruction Memory/I-Cache：根据 PC 取指令
- Register File （寄存器堆）：两读一写（rs1、rs2 读；rd 写）
- Immediate Generator（立即数生成/符号扩展）
- ALU：算数逻辑/比较/地址计算
- Data Memory/D-Cache：load/store 访问
- Adders：
- PC &amp;#43; 4
- PC &amp;#43; imm （分支目标）
- 关键 MUX（多路选择器）
- ALUSrc：ALU 第二操作数是来自 rs2 还是 imm
- MemToReg：写回 rd 的数据来自 ALU 还是内存
- PCSrc：下一 PC 来自 PC&amp;#43;4 还是分支目标
- Control Unit（控制器）：根据 opcode/funct 产生控制信号
- datapath = 一堆功能模块 &amp;#43; 多根线 &amp;#43; MUX 选路
- 四条指令在 datapath 里怎么走
- R 型算数：add rd, rs1, rs2
- 目标：rd = rs1 &amp;#43; rs2
- 数据流
- PC -&amp;gt; 取指令
- 寄存器堆读 rs1、rs2
- ALU 计算 ALUOut = rs1 &amp;#43; rs2
- 写回：rd &amp;lt;- ALUOut
- PC 更新：PC &amp;lt;- PC &amp;#43; 4
- 关键控制
- RegWrite = 1 写寄存器
- ALUSrc = 0 第二操作数来自 rs2
- MemRead = 0, MemWrite = 0
- MemToReg = 0（写回来自 ALU）
- Branch = 0（不分支）
- ALUOp = R-type（由 funct 决定是 add/sub/and/or）
- 访存读：lw rd, imm(rs1)
- 目标：rd = Mem[rs1&amp;#43;imm]
- 数据流
- 取指
- 读寄存器 rs1 (base)，rs2 通常也读但不用
- ALU 计算有效地址：ALUOut = rs1 &amp;#43; imm
- 数据内存读：MemOut = Mem[ALUOut]
- 写回：rd &amp;lt;- MemOut
- PC &amp;lt;- PC &amp;#43; 4
- 访存写：sw rs2, imm(rs1)
- 目标：Mem[rs1&amp;#43;imm] = rs2
- 数据流
- 取指
- 读 rs1（base） 和 rs2（要写入的数据）
- ALU 算地址：ALUOut = rs1 &amp;#43; imm
- 数据内存写：Mem[ALUOut] &amp;lt;- rs2
- PC &amp;lt;- PC &amp;#43; 4
- 条件分支：beq rs1, rs2, imm
- 目标：如果 rs1 == rs2，则 PC &amp;lt;- PC &amp;#43; imm；否则 PC &amp;lt;- PC &amp;#43; 4
- 数据流
- 取指
- 读 rs1，rs2
- ALU 做比较：Zero = (rs1-rs2==0)
- 同时计算目标分支：Target = PC &amp;#43; imm
- PC 选择
- if Branch &amp; Zero：PC &amp;lt;- Target
- else: PC &amp;lt;- PC &amp;#43; 4
- 单周期 vs 多周期
- 单周期
- 每条指令都在一个周期完成
- 周期必须长到容纳最慢指令路径
- 结果：简单但浪费，端指令也被迫跑长周期
- 多周期
- 把一条指令拆分成多个步骤，每一步一个周期
- 不同指令用不同步骤数量
- 可以复用硬件
- 性能
- 性能公式
- ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/18/20260218153851413.png,354,137)
- IC：执行的指令条数
- CPI：平均每条指令需要多少周期
- Cycle Time：每周期多长时间 (=1/主频)
- Amdahl 定律（优化上限）
- 如果系统中某部分占比位 f，该部分加速 S 倍，总加速比
- ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/18/20260218154107114.png,227,64)
- IPC、吞吐、延迟
- 延迟：完成单个任务/单条指令/一次请求需要的时间
- 吞吐：单位时间完成多少任务
- IPC：每周期完成多少条指令
- 吞吐 = IPC x 频率
&lt;/textarea&gt;
&lt;h1 id="流水线与冒险"&gt;流水线与冒险&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-71485236"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-71485236" style="display:none;"&gt;
- 流水线与冒险
- 五级流水线核心内容
- 五段功能定义
- IF：取值；PC-&amp;gt;I-Cache；计算 PC&amp;#43;4
- ID：译码；读寄存器；生成立即数
- EX：ALU 运算；地址计算；分支比较
- MEM：访问 D-Cache
- WB：写回寄存器 rd
- 流水线寄存器
- 为了让每段每周期独立推进，段与段之间插入寄存器
- IF/ID、ID/EX、EX/MEM、MEM/WB 它们保存上一段的输出给下一段用（包含数据与控制信号）
- 理想吞吐与延迟
- 非流水：一条指令用 5 个周期，下一条等它结束
- 流水：启动后每周期完成 1 条
- 延迟：单条仍然=5 个阶段
- 吞吐：理想从 1/5 提升到 1
- 流水线的代价
- 需要通多寄存器与控制逻辑
- 分支错预测要清刷流水线
- 结构冲突要加资源或停顿
- 结构冒险
- 定义
- 同一周期内两个阶段需要同一个硬件资源
- 例子
- 单端口存储器：IF 要取值，MEM 要访存
- 同一周期冲突 -&amp;gt; 必须 stall 或改造硬件
- 解决方案
- 分离 I-Cache/D-Cache
- 多端口存储器
- 数据冒险
- 三种依赖
- RAW(Read After Write)真依赖：后读依赖前写，最关键
- WAR(Write After Read)反依赖：后写不能早于前读
- WAW(Write After Write)输出依赖：两条写同一目的寄存器
- 为什么会 RAW：写回晚，读取早
- 读寄存器在 ID
- 写回寄存器在 WB
- Fowwarding （旁路/转发）机制
- 不等写回到寄存器堆，直接把产生的结果从后端绕回到前段使用
- 转发路径
- EX/MEM -&amp;gt; ID/EX（给下一条的 EX 用）
- MEM/WB -&amp;gt; ID/EX （给下一条或下下条的 EX 用）
- Stall(插泡)与 load-use hazard
- load-use 典型序列
- lw r1, 0(r2)
- add r3, r1, r4
- 原因
- lw 的数据到 MEM 末才可用
- add 需要在 EX 用到 r1
- 时间上赶不上，即使有 forwarding 也不够快
- 结论：需要插入一个 bubble
- 实现
- 冻结 PC 和 IF/ID
- 往 ID/EX 注入一个 NOP （控制信号清零）形成 bubble
- 控制冒险
- 定义：PC 的下一值不确定：到底走 PC&amp;#43;4 还是分支目标
- 分支决定在哪里做决定 penalty
- 如果分支在 EX 才能确定
- 在分支结果出来之前，流水线已经取了后续若干条
- 一旦发现分支 taken，就要把错路径指令 flush 掉
- 基础策略
- stall until branch resolved：等分支算出来再取后续
- predict not taken：默认不跳，先取顺序；若实际 taken -&amp;gt; flush
- 动态分支预测：用分支历史表、2-bit 饱和计数器等降低错预测
- BTB：预测 taken 时快速给出目标地址
- 延迟槽
- penalty
- 分支罚时：错预测或 taken 导致需要清空的阶段数
&lt;/textarea&gt;
&lt;h1 id="存储体系"&gt;存储体系&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-68314752"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-68314752" style="display:none;"&gt;
- 存储体系
- 核心矛盾
- CPU 周期是纳秒级，DRAM 访问通常是几十到上百纳秒级，差一个数量级
- 解决思路：分层存储 &amp;#43; 局部性
- Cache 用更贵更快的 SRAM 缓存最近用过的数据块，依赖时间/空间局部性降低平均访存时间
- Cache 基础概念
- Cache line （缓存行）
- Cache 以“行/块“位单位搬运数据
- CPU 读写某地址时，实际会把该地址所在的整条 cache line 搬到 Cache。
- 推论：顺序访问数组通常很友好；跨行访问容易 miss
- Hit/Miss 与三大指标
- Hit time：命中时访问延迟
- Miss rate：未命中比例
- Miss penalty：未命中后去下一级/内存取回的代价
- 平均访存时间 AMAT
- AMAT = Hit Time &amp;#43; Miss Rate * Miss Penalty
- MISS 分类
- Compulsory miss（冷启动）：第一次访问必 miss
- Capacity miss（容量）：工作集超过 Cache 容量
- Conflict miss（冲突）：映射/组相联导致不同块争同一行
- 映射方式：直接映射/全相联/组相联
- 前置数据
- Cache 总容量 (不含tag/元数据) = C bytes
- Cache line 大小 = B bytes
- 总行数 (lines) = L = C/B
- 相联度 (ways) = A
- 组数 (sets) = S = L/A
- 直接映射
- 每个内存块只能映射到唯一一个 cache 行
- 优点：硬件简单、命中快
- 缺点：冲突 miss 多
- 全相联
- 任意内存块可放任意行
- 优点：几乎无冲突 miss
- 缺点：需要对所有行比较 tag（实现复杂、命中更慢、耗能更高）
- 组相联
- 内存块映射到某个 set，但可以放在 set 内任一 way。
- 折中方案
- 地址拆分
- 对于字节编址系统，地址通常拆分为：tag&amp;#43;index&amp;#43;block offset
- offset 块内偏移
- 表示在一条 cache line 內的字节位置
- 位数 offset bits = log2(B)
- index 组索引
- 选择 set
- index bits = log2(S)
- tag 标记
- 用于判断该 set 中某一 way 是否是想要的内存块
- tagbits = Address bits - index bits - offset bits
- 替换策略
- LRU：替换最久没用的行
- Pseudo-LRU（伪 LRU）：用更少 bit 近似 LRU
- FIFO：先进先出
- 写策略
- write-throught（写穿）
- 写命中：同时写 Cache 和下一级
- 优点：下一级始终最新，实现简单
- 缺点：写流量大
- write-back（写回）
- 写命中：只写 Cache，把该行标记为 dirty
- 该行被替换时才写回下一级
- 优点：显著减少写带宽压力
- 缺点：一致性更复杂（多核）
- 写不命中 write miss 的两种策略
- write-allocate（写分配）：先把整条 line 读入 Cache，再在 Cache 上写，与 write-back 搭配
- no-write-allocate（不写分配）：不把 line 加入 Cache，直接写下一级，常与 write-throught 搭配
- 多级 Cache 的 AMAT
- L1/L2 两级
- AMAT=HitTime_L1​&amp;#43;MissRate_L1​×(HitTime_L2​&amp;#43;MissRate_L2​×MissPenalty_L2​)
- 多核：一致性 vs 一致性协议
- Cache Coherence（一致性）
- 目标：对于同一内存地址 X，各核的 cache 中 X 的副本要看起来像一个指在变化，不能读各自的旧值
- 解决办法：一致性协议(MESI)保证写入能让其他核的副本失效/更新
- Memory Consistency（一致性模型，针对不同地址的顺序）
- 目标：规定不同核对不同地址的读写在时间顺序上允许怎么样被观察到
- 是内存模型讨论的问题，涉及重排序与屏障。
- Coherence 解决同一地址副本一致，Consistency 解决不同地址操作的可见顺序，前者靠协议，后者靠内存模型和 fence。
- MESI 协议
- False Sharing
- 定义
- 不同核访问的是不同变量，但是这些变量恰好落在同一条 cache line 里
- 其中一个核写它自己的变量，会把整条 line 的其他核副本失效，导致另一核反复 miss
- 性能问题
- 引发 invalidation（整条 line 粒度）
- 两个核交替写同一条 line 上不同字节
- line 在核之间反复迁移，带来大量一致性流量
&lt;/textarea&gt;
&lt;h1 id="虚拟内存"&gt;虚拟内存&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-74653218"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-74653218" style="display:none;"&gt;
- 虚拟内存
- 要解决的问题
- 抽象：每个进程看到连续的虚拟内存空间（VA）
- 保护：权限控制（R/W/X，用户态/内核态）
- 共享：共享库/共享内存/页共享
- 扩展：按需分页 &amp;#43; 换入换出
- 页
- 虚拟页：进程看到的虚拟地址空间被切分成的固定大小块
- 物理页框：正式物理内存被切成同样大小的块
- 二者的大小相同，比如常见 4KB
- 虚拟内存用虚拟页编号，物理内存用页框编号，页表负责把虚拟页号 -&amp;gt; 物理页框号
- 地址和页对应
- 页大小是 4KB
- 4KB = 4096 字节 = 2^12
- 一个地址的低 12 位代表页内偏移
- 高位表示虚拟页号（VPN）
- 地址转换链路
- VA -&amp;gt; TLB -&amp;gt; page table -&amp;gt; PTE -&amp;gt; PA -&amp;gt; Cache/Memory
- 术语解释
- VA：进程发出的地址
- PA：真实内存地址
- VPN：虚拟页号
- VPO：页内偏移
- PPN：物理页框号
- PTE：页表项，记录 VPN -&amp;gt; PPN 的映射与权限等元数据
- TLB：页表项的高速缓存
- 页大小与地址拆分
- 页大小是 PageSize = 2^k 字节
- offsetBits = k
- VPNBits = AddrBits - k
- 转换过程
- CPU 产生 VA
- MMU 用 VA 的 VPN 去查 TLB
- TLB hit：直接得到 PPN &amp;#43; 权限
- 物理地址 PA = (PPN &amp;lt;&amp;lt; offsetBits) | Offset
- TLB miss：用 VPN 去查页表
- 拿到 PTE
- 有效 -&amp;gt; 填入 TLB -&amp;gt; 形成 PA -&amp;gt; 继续访问
- 无效 -&amp;gt; 触发异常
- 页表结构
- PTE 包含什么
- PPN：物理页框号
- Present/Valid：是否在内存
- R/W/X：读写执行权限
- U/S：用户/内核权限
- Accessed/Referenced(A)：是否被访问过
- Dirty(D)：是否被写过
- 为什么需要多级页表
- 单级页表
- VA 空间大，VPN 组合数巨大
- 每个进程都有一张覆盖整个虚拟空间的大表
- 多级页表做法
- 把 VPN 再拆分成多端：VPN1/VPN2/.../VPNn
- 顶层页表只在需要时为某个范围分配下一级页表
- 没用到的地址区间不分配页表 -&amp;gt; 按需分配页表
- 多级页表的 Page Walk
- 先用 VPN 的高段索引顶层页表，取到二级页表的地址
- 再用下一段索引二级页表页
- 最终找到叶子 PTE 得到 PPN
- 代价：要读多次内存
- TLB
- 没有 TLB
- 每次 load/store 都要走 page walk，可能是 4/5 级
- 意味着每次访问内存前都要额外多次访存
- TLB 的本质
- 缓存最近用过的 VPN -&amp;gt; PPN &amp;#43; 权限
- 命中时地址翻译几乎是常数空间
- 缺页异常
- 典型原因
- Demand paging（按需分页）：页还没加载到内存
- Swap out：页被换出到磁盘
- 权限违规：写只读页
- Copy-on-write：写共享只读页
- 缺页处理流程
- CPU 访问 VA，发现 PTE present = 0 或者权限不符合
- 触发异常，陷入内核（保存上下文，切换到内核栈）
- 内核 fault handler 判断 fault 类型
- 是合法的按需页/非法地址
- 合法
- 找到磁盘位置
- 分配物理页框
- 发起 IO 把页读入内存
- 更新页表 PTE
- 更新 TLB
- 恢复进程执行
- 页面置换
- 当需要一个物理页框但是没有空闲页时，OS 必须选择一个牺牲页换出
- LRU
- Clock/Second-Chance(近似 LRU)
- 维护环形指针
- 每个页有 Accessed/Referenced 位
- 扫描时
- 若 A=0：淘汰
- 若 A=1：把 A 清 0，给第二次机会，指针继续走
- 共享与写时复制(COW)
- 为什么需要 COW
- fork 的语义：子进程得到父进程内存的副本
- 如果把所有页都复制一遍，成本极高
- COW
- fork 后，父子进程的页表都指向同一批物理页框
- 把这些页的 PTE 权限改为只读
- 当父或子尝试写入某页
- 写入触发 page fault
- 内核分配新物理页
- 复制旧页内容到新页
- 修改该进程页表让它指向新页，恢复写权限
- 另一个进程仍然指向旧页
&lt;/textarea&gt;
&lt;h1 id="io-总线"&gt;IO 总线&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-45127386"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-45127386" style="display:none;"&gt;
- IO 总线
- IO 本质上慢
- 设备物理特性：SSD/网卡/磁盘的延迟远远高于 CPU
- 路径长：系统调用、内核态切换、驱动、协议栈、队列管理
- 数据搬运：拷贝次数多、缓存一致性/同步开销高
- I/O 的基本硬件/软件组件
- 设备、控制器、驱动
- 设备：网络、磁盘、USB 设备等
- 控制器：设备侧的执行单元，负责 DMA、队列、寄存器接口等
- 驱动：内核中的软件，负责配置设备、提交请求、处理中断、管理队列
- 数据通路常见参与者
- CPU 核心
- Cache
- 内存
- I/O 互联 (PCIe)
- 设备控制器
- 设备介质
- 轮询 vs 中断
- 轮询
- 做法：CPU 不断读取设备状态寄存器
- 优点：实现简单、延迟可控
- 缺点：浪费 CPU、高并发/多设备会严重占用 CPU
- 中断
- 设备完成后发中断信号，CPU 暂停当前执行，进入中断处理程序(ISR)
- 优点：
- CPU 不需要忙等-&amp;gt;提升整体吞吐
- 缺点：
- 有中断开销：恢复/保存上下文、切换栈、处理 ISR
- 中断频繁造成抖动
- DMA
- 不用 DMA 的程序搬运
- CPU 循环从设备寄存器读数据，再写入内存
- 缺点：CPU 被数据搬运绑死，带宽高、功耗高
- DMA 的本质
- DMA 让设备/控制器直接读写内存，CPU 只做设置与收尾
- CPU 配置 DMA 描述符：内存地址、长度、方向、队列位置
- DMA 引擎通过总线把数据直接写入/读出 DRAM
- 完成后用中断通知 CPU（或写状态寄存器）
- DMA 为什么更快
- 释放 CPU 周期
- DMA 更贴近总线/设备，能更高效做 burst 传输
- 配合 ring buffer/descriptor queue 能批量提交
- 内存映射 I/O 与端口 I/O
- MMIO
- 设备寄存器被映射到某段物理地址空间
- CPU 用普通的 load/store 指令读写这些地址，就等价于读写设备寄存器
- Port-mapped I/O
- 使用专门的 IN/OUT 指令访问 I/O 端口空间
- 现代系统多以 MMIO 为主
- 零拷贝
- 传统读文件再发网络的拷贝路径
- read()：磁盘-&amp;gt;内核页缓存-&amp;gt;拷贝到用户缓冲区
- write()：用户缓冲区-&amp;gt;再拷贝回内核 socket buffer -&amp;gt; 网卡 DMA 发走
- 至少需要两次 CPU 拷贝（内核&amp;lt;-&amp;gt;用户，用户&amp;lt;-&amp;gt;内核）
- sendfile 思路
- 直接让内核把 page cache 中的文件页挂到 socket 发送队列
- 避免拷贝到用户态再拷回内核态
- 网卡通过 DMA 从内核缓冲直接发
- mmap 思路
- mmap 把文件映射进进程地址空间
- 应用像访问内存一样访问文件内容，缺页时 OS 按需把页载入 page cache
- 适合随机访问，但是直接转发场景 sendfile 更适合
&lt;/textarea&gt;</description></item><item><title>计算机网络</title><link>https://blog.ans20xx.com/posts/infra/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E5%AD%A6%E4%B9%A0/</link><pubDate>Mon, 16 Feb 2026 00:00:00 +0000</pubDate><guid>https://blog.ans20xx.com/posts/infra/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C%E5%AD%A6%E4%B9%A0/</guid><description>&lt;h1 id="网络分层--端到端路径"&gt;网络分层 + 端到端路径&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-84763251"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-84763251" style="display:none;"&gt;
- 网络分层 &amp;#43; 端到端路径
- 分层模型
- 链路层 (LINK)
- 负责：同一链路/局域网内把 IP 包送到下一跳
- 典型概念：以太网、WIFI、ARP、MAC、MTU
- 网络层 (IP)
- 负责：跨网络把包从源 IP 路由到目的 IP
- 典型概念：IP 地址、路由表、NAT、ICMP、分片
- 传输层 (Transport)
- 负责：端到端进程通信，提供端口与可靠性
- 应用层 (Application)
- 负责：定义应用语义
- 端到端路径（一次 HTTPS URL 发生了什么）
- URL 解析与策略决策（应用层前置）
- 浏览器先拆 URL：
- schema：https -&amp;gt; 需要 TLS
- host：example.com -&amp;gt; 需要 DNS
- port：默认 443
- path：/index.html
- 是否可用缓存
- 是否复用已有连接
- DNS 解析：把域名变成 IP
- 目标：得到服务器的 IP (A=IPv4/AAAA=IPv6)
- 典型流程
- 浏览器/OS 先查本地缓存
- 没命中 -&amp;gt; 向递归解析器发起查询
- 递归解析器询问：根 -&amp;gt; TLD -&amp;gt; 权威 DNS
- 返回 IP &amp;#43; TTL
- TTL 决定缓存时长
- 同一域名可能返回多个 IP：简单的负载均衡，但是不可控
- TCP 连接（传输层）
- 目标：建立一条到服务器IP:443的 TCP 连接
- 典型流程
- 客户端选一个临时端口作为源端口
- 形成 socket 四元组
- 三次握手完成后，连接进入 ESTABLISHED
- 连接的唯一标识符是四元组
- NAT 环境下：srcIP 可能是私网 IP，但是公网会被 NAT 改写
- RTT 大时，建连成本高：三次握手至少消耗 1 个 RTT
- NAT：内网 IP 也能访问公网（网络层/链路层交叉）
- 大多数客户端在 NAT
- 内网 192.168.x.x:ramdomPort
- 出口 NAT 把它映射成：公网 IP:另一个 Port
- NAT 设备维护一张映射表（内网四元组&amp;lt;-&amp;gt;公网四元组）
- 映射有超时：长时间无数据可能被回收
- 并发连接太多可能端口耗尽
- TLS 握手：在 TCP 上建立安全通道
- 目标
- 验证服务器身份
- 协商密钥
- 后续数据用对称加密 &amp;#43; 完整性保护
- TLS 发生在 TCP 建立之后、HTTP 之前
- 证书用于“证明对方是它声称的域名“
- 发送 HTTP 请求并接受响应
- HTTP 内容
- 请求行：GET /index.html
- 头：Host、User-Agent、Accept、Cookie
- 体：GET 通常无，POST 常见
- 服务器返回
- 状态码：200/301/404/500
- 头：Content-Type、Content-Length、Set-Cookie
- 体：HTML/JSON/图片等
- 连接复用与关闭：不是每次都重新握手
- HTTP/1.1 默认倾向长连接（Keep-Alive）
- 浏览器有连接池，会复用到同一个 host:port 的连接
- 关键概念锚点
- RTT：网络交互的时间单位
- RTT = 一个来回的延迟
- 建连/TLS/重传都依赖 RTT
- 高 RTT 下，交互次数越多越慢
- 吞吐与 BDP(带宽-时延积)
- MTU 月 MSS
- MTU：链路层最大承载
- MSS：TCP 单段可承载的应用数据最大值
- 包太大可能被分片或者丢弃
- 合理 MSS 能减少分片风险，提升稳定性
&lt;/textarea&gt;
&lt;h1 id="tcp-精讲"&gt;TCP 精讲&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-52834761"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-52834761" style="display:none;"&gt;
- TCP 精讲
- TCP 本质：在不可靠 IP 上提供可靠字节流
- TCP 提供内容
- 面向连接：双方都维护连接状态（序号、窗口、定时器等）
- 可靠、有序：丢包重传、乱序重排、去重
- 字节流：没有消息边界（粘包/拆包的根源）
- 流量控制：避免把对方接收缓冲打爆（rwnd），端到端
- 拥塞控制：避免把网络打爆，端到网络
- 报文段关键字段
- Sequence Number：本段数据在字节流里的起点序号
- Acknowledgment Number：我期望收到的下一个收到的序号
- Flags：SYN/ACK/FIN/RST/PSH/URG
- Window（rwnd）：接收端通告窗口
- Options：MSS，Window Scale
- 连接建立：三次握手
- 标准流程
- C-&amp;gt;S: SYN（seq=x）
- S-&amp;gt;C: SYN&amp;#43;ACK (seq=y,ack=x&amp;#43;1)
- C-&amp;gt;S: ACK (ack=y&amp;#43;1)
- ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/17/20260217111458329.png,514,267)
- 为什么必须三次
- 双方都要确认两件事
- 对方能收：对方收到了我的东西
- 对方能发：对方能把东西发回来让我收到
- 两次握手只能证明客户端 -&amp;gt; 服务端这条路通了；但是服务端无法证明服务端-&amp;gt;客户端也通、且客户端确实收到了服务端的回应，第三次 ACK 就是让服务端拿到这个确认（服务端无法确认客户端能收）
- 如果只有两次握手
- 服务端回 SYN &amp;#43; ACK 丢失了
- 服务端仍为连接建立，开始分配连接资源，等待数据
- 客户端实际上没有收到任何回应，会重试或放弃
- 三次握手还能防止历史重复 SYN 造成的“幽灵连接“
- 历史重复 SYN 指的是：某次旧连接尝试时发出的 SYN，由于网络拥塞、路由绕行、链路重传、设备缓存/异常等原因，在很久之后才到达服务器
- 如果服务器把旧 SYN 当新建连：
- 服务器会分配资源
- 如果后续又出现旧 ACK 包，让服务器误以为连接已建立，交付错误的数据
- 幽灵连接：就是服务端自嗨式建立，客户端不认可
- 半连接队列与 SYN Flood
- 服务端收到 SYN 后进入 SYN_RECV，会占用半连接资源（backlog）
- SYN Flood：大量 SYN 不完成握手 —&amp;gt; 半连接队列被占满 -&amp;gt; 正常连接进不来
- 典型缓解：SYN cookies、增大 backlog、限速、丢弃策略、前置抗 DDoS
- 连接关闭：四次挥手、半关闭
- 主动关闭方 A-&amp;gt;B：FIN（不再发了）
- B -&amp;gt; A：ACK （知道了）
- B -&amp;gt; A：FIN （我也不再发了）
- A -&amp;gt; B：ACK （知道了）
- ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/17/20260217113908534.png,440,230)
- 为什么常见是四次
- TCP 是全双工：A 不发 != B 不发
- B 可能还有数据要发完，所以 ACK 和 FIN 分开
- 半关闭
- A 发 FIN 表示 A 的发送方向关闭，但仍然可以接受 B 的数据
- 工程上常见于“请求发送完但继续接受响应流“的场景
- RST：不是正常关闭，是异常打断
- RST（Reset）是 TCP 里面非常硬的控制信号：立刻终止连接/拒绝连接
- 不是正常的四次挥手关闭，告诉对方：“这条连接不存在/状态不一致，马上停“
- RST 常见原因
- 端口没监听
- 应用崩溃/强制关闭
- 中间设备丢弃状态后注入 RST
- TIME_WAIT、CLOSE_WAIT
- 主动关闭方在发送最后一个 ACK 后进入 TIME_WAIT，并等待 2MSL。
- 必须 TIME_WAIT
- 保证对方收到最后 ACK：最后 ACK 丢了，对方重发 FIN；TIME_WAIT 还能再次 ACK
- 让旧连接的延迟报文过期：避免旧数据混入新连接
- 为什么是 2MSL
- MSL = 报文在网络中可能存活的最大时间
- 等待 2MSL 是为了覆盖对方 FIN 重传 &amp;#43; 我 ACK 再次发送等往返的不确定性。
- TIME_WAIT 多了怎么办
- 常见场景：短连接高 QPS
- 治理思路
- 优先做连接复用：HTTP Keep-Alive/连接池
- 调整应用行为：减少主动 close、合并请求、复用长连接
- 系统参数：允许端口更快复用等
- 通常是我方主动关 &amp;#43; 短连接
- CLOSE_WAIT 多是什么
- CLOSE_WAIT 表示：对方已经 FIN 了，但是我方应用还没有 close
- 常见原因：应用层忘记关闭连接、连接泄露、线程阻塞导致没有走到 close
- 通常是我方没关
- 可靠传输机制：序号、ACK、重传、SACK
- 序号与累计 ACK
- TCP 把数据看成连续字节流：每个字节都有序号
- ACK 是累计的：ack = N 表示 0..N-1 都收到了，下一步要 N
- 好处：ACK 开销小
- 坏处：遇到乱序/丢包，单靠累计 ACK 不够精细
- 重传触发机制
- 超时重传（RTO）：等不到 ACK 就重发（RTO 动态估计，基于 RTT 抖动）
- TCP 发送一个数据段后，会期待收到 ACK，如果在某个时间窗口没有等到 ACK，TCP 可能认为丢了，于是触发超时重传。
- 等多久的时间阈值就是 RTO（Retransmission Timeout）。
- 触发条件：发送后，计时器到期，仍未收到能确认该段的 ACK
- 行为：重传未确认的数据段
- RTO 动态估计
- 网络 RTT 会变，而且会抖动，RTO 必须跟随着链路实际 RTT 变化
- SRTT &amp;#43; RTTVAR -&amp;gt; RTO
- 名词解释
- RTT sample：一次测得的 RTT（从发出某段到收到它对应 ACK 的时间）
- SRTT (Smoothed RTT)：平滑后的 RTT 均值。
- RTTVAR：RTT 的波动。
- RTO = SRTT &amp;#43; 4 * RTTVAR
- SRTT 给出正常情况下 ACK 来回要多久
- RTTVAR 给出不确定性有多大
- 乘以 4 是给一个安全幅度：抖动越大，RTO 增长越明显，避免误重传
- 指数退避：超时一次，RTO 翻倍
- RTO 重传 vs 快速重传
- RTO 重传
- 触发：计时器到期，没等到 ACK
- 特点：更“保底“，但通常更慢；而且代表网络更糟糕
- 常伴随：指数退避，cwnd 通常会大幅下降
- Fast Retransmit （快速重传）
- 触发：重复 ACK
- 特点：更快，更轻判拥塞
- 一半比 RTO 更温和
- 快速重传（Fast Retransmit）：收到多个重复 ACK （典型 3 个）推断某段丢失了，立即重传）
- RTO 重传局限性
- 必须等待一段时间没收到 ACK 才重传
- 在 RTT 较大或 RTO 比较保守时，恢复会很慢
- 快速重传的思路
- 不等超时，利用接收端反馈的 ACK 形态更早推断丢包
- 重复 ACK
- 如果接收端收到了更后面的段，但是缺了中间某段
- 不能把乱序段交付给应用
- 会继续发送同一个累计 ACK，告诉发送端还在等从 1000 开始的那段，后面来的我先缓存
- ACK 值没前进，但又反复出现的 ACK 就是重复 ACK
- 3 个重复 ACK 会触发快速重传
- 收到 3 个重复 ACK 代表某段大概率丢了
- 立即重传缺口对应的最早未确认段
- 快恢复
- 原因
- 收到快重传代表完了给没有完全堵死（还能持续收到 ACK）
- 不必像 RTO 那样极度保守把发送速率打到很低
- Reno 直觉
- 触发快速重传后，认为发生拥塞 -&amp;gt; 降低 cwnd
- 但利用重复 ACK 的到来，允许发送端在恢复期间适度继续发，避免完全停摆
- 当收到新的 ACK 后，退出快恢复，回到拥塞避免阶段
- SACK（选择确认）
- 痛点：累计 ACK 信息不够
- 当出现丢包 &amp;#43; 乱序到达时，累计 ACK 只能不断重复同一个 ACK=N，发送端只知道缺口在 N，不知道
- 后面哪些段已经到了
- 究竟丢失了几段，丢失了哪几段
- 重传时是否把已到达的也重传
- 目标是把接收端实际收到的乱序块显式告诉发送端
- SACK 是什么：在 ACK 里带已收到的区间块
- SACK 是 TCP 的一个 Option，启用后
- 接收端在 ACK 报文的 TCP Options 中携带若干个 SACK blocks
- 每个 block 描述一个已经收到的连续字节区间
- SACK block 的语义
- SACK block = (Left Edge, Right Edge)
- 表示接收端已经收到：[LeftEdge, RightEdge) 这个区间
- ACK 字段仍然存在，表示最左侧连续已收前缀的下一个期待序号
- 启用流程：SACK Permitted
- SACK 需要在三次握手时协商
- 双方在 SYN/SYN&amp;#43;ACK 中携带 SACK Permitted 选项
- 只要双方都声明支持，后续才会在 ACK 中携带 SACK blocks
- 乱序与重排
- 网络层可能乱序，TCP 负责在接收端按序交付给应用
- 接收端会缓存乱序段，等缺口补齐再交付
- 滑动窗口
- 痛点
- 如果每发一段就必须等 ACK 再发下一段，吞吐会被 RTT 严重限制：链路大部分时间在等回音。
- 滑动窗口允许发送端在未收到 ACK 前，先发出一批数据，让数据在路上飞，从而把链路利用起来。
- 窗口 = 允许在途未确认数据的上限；窗口越大，在高 RTT 链路上容易跑满带宽
- rwnd（receive window，接收窗口，流量控制）
- 由接收端通告（在 TCP 头的 Window 字段里）
- 表示接收端缓冲区还剩多少空间
- 目的：防止发送端把接收端内存撑爆
- cwnd（congestion window，拥塞窗口，拥塞控制）
- 由发送端维护
- 表示发送端认为网络当前能够承受的在途数据量上限
- 目的：防止把网络打爆
- swnd（send window，实际发送窗口）
- 发送端真正允许“在途未确认“的上限
- 近似于 min(rwnd,cwnd)
- 发送端视角：窗口如何限制发送
- 发送端维护两个关键指针
- SND.UNA：最早未确认的序号(UnACKed)
- SND.NXT：下一个准备发送的序号(Next to send)
- 窗口约束可以理解为：
- 允许发送的序号范围：[SND.UNA, SND.UNA &amp;#43; swnd)
- 已发送未确认的范围：[SND.UNA, SND.NXT)
- 还能继续发送的额度：swnd - (SND.NXT - SND.UNA)
- ACK 到来时发生什么
- 收到 ACK，把 SND.UNA 往前推进
- 已确认数据从窗口左边滑出
- 窗口右边腾出了空间，允许继续发送新的数据
- 接收端视角：为什么会出现 dupACK/乱序缓存
- 接收端维护两个关键指针
- RCV.NXT：下一段按序期望的序号
- 接收缓冲：可以暂存乱序到达的段
- 出现乱序
- 接收端会缓存后到的段，但 ACK 仍然回 RCV.NXT（累计 ACK）
- 发送端看到重复 ACK (dupACK)，可能触发快速重传
- 窗口、吞吐、RTT
- 吞吐上限 = 窗口大小/RTT
- RTT=100ms，窗口=64KB，吞吐=64KB/0.1s=640KB/s
- BDP（带宽-时延积），窗口需要=BDP 才能跑满带宽
- 表示一条网络路径在任意时刻在路上飞着的数据量大概有多少，也叫管道容量
- BDP = 带宽 * 往返时延 (RTT)
- 想要把链路跑满，发送端需要让“在途未确认数据“接近 BDP
- 在途未确认数据主要受窗口限制：in_flight &amp;lt;= min(cwnd, rwnd)
- 要跑满带宽，需要窗口大小 &amp;gt;= BDP
- 零窗口与窗口探测
- 如果接收端缓冲满了，会通告 rwnd=0
- 发送端此时必须停止发送新数据，但是要避免死锁
- TCP 有 persist timer：周期性发送 Zero Window Probe 探测包
- 一旦接收窗口恢复，发送端继续发
- 粘包/拆包
- 粘包：在发送端 send() 了两条业务消息，接收端一次 recv() 读到了两条合在一起的字节。
- 拆包：在发送端 send() 了一条业务消息，接收端需要 recv() 多次才能读完
- TCP 不保证 send 一次就对应端 recv 一次
- 原因
- TCP 的抽象是流
- 像读文件一样读一串字节
- send()：把字节追加到发送缓冲区
- recv()：从接收缓冲区取走当前可读的字节
- 分段由 TCP 决定，不由应用决定
- 接收端读取粒度由应用决定
- 解法
- 在应用层定义消息边界
- 长度前缀（Length-Prefixed，最通用）
- 格式 | 4-byte length (big-endian) | payload bytes... |
- length 表示 payload 长度
- 接收端先读满 4 字节得到 length，再继续读满 length 字节
- 分隔符（Delimiter-Based，适合文本协议）
- 行协议：以 \r\n 结尾
- 固定长度（Fixed-Size）
- 每条消息固定 N 字节
- 拥塞控制
- 拥塞不是对端瘦不下，那是流量控制 rwnd，拥塞是网络路径上的队列/链路承载不住
- 路由器/交换机队列堆积 -&amp;gt; RTT
- 队列溢出 -&amp;gt; 丢包
- 丢包/排队抖动 -&amp;gt; 吞吐下降、延迟暴涨
- 拥塞控制控制的是发送端允许在网络中飞着的未确认数量，cwnd
- Reno 的两个核心状态变量
- cwnd：拥塞窗口（发送端认为网络现在能承受的在途数据量）
- ssthresh：慢启动阈值，决定从指数增长到线性增长的分界点
- 四个阶段
- 慢启动
- 目的：刚开始不知道网络能承受多少，先快速试探
- 规则：
- 初始 cwnd 为一个较小值，历史是 1MSS
- 每收到一个 ACK，cwnd 增加 1MSS
- 因为一个 RTT 内会收到大约 cwnd/MSS 个 ACK，所以每个 RTT cwnd 近似翻倍
- 每 RTT 把在路上的包数翻倍，迅速逼近瓶颈带宽
- 太快可能把队列灌满，引发丢包
- 拥塞避免
- 当 cwnd 到达或超过 ssthresh，从慢启动切换到拥塞避免
- 每 RTT 让 cwnd 大约增加 1 MSS，每个 ACK 增加 MSS*MSS/cwnd
- 大多数时间连接处于的阶段
- 快重传：用 dupACK 提前判断丢包，不等 RTO
- 触发条件
- 收到 3 个重复 ACK dupACK
- ACK 号不前进，连续出现多次
- 快恢复：丢包后减速但不中断，比 RTO 温和
- 发生快重传后的关键动作
- 设定新的值
- ssthresh = cwnd/2
- cwnd 不回到很小，而是进入快恢复逻辑
- 当收到能够推进 ACK 的新 ACK，退出快恢复，进入拥塞避免
- RTO vs dupACK
- RTO：最严重，认为网络可能很糟
- ssthresh = cwnd / 2
- cwnd 下降到很小，经典是 1MSS
- 回到慢启动（重新探测）
- RTO 通常会指数退避，导致恢复更慢
&lt;/textarea&gt;
&lt;h1 id="http-与-web-体系"&gt;HTTP 与 Web 体系&lt;/h1&gt;
&lt;div
class="mindmap-container"
id="mindmap-83541726"
style="width:100%; height:860px; min-height: 860px;"
&gt;&lt;/div&gt;
&lt;textarea id="mindmap-data-83541726" style="display:none;"&gt;
- HTTP 与 Web 体系
- HTTP 基础语义
- HTTP 是无状态应用层协议：每个请求都自包含信息，状态由 Cookie/Token/Session 等机制在应用层实现
- HTTP 定义的语义，具体传输方式由 HTTP/1.1、HTTP/2、HTTP/3 决定
- 方法：语义 &amp;#43; 幂等 &amp;#43; 安全性
- GET：获取资源，应当安全&amp;#43;幂等
- HEAD：只要响应头，不要响应体，用作探测/缓存探测
- POST：提交数据，创建子资源或触发动作，通常非幂等
- PUT：整体替换/创建指定资源，通常幂等
- PATCH：部分更新，是否幂等取决于语义设计
- DELETE：删除资源，通常幂等
- OPTIONS：探测服务器能力（常用于 CORS 预检）
- 状态码
- 1xx：信息（少见）
- 2xx：成功
- 200 OK
- 201 Created 创建成功
- 204 No Content 成功但无响应体
- 3xx：重定向
- 301 永久
- 302 临时(历史语义混乱)
- 303 让客户端用 GET 拿
- 307/308 保持方法不变
- 4xx：客户端错误
- 400 参数/格式问题
- 401 需要认证
- 403 拒绝
- 404 不存在
- 409 冲突
- 429 限流
- 5xx：服务器错误
- 500、502 上游坏/网关问题
- 503 不可用
- 504 上游超时
- Header
- 通用与内容相关
- Content-Type：实体类型（json/html...）
- Content-Length：长度（HTTP/1.1 重要）
- Content-Encoding：gzip/br 等压缩
- Accept/Accept-Encoding/Accept-Language：内容协商
- Host：HTTP/1.1 必须（虚拟主机）
- Connect：连接管理（HTTP/2 中不再使用）
- 会话与安全
- Cookie/Set-Cookie
- Authorization (Bearer/Basic 等)
- Origin/Refer：CORS/CSRF 相关
- Strict-Transport-Security：强制 HTTPS
- Content-Security-Policy(CSP)：前端安全策略
- 缓存相关
- Cahce-Control
- Expires
- ETag/If-None-Match
- Last-Modified/If-Modified-Since
- Vary
- Age （代理缓存的年龄）
- Pragma：no-cache （历史兼容）
- HTTP 缓存
- 两类缓存：强缓存vs协商缓存
- 强缓存
- 命中强缓存时浏览器直接用本地缓存，不走网络
- 典型控制
- Cache-Control: max-age=
- Expires：老机制
- 协商缓存（发请求但可能不下载）
- 浏览器发请求带校验文件，服务器判断资源是否变化
- ETag 流程
- 响应：ETag: &amp;#34;xxx&amp;#34;
- 下次请求：If-None-Match: &amp;#34;xxx&amp;#34;
- 若未变：返回 304 Not Modified
- Last-Modified 流程
- 响应：Last-Modified: ...
- 下次请求：If-Modified-Since：...
- 未变：304
- Cache-Control 关键指令
- max-age=N：资源在 N 秒内视为新鲜（强缓存）
- no-store：绝对不缓存
- no-cache：可以缓存，但每次使用前必须去服务器验证
- must-revalidate：过期后必须验证，不能用陈旧副本
- public：允许被共享缓存（CDN/代理）缓存
- private：只允许浏览器私有缓存，不允许共享缓存缓存
- s-maxage=N：给共享缓存的 max-age
- Vary 存储维度的分桶键
- Vary 告诉缓存，同一个 URL 的响应会因为某些请求头不同而不同
- Vary：Accept-Encoding，gzip/br 不同版本要分开缓存
- Vary：Origin：CORS 常见
- Vary 过多会让缓存碎片化，命中率下降
- ETag 的强弱、代理与一致性
- 强 ETag：字节级一致才算命中
- 若 ETag (W/)：语义上等价即可
- HTTP/1.1：连接、报文、队头阻塞
- HTTP/1.1 报文与传输
- 文本协议：请求行 &amp;#43; 头 &amp;#43; 空行 &amp;#43; body
- 连接默认与持久化：Keep-Alive （减少握手成本）
- Chunked Transfer-Encoding：服务端可流式传输，不提前知道 Content-Length
- HTTP/1.1 的性能问题：应用层 HOL
- 同一连接上请求/响应按顺序进行
- 一个慢请求会阻塞后续的请求（队头阻塞）
- HTTP/2：二进制分帧与多路复用
- HTTP/2 为什么快
- 二进制分帧：把大消息拆成帧
- 多路复用：多个 stream 在同一 TCP 连接上并发交错传输
- 头部压缩：HPACK（减少重复头部开销）
- HTTP/2 性能问题：TCP 层 HOL(队头阻塞)
- TCP 一旦丢包，需要重传并按序交付
- 丢的那段之前的洞补不上，后续数据即使到了也不能交给上层
- 结果：一个丢包会让同连接上所有 stream 都受影响
- HTTP/3/QUIC
- 核心变化
- HTTP/3 跑在 QUIC 上，QUIC 基于 UDP
- QUIC 自己实现可靠传输与拥塞控制
- 按 stream 提供独立的有序交付，一个 stream 丢包不影响其他 stream 的交付
- TLS 与 HTTPS
- 位置与成本
- HTTPS = TCP 建连后再 TLS 握手，再 HTTP
- 首次访问慢常见原因：DNS &amp;#43; TCP &amp;#43; TLS 多次 RTT
- 解决的问题
- 身份认证：验证连接的是域名的真实服务器
- 机密性：传输内容加密
- 完整性：防篡改
- TLS 握手
- TLS 1.2
- ClientHello：客户端支持的版本/套件，随机数，SNI 等
- ServerHello：选定版本/套件、随机数，让双方对本次会话参数单程一致
- Certificate：服务器发送证书链，客户端据此验证：链是否可信、域名是否匹配、是否过期，用途是否正确
- ServerKeyExchange：服务器提供 ECDHE 参数与公钥，客户端通过验证签名，确保密钥协商参数的安全性
- ServerHelloDone：告诉客户端，材料已经发完
- ClientKeyExchange：客户端协商参数，客户端发送自己的 ECDHE 公钥，双方据此计算共享 secret
- ChangeCipherSpec &amp;#43; Finished：双方切换到加密通信并校验握手完整性
- Web 会话：Cookie/Session/Token
- Web 会话解决的问题
- 认证：你是谁
- 授权：你能做什么
- 会话保持：登录态怎么持续、怎么过期、怎么注销
- 安全边界：防窃取、防伪造、防重放、防跨站
- Cookie：浏览器自动携带的会话容器
- Cookie 是浏览器保存的一组 key=value，由服务器通过响应头 Set-Cookie 下发。
- 后续请求中，浏览器会按照规则自动带上 Cookie 头，无需 JS 参与
- Cookie 关键属性
- Domain/Path：决定发送范围
- Domain=example.con：子域也可能携带
- 不设置 Domain：仅当前 host
- Path=/api：只对当前路径生效
- Expires/Max-age：决定生命周期
- Max-Age=0：立即删除
- Session Cookie：不设过期时间，随浏览器进程结束而清理
- Secure：只在 HTTPS 发送
- HttpOnly：禁止 JS 读取
- 有 HttpOnly 的 cookie，JS 读取不到
- 不能防止 CSRF
- SameSite：控制跨站请求是否携带
- Strict：几乎不跨站携带
- Lax：默认常用
- None：允许跨站携带，必须同时设置 Secure
- Session：服务端保存状态，客户端只存门票
- Session 的典型模式
- 服务端维护 session_id -&amp;gt; session_data
- 客户端用 Cookie 存 session_id
- 每次请求：浏览器自动带 cookie -&amp;gt; 服务端查 session store -&amp;gt; 识别用户
- Token（尤其是 JWT）：把身份证明带在请求里
- Token 的核心模式
- 服务端签发 token 给客户端
- 客户端后续请求带 token
- 服务端验证 token 来识别用户
- JWT 结构
- JWT=header.payload.signature
- header：算法等元信息
- payload：claims 声明
- signature：服务端用密钥签名
- Refresh Token 与 Access Token
- Access Token：短有效期，每次用于请求
- Refresh Token：长有效期，用于换取新的 Access Token
- 安全边界：XSS、CSRF、会话固定、重放
- XSS（跨站脚本）
- 若 token/cookie 可以被 JS 读取，xss 可以直接窃取并盗用
- cookie 用 HttpOnly &amp;#43; Secure
- CSP 减少脚本注入
- 输入输出编码 &amp;#43; 模板转义
- CSRF 跨站请求伪造
- CSRF 利用浏览器自动携带 cookie
- 攻击者诱导用户在登录态下访问恶意页面，发起跨站请求到受害站点
- 防护手段
- SameSite Cookie （优先）
- CSRF Token（表单/请求头携带，服务端校验）
- 检查 Origin/Rerfer
- 同源策略与 CORS
- 痛点
- 浏览器安全模型默认假设：不同站点之间互不信任
- 浏览器用同源策略（Same-Origin Policy，SOP）限制一个网页脚本能读取另一个来源的资源内容
- 业务需要跨域调用 API，有了 CORS（CrossOrigin Resource Sharing）：在 SOP 的基础上提供一个受控的放行机制。
- Orgin 源是什么：同源的三元组
- Origin = scheme &amp;#43; host &amp;#43; port
- https://example.com:443
- 同源策略具体限制
- 跨源读被禁止
- JS 用 fetch 请求 https://xxx.com，请求可能能发出去，但是浏览器会阻止 JS 读取响应，除非响应满足 CORS 规则
- 跨源写/发送很多情况下是允许的
- &amp;lt;form action=&amp;#34;xxx.com&amp;#34; method=&amp;#34;POST&amp;#34;&amp;gt; 可以提交，因此 CSRF 能成立，请求可以发出去，但是页面读取不到 bank.com 的响应内容
- 部分资源加载天然跨源允许
- &amp;lt;img&amp;gt; &amp;lt;scirpt&amp;gt; &amp;lt;link&amp;gt; 浏览器允许加载这些资源
- CORS 本质：服务器声明允许，浏览器执行拦截
- 浏览器在请求带上 Origin
- 服务器在响应返回 Access-Control-* 头
- 浏览器据此决定：是否把响应暴露给 JS
- CORS 两种请求路径：简单请求 vs 预检请求
- 简单请求
- 方法是 GET/HEAD/POST
- Content-Type 只能是三种之一
- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
- 请求头只能是 CORS-safelistedhead，不能随意加自定义头
- 预检请求
- 不满足简单请求条件，浏览器会先发送一个 OPTIONS 请求，询问服务器是否允许
- 预检请求携带关键头
- Origin: https://a.com
- Access-Control-Request-Method：PUT
- Access-Control-Request-Headers：x-token（将要携带的自定义头列表）
- 服务器如果允许，需要在 OPTIONS 响应返回
- Access-Control-Allow-Origin
- Access-Control-Allow-Methods
- Access-Control-Allow-Headers
- Access-Control-Max-Age（预检缓存多久）
- 通过后，浏览器才会发真实请求
- CORS 响应头：每个头怎么用
- Access-Control-Allow-Origin
- 指定允许的 Origin
- 可以是精确 Origin 或者 * （允许任意源），如果要带 Cookie/凭证，不能用 *
- Access-Control-Allow-Methods
- 允许的方法列表：GET/POST
- 主要用于预检响应
- Access-Control-Allow-Headers
- 允许的自定义请求头列表
- 必须覆盖预检中 Access-Control-Request-Headers 请求到的那些头
- Access-Control-Allow-Credentials
- true 表示允许携带凭证，例如 Cookie
- 配合前端请求 credentials: &amp;#39;include&amp;#39;
- Access-Control-Expose-Headers
- 默认情况下，JS 能够读取的响应头很有限，如果希望暴露，必须显式添加
- Vary: Origin
- 按 Origin 分桶缓存
- DNS 解析
- 基础对象
- 记录类型
- A(IPv4)
- AAAA(IPv6)
- CNAME(别名)
- NS(权威)
- 解析角色
- Stub resolver（客户端/OS）
- Recursive resolver（递归解析器：运营商/公司 DNS/公共 DNS）
- Authoritative server （权威 DNS）
- 正向代理、反向代理、透明代理与隧道
- 代理：本质上是位于客户端与服务端之间的一种中间节点，代表一侧发起连接、转发数据、并可能施加策略
- 承担职责
- 路由/转发：把请求送到正确上游
- 安全控制：鉴权、WAF、访问控制
- 性能优化：连接复用、压缩、缓存、限流
- 可观测性：日志、指标、追踪、采样
- 协议转换：HTTP &amp;lt;-&amp;gt; grpc 等
- 正向代理
- 正向代理站在客户端一侧，客户端明确知道自己在用代理，由代理代表客户端访问外部资源
- 反向代理
- 反向代理站在服务端一侧，对外它像真正的服务器入口，对内把请求转发到后端服务
- 反向代理常用原因
- 隐藏后端拓扑：外部只有一个入口
- 统一 TLS 终止：证书集中管理
- 统一流量治理：限流、熔断、灰度、路由、鉴权
- 连接复用：对外维持大量连接、对内复用少量连接
- 观测与审计：同一日志/指标/链路追踪
- 透明代理
- 隧道：Connect 到底干了什么
- 隧道：代理不解析上层协议，只做字节透传。
- HTTP Connect 隧道流程
- 客户端先对代理发 CONNECT
- 客户端 -&amp;gt; 代理
- CONNECT bank.com:443 HTTP/1.1
- Host: back.com:443
- 代理建立到目标的 TCP 连接
- 代理 -&amp;gt; bank.com:443：发起 TCP 连接
- 代理回客户端
- HTTP/1.1 200 Connect Established
- 隧道建立，字节透传
- 客户端与 bank.com 的 TLS 握手数据会被原封不动地通过代理转发，代理看不懂也不需要看懂
- TLS 会话在客户端&amp;lt;-&amp;gt;目标服务器之间建立
- 负载均衡：L4 vs L7、算法、会话保持、健康检查
- 目标
- 扩展性：把流量分散到多台实例，提高吞吐
- 高可用：实例故障自动摘除，流量不中断或快速恢复
- 性能：就近、减少重连、减少排队、提升尾延迟
- 治理：灰度、限流、熔断、路由、可观测
- 边界
- LB 解决的是把请求推送到谁，不解决业务正确性
- LB 层任何自动重试/超时，都可能改变业务语义
- L4 与 L7 的区别
- L4 LB（传输层）：基于 IP/端口/连接做转发
- 优点：性能高，对协议透明
- 缺点：无法基于 URL/Header 做路由
- 常见实现：内核转发、四层 VIP、DSR、NAT 模式等
- 适合长连接：WebSocket、MQ、数据库连接等
- L7 LB（应用层）：理解 HTTP，按照 Host/Path/Header/Cookie 路由
- 常见常见：API Gateway、Ingress、CDN 回源入口
- 负载均衡算法
- Round Robin（轮询）
- 依次分发到每个后端
- 适合：请求耗时相近、后端同质、短连接
- 问题
- 后端性能差异大时不均衡
- 长连接场景下连接一旦分配就会粘住，RR 也会失衡
- Weighted Round Robin
- 按权重分配（机器规格不同，冷热分层）
- 适合：异构机器，逐步扩容
- 问题：权重需要维护；负载时间随时间变化时仍然不准
- Least Connections（最少连接）
- 连接数最少的后端连接
- 适合：长连接、连接数与负载相关的业务
- 问题：
- 连接数不等于负载
- 需要 LB 维护连接计数状态
- Least Request/Least Load（最少请求/最小负载）
- 按当前活跃请求数或综合指标选择
- 适合：请求耗时差异大，尾延迟敏感
- 问题：实现复杂、依赖观测准确性；指标延迟可能导致震荡
- Random/Power of Two Choices（随机/二选一）
- 随机挑两个后端，选最优者
- 优点：简单且实际效果接近最优，分布更稳定，大规模下能显著降低热点概率
- 适合：大规模集群、需要避免中心化状态
- Consistent Hash（一致性 hash）
- 按 key 映射到固定节点
- 适合：
- 需要缓存命中（本地 cache）
- 会话保持（不想引入共享 session）
- 分片存储（按 key 分片）
- 关键性质：节点增减时迁移量小
- 问题：
- 热点 key 会压垮单节点（热点拆分/虚拟节点）
- 节点故障时 key 重映射会引发抖动。
- 会话保持
- 为什么需要 sticky
- 后端有状态
- 本地缓存必须命中
- WebSocket/长连接绑定到某实例
- 常见 sticky 手段
- Source IP hash
- 按客户端 IP 做 hash 选节点
- 优点：简单
- 缺点：
- NAT/代理下大量用户共享同一出口 IP -&amp;gt; 严重倾斜
- 用户换网 IP 变化导致漂移
- Cookie 粘性
- LB 注入 cookie ，后续按照 cookie 路由
- 优点：比 IP 稳定
- 缺点：跨域，隐私策略，cookie 清理，灰度/回源复杂
- 一致性哈希（按 userId/会话键）
- 工程风险
- 降低容错：节点挂了，该节点的用户都受影响
- 产生热点：某些用户/租户流量大-&amp;gt;单节点热
- 扩缩容抖动：重新分配导致缓存冷启动、延迟尖刺
- 推荐的替代方案
- 应用无状态 &amp;#43; 共享 session (Redis)
- 把状态外置(DB/Cache)，实例可以随时替换
- 长连接常见：连接层 sticky 不可避免，要做好限流与迁移策略
- 健康检查
- L4 健康检查
- 探测 TCP 能否 connect 成功
- 优点：便宜、通用
- 缺点：应用假活时仍然会通过（线程池满、依赖挂）
- L7 健康检查
- 请求 /healthz 或 /readyz
- 可检查
- 进程存活
- 是否可接新流量
- 关键依赖是否可用
- 设计要点
- liveness vs readliness 分离
- liveness：进程是否需要重启
- readliness：是否接流量
- 避免健康检查过重
- 如果每秒大量探测并且检查依赖，会把依赖也打挂
- 阈值与抖动控制
- 连续 N 次失败才摘除
- 连续 M 次成功才恢复
- 慢启动与预热
- 新实例启动后先不接流量，预热缓存/JIT，再标 ready
- 网络排障
- L3/L4 基础联通与路径
- ping
- 测试的是什么
- ping 用的是 ICMP Echo Request/Echo Reply
- 发 Echo Request（带序号 seq、时间戳等）
- 对端回 Echo Reply
- 测量往返时延 RTT
- ![](https://an-img.oss-cn-hangzhou.aliyuncs.com/2026/02/18/20260218103650584.png,492,184)
- 查看延迟
- 看 avg 和 min/max
- 抖动
- RTT 的波动程度
- stddev（标准差） 大，说明网络队列/无线/拥塞波动明显
- 丢包
- packet loss &amp;gt; 0 说明有包没有收到回包
- tracerout/mtr 的原理
- traceroute：用 TTL 把路径探测出来
- IP 包里有 TTL，每过一跳路由器把 TTL - 1
- 当 TTL 变为 0，路由器丢包并回 ICMP Time Exceeded
- traceroute 依次发 TTL=1,2,3 的探测包
- 可以看到每一跳的地址与该跳的 RTT
- 每一跳显式的 RTT 是你-&amp;gt;该跳-&amp;gt;你的往返，不是单程，也不是该跳-&amp;gt;下一跳的延迟
- mtr：traceroute &amp;#43; ping 的结合
- mtr 会对每一跳持续发探测包并统计
- 丢包率
- 最快/平均/最慢 RTT
- 抖动
- mtr 更适合定位抖动/偶发丢包/拥塞点
- 用 traceroute 看路径与绕路
-
&lt;/textarea&gt;</description></item></channel></rss>