计算机网络二月 16, 2026网络分层 + 端到端路径# - 网络分层 + 端到端路径 - 分层模型 - 链路层 (LINK) - 负责:同一链路/局域网内把 IP 包送到下一跳 - 典型概念:以太网、WIFI、ARP、MAC、MTU - 网络层 (IP) - 负责:跨网络把包从源 IP 路由到目的 IP - 典型概念:IP 地址、路由表、NAT、ICMP、分片 - 传输层 (Transport) - 负责:端到端进程通信,提供端口与可靠性 - 应用层 (Application) - 负责:定义应用语义 - 端到端路径(一次 HTTPS URL 发生了什么) - URL 解析与策略决策(应用层前置) - 浏览器先拆 URL: - schema:https -> 需要 TLS - host:example.com -> 需要 DNS - port:默认 443 - path:/index.html - 是否可用缓存 - 是否复用已有连接 - 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 设备维护一张映射表(内网四元组<->公网四元组) - 映射有超时:长时间无数据可能被回收 - 并发连接太多可能端口耗尽 - 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/1.1 默认倾向长连接(Keep-Alive) - 浏览器有连接池,会复用到同一个 host:port 的连接 - 关键概念锚点 - RTT:网络交互的时间单位 - RTT = 一个来回的延迟 - 建连/TLS/重传都依赖 RTT - 高 RTT 下,交互次数越多越慢 - 吞吐与 BDP(带宽-时延积) - MTU 月 MSS - MTU:链路层最大承载 - MSS:TCP 单段可承载的应用数据最大值 - 包太大可能被分片或者丢弃 - 合理 MSS 能减少分片风险,提升稳定性 TCP 精讲# - TCP 精讲 - TCP 本质:在不可靠 IP 上提供可靠字节流 - TCP 提供内容 - 面向连接:双方都维护连接状态(序号、窗口、定时器等) - 可靠、有序:丢包重传、乱序重排、去重 - 字节流:没有消息边界(粘包/拆包的根源) - 流量控制:避免把对方接收缓冲打爆(rwnd),端到端 - 拥塞控制:避免把网络打爆,端到网络 - 报文段关键字段 - 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 更温和 - 快速重传(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 后,退出快恢复,回到拥塞避免阶段 - SACK(选择确认) - 痛点:累计 ACK 信息不够 - 当出现丢包 + 乱序到达时,累计 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+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 + 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 决定,不由应用决定 - 接收端读取粒度由应用决定 - 解法 - 在应用层定义消息边界 - 长度前缀(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 通常会指数退避,导致恢复更慢 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/):语义上等价即可 - HTTP/1.1:连接、报文、队头阻塞 - HTTP/1.1 报文与传输 - 文本协议:请求行 + 头 + 空行 + 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 + TCP + TLS 多次 RTT - 解决的问题 - 身份认证:验证连接的是域名的真实服务器 - 机密性:传输内容加密 - 完整性:防篡改 - TLS 握手 - TLS 1.2 - ClientHello:客户端支持的版本/套件,随机数,SNI 等 - ServerHello:选定版本/套件、随机数,让双方对本次会话参数单程一致 - Certificate:服务器发送证书链,客户端据此验证:链是否可信、域名是否匹配、是否过期,用途是否正确 - ServerKeyExchange:服务器提供 ECDHE 参数与公钥,客户端通过验证签名,确保密钥协商参数的安全性 - ServerHelloDone:告诉客户端,材料已经发完 - ClientKeyExchange:客户端协商参数,客户端发送自己的 ECDHE 公钥,双方据此计算共享 secret - ChangeCipherSpec + 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 -> session_data - 客户端用 Cookie 存 session_id - 每次请求:浏览器自动带 cookie -> 服务端查 session store -> 识别用户 - 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 + Secure - CSP 减少脚本注入 - 输入输出编码 + 模板转义 - CSRF 跨站请求伪造 - CSRF 利用浏览器自动携带 cookie - 攻击者诱导用户在登录态下访问恶意页面,发起跨站请求到受害站点 - 防护手段 - SameSite Cookie (优先) - CSRF Token(表单/请求头携带,服务端校验) - 检查 Origin/Rerfer - 同源策略与 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 规则 - 跨源写/发送很多情况下是允许的 - <form action="xxx.com" method="POST"> 可以提交,因此 CSRF 能成立,请求可以发出去,但是页面读取不到 bank.com 的响应内容 - 部分资源加载天然跨源允许 - <img> <scirpt> <link> 浏览器允许加载这些资源 - 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 分桶缓存 - 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 会话在客户端<->目标服务器之间建立 - 负载均衡: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 -> 严重倾斜 - 用户换网 IP 变化导致漂移 - Cookie 粘性 - LB 注入 cookie ,后续按照 cookie 路由 - 优点:比 IP 稳定 - 缺点:跨域,隐私策略,cookie 清理,灰度/回源复杂 - 一致性哈希(按 userId/会话键) - 工程风险 - 降低容错:节点挂了,该节点的用户都受影响 - 产生热点:某些用户/租户流量大->单节点热 - 扩缩容抖动:重新分配导致缓存冷启动、延迟尖刺 - 推荐的替代方案 - 应用无状态 + 共享 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 -  - 查看延迟 - 看 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 更适合定位抖动/偶发丢包/拥塞点 - 用 traceroute 看路径与绕路 -