网络分层 + 端到端路径
- 网络分层 + 端到端路径
- 分层模型
- 链路层 (LINK)
- 负责:同一链路/局域网内把 IP 包送到下一跳
- 典型概念:以太网、WIFI、ARP、MAC、MTU
- 网络层 (IP)
- 负责:跨网络把包从源 IP 路由到目的 IP
- 典型概念:IP 地址、路由表、NAT、ICMP、分片
- 传输层 (Transport)
- 负责:端到端进程通信,提供端口与可靠性
- 应用层 (Application)
- 负责:定义应用语义
- 链路层 (LINK)
- 端到端路径(一次 HTTPS URL 发生了什么)
- URL 解析与策略决策(应用层前置)
- 浏览器先拆 URL:
- schema:https -> 需要 TLS
- host:example.com -> 需要 DNS
- port:默认 443
- path:/index.html
- 是否可用缓存
- 是否复用已有连接
- 浏览器先拆 URL:
- DNS 解析:把域名变成 IP
- 目标:得到服务器的 IP (A=IPv4/AAAA=IPv6)
- 典型流程
- 浏览器/OS 先查本地缓存
- 没命中 -> 向递归解析器发起查询
- 递归解析器询问:根 -> TLD -> 权威 DNS
- 返回 IP + 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 设备维护一张映射表(内网四元组<->公网四元组)
- 映射有超时:长时间无数据可能被回收
- 并发连接太多可能端口耗尽
- 大多数客户端在 NAT
- TLS 握手:在 TCP 上建立安全通道
- 目标
- 验证服务器身份
- 协商密钥
- 后续数据用对称加密 + 完整性保护
- 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 内容
- 连接复用与关闭:不是每次都重新握手
- HTTP/1.1 默认倾向长连接(Keep-Alive)
- 浏览器有连接池,会复用到同一个 host:port 的连接
- URL 解析与策略决策(应用层前置)
- 关键概念锚点
- RTT:网络交互的时间单位
- RTT = 一个来回的延迟
- 建连/TLS/重传都依赖 RTT
- 高 RTT 下,交互次数越多越慢
- 吞吐与 BDP(带宽-时延积)
- MTU 月 MSS
- MTU:链路层最大承载
- MSS:TCP 单段可承载的应用数据最大值
- 包太大可能被分片或者丢弃
- 合理 MSS 能减少分片风险,提升稳定性
- RTT:网络交互的时间单位
- 分层模型
TCP 精讲
- TCP 精讲
- TCP 本质:在不可靠 IP 上提供可靠字节流
- TCP 提供内容
- 面向连接:双方都维护连接状态(序号、窗口、定时器等)
- 可靠、有序:丢包重传、乱序重排、去重
- 字节流:没有消息边界(粘包/拆包的根源)
- 流量控制:避免把对方接收缓冲打爆(rwnd),端到端
- 拥塞控制:避免把网络打爆,端到网络
- TCP 提供内容
- 报文段关键字段
- Sequence Number:本段数据在字节流里的起点序号
- Acknowledgment Number:我期望收到的下一个收到的序号
- Flags:SYN/ACK/FIN/RST/PSH/URG
- Window(rwnd):接收端通告窗口
- Options:MSS,Window Scale
- 连接建立:三次握手
- 标准流程
- C->S: SYN(seq=x)
- S->C: SYN+ACK (seq=y,ack=x+1)
- C->S: ACK (ack=y+1)
- 为什么必须三次
- 双方都要确认两件事
- 对方能收:对方收到了我的东西
- 对方能发:对方能把东西发回来让我收到
- 两次握手只能证明客户端 -> 服务端这条路通了;但是服务端无法证明服务端->客户端也通、且客户端确实收到了服务端的回应,第三次 ACK 就是让服务端拿到这个确认(服务端无法确认客户端能收)
- 如果只有两次握手
- 服务端回 SYN + ACK 丢失了
- 服务端仍为连接建立,开始分配连接资源,等待数据
- 客户端实际上没有收到任何回应,会重试或放弃
- 三次握手还能防止历史重复 SYN 造成的“幽灵连接“
- 历史重复 SYN 指的是:某次旧连接尝试时发出的 SYN,由于网络拥塞、路由绕行、链路重传、设备缓存/异常等原因,在很久之后才到达服务器
- 如果服务器把旧 SYN 当新建连:
- 服务器会分配资源
- 如果后续又出现旧 ACK 包,让服务器误以为连接已建立,交付错误的数据
- 幽灵连接:就是服务端自嗨式建立,客户端不认可
- 双方都要确认两件事
- 半连接队列与 SYN Flood
- 服务端收到 SYN 后进入 SYN_RECV,会占用半连接资源(backlog)
- SYN Flood:大量 SYN 不完成握手 —> 半连接队列被占满 -> 正常连接进不来
- 典型缓解:SYN cookies、增大 backlog、限速、丢弃策略、前置抗 DDoS
- 连接关闭:四次挥手、半关闭
- 主动关闭方 A->B:FIN(不再发了)
- B -> A:ACK (知道了)
- B -> A:FIN (我也不再发了)
- A -> B:ACK (知道了)
- 为什么常见是四次
- 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 重传 + 我 ACK 再次发送等往返的不确定性。
- TIME_WAIT 多了怎么办
- 常见场景:短连接高 QPS
- 治理思路
- 优先做连接复用:HTTP Keep-Alive/连接池
- 调整应用行为:减少主动 close、合并请求、复用长连接
- 系统参数:允许端口更快复用等
- 通常是我方主动关 + 短连接
- 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 + RTTVAR -> RTO
- 名词解释
- RTT sample:一次测得的 RTT(从发出某段到收到它对应 ACK 的时间)
- SRTT (Smoothed RTT):平滑后的 RTT 均值。
- RTTVAR:RTT 的波动。
- RTO = SRTT + 4 * RTTVAR
- SRTT 给出正常情况下 ACK 来回要多久
- RTTVAR 给出不确定性有多大
- 乘以 4 是给一个安全幅度:抖动越大,RTO 增长越明显,避免误重传
- 指数退避:超时一次,RTO 翻倍
- RTO 重传 vs 快速重传
- RTO 重传
- 触发:计时器到期,没等到 ACK
- 特点:更“保底“,但通常更慢;而且代表网络更糟糕
- 常伴随:指数退避,cwnd 通常会大幅下降
- Fast Retransmit (快速重传)
- 触发:重复 ACK
- 特点:更快,更轻判拥塞
- 一半比 RTO 更温和
- RTO 重传
- 名词解释
- TCP 发送一个数据段后,会期待收到 ACK,如果在某个时间窗口没有等到 ACK,TCP 可能认为丢了,于是触发超时重传。
- 快速重传(Fast Retransmit):收到多个重复 ACK (典型 3 个)推断某段丢失了,立即重传)
- RTO 重传局限性
- 必须等待一段时间没收到 ACK 才重传
- 在 RTT 较大或 RTO 比较保守时,恢复会很慢
- 快速重传的思路
- 不等超时,利用接收端反馈的 ACK 形态更早推断丢包
- 重复 ACK
- 如果接收端收到了更后面的段,但是缺了中间某段
- 不能把乱序段交付给应用
- 会继续发送同一个累计 ACK,告诉发送端还在等从 1000 开始的那段,后面来的我先缓存
- ACK 值没前进,但又反复出现的 ACK 就是重复 ACK
- 如果接收端收到了更后面的段,但是缺了中间某段
- 3 个重复 ACK 会触发快速重传
- 收到 3 个重复 ACK 代表某段大概率丢了
- 立即重传缺口对应的最早未确认段
- 快恢复
- 原因
- 收到快重传代表完了给没有完全堵死(还能持续收到 ACK)
- 不必像 RTO 那样极度保守把发送速率打到很低
- Reno 直觉
- 触发快速重传后,认为发生拥塞 -> 降低 cwnd
- 但利用重复 ACK 的到来,允许发送端在恢复期间适度继续发,避免完全停摆
- 当收到新的 ACK 后,退出快恢复,回到拥塞避免阶段
- 原因
- RTO 重传局限性
- 超时重传(RTO):等不到 ACK 就重发(RTO 动态估计,基于 RTT 抖动)
- SACK(选择确认)
- 痛点:累计 ACK 信息不够
- 当出现丢包 + 乱序到达时,累计 ACK 只能不断重复同一个 ACK=N,发送端只知道缺口在 N,不知道
- 后面哪些段已经到了
- 究竟丢失了几段,丢失了哪几段
- 重传时是否把已到达的也重传
- 目标是把接收端实际收到的乱序块显式告诉发送端
- 当出现丢包 + 乱序到达时,累计 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 是 TCP 的一个 Option,启用后
- 启用流程:SACK Permitted
- SACK 需要在三次握手时协商
- 双方在 SYN/SYN+ACK 中携带 SACK Permitted 选项
- 只要双方都声明支持,后续才会在 ACK 中携带 SACK blocks
- SACK 需要在三次握手时协商
- 痛点:累计 ACK 信息不够
- 乱序与重排
- 网络层可能乱序,TCP 负责在接收端按序交付给应用
- 接收端会缓存乱序段,等缺口补齐再交付
- 序号与累计 ACK
- 滑动窗口
- 痛点
- 如果每发一段就必须等 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 + 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 <= min(cwnd, rwnd)
- 要跑满带宽,需要窗口大小 >= BDP
- 零窗口与窗口探测
- 如果接收端缓冲满了,会通告 rwnd=0
- 发送端此时必须停止发送新数据,但是要避免死锁
- TCP 有 persist timer:周期性发送 Zero Window Probe 探测包
- 一旦接收窗口恢复,发送端继续发
- 痛点
- 粘包/拆包
- 粘包:在发送端 send() 了两条业务消息,接收端一次 recv() 读到了两条合在一起的字节。
- 拆包:在发送端 send() 了一条业务消息,接收端需要 recv() 多次才能读完
- TCP 不保证 send 一次就对应端 recv 一次
- 原因
- TCP 的抽象是流
- 像读文件一样读一串字节
- send():把字节追加到发送缓冲区
- recv():从接收缓冲区取走当前可读的字节
- 分段由 TCP 决定,不由应用决定
- 接收端读取粒度由应用决定
- TCP 的抽象是流
- 解法
- 在应用层定义消息边界
- 长度前缀(Length-Prefixed,最通用)
- 格式 | 4-byte length (big-endian) | payload bytes… |
- length 表示 payload 长度
- 接收端先读满 4 字节得到 length,再继续读满 length 字节
- 分隔符(Delimiter-Based,适合文本协议)
- 行协议:以 \r\n 结尾
- 固定长度(Fixed-Size)
- 每条消息固定 N 字节
- 拥塞控制
- 拥塞不是对端瘦不下,那是流量控制 rwnd,拥塞是网络路径上的队列/链路承载不住
- 路由器/交换机队列堆积 -> RTT
- 队列溢出 -> 丢包
- 丢包/排队抖动 -> 吞吐下降、延迟暴涨
- 拥塞控制控制的是发送端允许在网络中飞着的未确认数量,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 通常会指数退避,导致恢复更慢
- RTO:最严重,认为网络可能很糟
- 慢启动
- 拥塞不是对端瘦不下,那是流量控制 rwnd,拥塞是网络路径上的队列/链路承载不住
- TCP 本质:在不可靠 IP 上提供可靠字节流
HTTP 与 Web 体系
- HTTP 与 Web 体系
- HTTP 基础语义
- HTTP 是无状态应用层协议:每个请求都自包含信息,状态由 Cookie/Token/Session 等机制在应用层实现
- HTTP 定义的语义,具体传输方式由 HTTP/1.1、HTTP/2、HTTP/3 决定
- 方法:语义 + 幂等 + 安全性
- GET:获取资源,应当安全+幂等
- 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: “xxx”
- 下次请求:If-None-Match: “xxx”
- 若未变:返回 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/):语义上等价即可
- 两类缓存:强缓存vs协商缓存
- HTTP/1.1:连接、报文、队头阻塞
- HTTP/1.1 报文与传输
- 文本协议:请求行 + 头 + 空行 + body
- 连接默认与持久化:Keep-Alive (减少握手成本)
- Chunked Transfer-Encoding:服务端可流式传输,不提前知道 Content-Length
- HTTP/1.1 的性能问题:应用层 HOL
- 同一连接上请求/响应按顺序进行
- 一个慢请求会阻塞后续的请求(队头阻塞)
- HTTP/1.1 报文与传输
- 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 的交付
- 核心变化
- HTTP/2 为什么快
- TLS 与 HTTPS
- 位置与成本
- HTTPS = TCP 建连后再 TLS 握手,再 HTTP
- 首次访问慢常见原因:DNS + TCP + TLS 多次 RTT
- 解决的问题
- 身份认证:验证连接的是域名的真实服务器
- 机密性:传输内容加密
- 完整性:防篡改
- TLS 握手
- TLS 1.2
- ClientHello:客户端支持的版本/套件,随机数,SNI 等
- ServerHello:选定版本/套件、随机数,让双方对本次会话参数单程一致
- Certificate:服务器发送证书链,客户端据此验证:链是否可信、域名是否匹配、是否过期,用途是否正确
- ServerKeyExchange:服务器提供 ECDHE 参数与公钥,客户端通过验证签名,确保密钥协商参数的安全性
- ServerHelloDone:告诉客户端,材料已经发完
- ClientKeyExchange:客户端协商参数,客户端发送自己的 ECDHE 公钥,双方据此计算共享 secret
- ChangeCipherSpec + Finished:双方切换到加密通信并校验握手完整性
- TLS 1.2
- 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
- Domain/Path:决定发送范围
- Web 会话解决的问题
- Session:服务端保存状态,客户端只存门票
- Session 的典型模式
- 服务端维护 session_id -> session_data
- 客户端用 Cookie 存 session_id
- 每次请求:浏览器自动带 cookie -> 服务端查 session store -> 识别用户
- Session 的典型模式
- Token(尤其是 JWT):把身份证明带在请求里
- Token 的核心模式
- 服务端签发 token 给客户端
- 客户端后续请求带 token
- 服务端验证 token 来识别用户
- JWT 结构
- JWT=header.payload.signature
- header:算法等元信息
- payload:claims 声明
- signature:服务端用密钥签名
- Token 的核心模式
- Refresh Token 与 Access Token
- Access Token:短有效期,每次用于请求
- Refresh Token:长有效期,用于换取新的 Access Token
- 位置与成本
- 安全边界:XSS、CSRF、会话固定、重放
- XSS(跨站脚本)
- 若 token/cookie 可以被 JS 读取,xss 可以直接窃取并盗用
- cookie 用 HttpOnly + Secure
- CSP 减少脚本注入
- 输入输出编码 + 模板转义
- CSRF 跨站请求伪造
- CSRF 利用浏览器自动携带 cookie
- 攻击者诱导用户在登录态下访问恶意页面,发起跨站请求到受害站点
- 防护手段
- SameSite Cookie (优先)
- CSRF Token(表单/请求头携带,服务端校验)
- 检查 Origin/Rerfer
- XSS(跨站脚本)
- 同源策略与 CORS
- 痛点
- 浏览器安全模型默认假设:不同站点之间互不信任
- 浏览器用同源策略(Same-Origin Policy,SOP)限制一个网页脚本能读取另一个来源的资源内容
- 业务需要跨域调用 API,有了 CORS(CrossOrigin Resource Sharing):在 SOP 的基础上提供一个受控的放行机制。
- Orgin 源是什么:同源的三元组
- Origin = scheme + host + port
- https://example.com:443
- 同源策略具体限制
- 跨源读被禁止
- JS 用 fetch 请求 https://xxx.com,请求可能能发出去,但是浏览器会阻止 JS 读取响应,除非响应满足 CORS 规则
- 跨源写/发送很多情况下是允许的
- 部分资源加载天然跨源允许
浏览器允许加载这些资源
- 跨源读被禁止
- 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: ‘include’
- Access-Control-Expose-Headers
- 默认情况下,JS 能够读取的响应头很有限,如果希望暴露,必须显式添加
- Vary: Origin
- 按 Origin 分桶缓存
- Access-Control-Allow-Origin
- 简单请求
- 痛点
- DNS 解析
- 基础对象
- 记录类型
- A(IPv4)
- AAAA(IPv6)
- CNAME(别名)
- NS(权威)
- 解析角色
- Stub resolver(客户端/OS)
- Recursive resolver(递归解析器:运营商/公司 DNS/公共 DNS)
- Authoritative server (权威 DNS)
- 记录类型
- 基础对象
- 正向代理、反向代理、透明代理与隧道
- 代理:本质上是位于客户端与服务端之间的一种中间节点,代表一侧发起连接、转发数据、并可能施加策略
- 承担职责
- 路由/转发:把请求送到正确上游
- 安全控制:鉴权、WAF、访问控制
- 性能优化:连接复用、压缩、缓存、限流
- 可观测性:日志、指标、追踪、采样
- 协议转换:HTTP <-> grpc 等
- 正向代理
- 正向代理站在客户端一侧,客户端明确知道自己在用代理,由代理代表客户端访问外部资源
- 反向代理
- 反向代理站在服务端一侧,对外它像真正的服务器入口,对内把请求转发到后端服务
- 反向代理常用原因
- 隐藏后端拓扑:外部只有一个入口
- 统一 TLS 终止:证书集中管理
- 统一流量治理:限流、熔断、灰度、路由、鉴权
- 连接复用:对外维持大量连接、对内复用少量连接
- 观测与审计:同一日志/指标/链路追踪
- 透明代理
- 隧道:Connect 到底干了什么
- 隧道:代理不解析上层协议,只做字节透传。
- HTTP Connect 隧道流程
- 客户端先对代理发 CONNECT
- 客户端 -> 代理
- CONNECT bank.com:443 HTTP/1.1
- Host: back.com:443
- 客户端 -> 代理
- 代理建立到目标的 TCP 连接
- 代理 -> bank.com:443:发起 TCP 连接
- 代理回客户端
- HTTP/1.1 200 Connect Established
- 隧道建立,字节透传
- 客户端与 bank.com 的 TLS 握手数据会被原封不动地通过代理转发,代理看不懂也不需要看懂
- TLS 会话在客户端<->目标服务器之间建立
- 客户端先对代理发 CONNECT
- 负载均衡: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 回源入口
- L4 LB(传输层):基于 IP/端口/连接做转发
- 负载均衡算法
- 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 重映射会引发抖动。
- Round Robin(轮询)
- 会话保持
- 为什么需要 sticky
- 后端有状态
- 本地缓存必须命中
- WebSocket/长连接绑定到某实例
- 常见 sticky 手段
- Source IP hash
- 按客户端 IP 做 hash 选节点
- 优点:简单
- 缺点:
- NAT/代理下大量用户共享同一出口 IP -> 严重倾斜
- 用户换网 IP 变化导致漂移
- Cookie 粘性
- LB 注入 cookie ,后续按照 cookie 路由
- 优点:比 IP 稳定
- 缺点:跨域,隐私策略,cookie 清理,灰度/回源复杂
- 一致性哈希(按 userId/会话键)
- Source IP hash
- 工程风险
- 降低容错:节点挂了,该节点的用户都受影响
- 产生热点:某些用户/租户流量大->单节点热
- 扩缩容抖动:重新分配导致缓存冷启动、延迟尖刺
- 推荐的替代方案
- 应用无状态 + 共享 session (Redis)
- 把状态外置(DB/Cache),实例可以随时替换
- 长连接常见:连接层 sticky 不可避免,要做好限流与迁移策略
- 为什么需要 sticky
- 健康检查
- L4 健康检查
- 探测 TCP 能否 connect 成功
- 优点:便宜、通用
- 缺点:应用假活时仍然会通过(线程池满、依赖挂)
- L7 健康检查
- 请求 /healthz 或 /readyz
- 可检查
- 进程存活
- 是否可接新流量
- 关键依赖是否可用
- 设计要点
- liveness vs readliness 分离
- liveness:进程是否需要重启
- readliness:是否接流量
- 避免健康检查过重
- 如果每秒大量探测并且检查依赖,会把依赖也打挂
- 阈值与抖动控制
- 连续 N 次失败才摘除
- 连续 M 次成功才恢复
- 慢启动与预热
- 新实例启动后先不接流量,预热缓存/JIT,再标 ready
- liveness vs readliness 分离
- L4 健康检查
- 目标
- 网络排障
- L3/L4 基础联通与路径
- ping
- 测试的是什么
- ping 用的是 ICMP Echo Request/Echo Reply
- 发 Echo Request(带序号 seq、时间戳等)
- 对端回 Echo Reply
- 测量往返时延 RTT
- 查看延迟
- 看 avg 和 min/max
- 抖动
- RTT 的波动程度
- stddev(标准差) 大,说明网络队列/无线/拥塞波动明显
- 丢包
- packet loss > 0 说明有包没有收到回包
- 测试的是什么
- tracerout/mtr 的原理
- traceroute:用 TTL 把路径探测出来
- IP 包里有 TTL,每过一跳路由器把 TTL - 1
- 当 TTL 变为 0,路由器丢包并回 ICMP Time Exceeded
- traceroute 依次发 TTL=1,2,3 的探测包
- 可以看到每一跳的地址与该跳的 RTT
- 每一跳显式的 RTT 是你->该跳->你的往返,不是单程,也不是该跳->下一跳的延迟
- mtr:traceroute + ping 的结合
- mtr 会对每一跳持续发探测包并统计
- 丢包率
- 最快/平均/最慢 RTT
- 抖动
- mtr 更适合定位抖动/偶发丢包/拥塞点
- mtr 会对每一跳持续发探测包并统计
用 traceroute 看路径与绕路
- traceroute:用 TTL 把路径探测出来
- ping
- L3/L4 基础联通与路径
- HTTP 基础语义