HTTP2 队头阻塞
HTTP/2 的多路复用确实解决了 HTTP/1.x 时代应用层的队头阻塞,但它并未根除传输层(TCP)的队头阻塞问题。
🧐 “队头阻塞”是什么?
简单说,这就像只有一个服务窗口的队伍:当前面的人因为业务复杂耗时很久,后面的人就算只想办个简单业务,也必须排队等着,效率极低。在计算机网络中,它被定义为因队列中首个数据包受阻,而导致后续数据包无法被及时处理的现象。
🚀 应用层问题已解决:HTTP/2 的多路复用
在 HTTP/1.1 时代,队头阻塞是性能瓶颈。一个 TCP 连接同一时间只能处理一个请求,上一个响应没完成,下一个请求就不能发。为了解决这个问题,HTTP/2 引入了革命性的**多路复用(Multiplexing)**技术:
- 将请求切分为帧(Frame):把每个 HTTP 消息切分成更小的二进制帧。
- 通过流(Stream)并行传输:在同一 TCP 连接上,将不同请求/响应的帧交织在一起传输,每个流的帧能通过
Stream ID被识别和重组,互不干扰。 - 实现全双工通信(Full-duplex):由此实现了真正的全双工,多个请求和响应可同时在一个 TCP 连接上交错传输,成功消除了应用层队头阻塞。
⛓️ 深层矛盾犹存:TCP 层的队头阻塞
虽然应用层问题解决了,但 HTTP/2 所有数据还是跑在单一 TCP 连接上,TCP 协议要求数据严格按序到达的特性,导致了新的问题。一旦底层网络发生数据包丢失,后续已到达的包都无法交给上层应用,直到丢失的包被重传,造成所有流瞬间阻塞。在高丢包率的弱网环境下,这种底层阻塞将变得极为常见。
💡 完美之道:向 HTTP/3 演进
针对 HTTP/2 在 TCP 层的瓶颈,使用 QUIC 协议的 HTTP/3 带来了根本性解决方案。QUIC 基于 UDP 独立管理每个流,一个流的丢包完全不影响其他流,从根本上避免了队头阻塞,显著优化了弱网环境下的用户体验。
总结来说,HTTP/2 的多路复用是巨大的飞跃,但它只是把队头阻塞从应用层移到了传输层。因此,它并非终点,而是一个关键的演进阶段。
TLS1.3 加密
上回我们聊到,HTTP/3 基于 QUIC 协议,彻底解决了 TCP 层的队头阻塞。而 QUIC 能实现安全高效的传输,秘诀就在于它深度集成了 TLS 1.3。可以说,TLS 1.3 正是 HTTP/3 安全的基石。
接下来,我就为你详细拆解 TLS 1.3 的加密机制,看看它如何做到既快又安全。
一、TLS 1.3 的核心设计目标
相较于存在了十多年的 TLS 1.2,1.3 版本的改进是颠覆性的,主要针对两大痛点:
- 减少握手延迟:将过去常见的 2-RTT 握手大幅优化为 1-RTT,甚至支持 0-RTT 恢复。
- 强化安全性:剔除所有已知不安全的加密算法和机制,默认启用完全前向保密。
二、握手即加密:1-RTT 握手详解
这是 TLS 1.3 最精髓的部分。我们来看一次完整的首次连接握手过程,注意加密是如何层层推进的。
1. 客户端发起:ClientHello
客户端发送支持的密码套件、密钥交换参数(如 ECDHE 的公钥共享)等。由于是明文,这里只包含最基础的信息。
注:TLS 1.3 的密码套件已极度简化,不再包含密钥交换算法和签名算法,只指定“对称加密算法(AEAD)”和“哈希算法”。例如 TLS_AES_128_GCM_SHA256。
2. 服务器回应:ServerHello + 立即加密 这一步服务器同时完成三件事,效率极高:
- ServerHello:选定密码套件,并发送自己的 ECDHE 公钥共享。
- 密钥协商完成:此时,双方已拥有足够信息,在内存中计算出握手通信密钥(Handshake Traffic Keys)。从这一刻起,后续所有的握手消息全部被这个密钥加密。
- 发送加密消息:在同一个包体中,紧接着发送被加密的
[Certificate](服务器证书)、[CertificateVerify](用服务器私钥对握手信息的签名,证明证书持有权)和[Finished](握手完整性校验)。
3. 客户端验证并完成:Finished
客户端用收到的服务器公钥共享计算出同样的握手通信密钥,解密并验证证书链和签名。验证通过后,双方基于之前的协商数据派生出最终的应用数据通信密钥(Application Traffic Keys)。客户端发送加密的 [Finished] 消息,此后所有应用数据都用最终的应用密钥加密。
可以看到,TLS 1.3 将密钥交换所需要的往返从 2 次降为 1 次,并且大部分握手信息都被加密了,大幅提升了隐私性和速度。
三、三大加密机制保障安全
强制“完全前向保密” 这是 TLS 1.3 最重要的安全特性。它完全废弃了静态 RSA 和 DH 密钥交换,强制使用带临时密钥的 ECDHE(椭圆曲线迪菲-赫尔曼短时) 或 DHE。这意味着,即使服务器私钥未来被破解,历史上记录的所有会话也无法被解密,因为每一次会话的加密密钥都是一次性的临时密钥。
纯粹高效的 AEAD 加密 TLS 1.3 只允许使用带关联数据的认证加密(AEAD),彻底告别了“加密后MAC”的古板模式。AEAD 算法(如 AES-GCM、ChaCha20-Poly1305)能同步完成加密和完整性校验,在保证数据机密性的同时,有效防止数据被篡改。其中 ChaCha20-Poly1305 专为移动设备优化,在没有 AES 硬件加速的设备上表现更好。
安全的密钥派生体系 TLS 1.3 采用 HKDF(基于HMAC的密钥派生函数)这种标准化的密码学工具,将一次密钥协商的结果,安全地派生出不同阶段(握手、应用、恢复等)和不同方向(客户端写、服务器写)的独立密钥。这样即使一个阶段的密钥意外泄露,也不会威胁到其他阶段。
四、锦上添花:0-RTT 恢复
对于近期连接过的网站,TLS 1.3 支持“零往返”恢复。客户端可在握手首个包中,用之前保存的预共享密钥(PSK)直接加密应用数据发出,延迟接近于零。 但需要警惕的是,0-RTT 数据不具备前向保密性,且存在重放攻击风险。因此它通常只适用于无副作用(幂等)的请求,如静态资源的 GET 请求。
总结:TLS 1.2 与 1.3 关键区别
| 特性 | TLS 1.2 (典型) | TLS 1.3 |
|---|---|---|
| 首次握手往返 | 2-RTT | 1-RTT |
| 密钥协商算法 | 支持RSA, DH, ECDHE | 仅支持 (EC)DHE,强制前向保密 |
| 握手过程加密 | 证书等大部分消息明文传输 | ServerHello 之后全部加密 |
| 对称加密算法 | 支持 CBC 流密码等 | 仅支持 AEAD 加密 |
| 密码套件复杂度 | 组合混乱 (四段式) | 精简 (仅AEAD+Hash) |
| 0-RTT 模式 | 不支持 | 支持 |
总体来看,TLS 1.3 通过对握手的精妙简化和对过时技术的无情抛弃,构建了一个既快又安全的现代加密通道。它不仅是 QUIC/HTTP3 的先天优势,现今也已广泛应用于各类主流服务中,默默守护着我们每一次的网络通信。
ECDHE & DHE
简单来说,DHE 和 ECDHE 都能提供“前向保密”,但 ECDHE 是 DHE 的高效升级版,用更短的密钥实现了同样的安全性和更快的速度。
两者的核心区别在于底层的数学基础,这直接导致了性能和安全性上的巨大差异。
数学基础不同
DHE (临时迪菲-赫尔曼) 基于整数模幂运算,使用的是大质数。为了实现当前安全水平,密钥需要很长,导致计算量大、速度慢,尤其消耗 CPU。
ECDHE (临时椭圆曲线迪菲-赫尔曼) 基于椭圆曲线密码学,同样依赖离散对数难题,但解题难度远超于传统的大质数分解。因此,它能用非常短小的密钥,达到极高的安全强度。
关键差异对照
| 对比维度 | DHE | ECDHE |
|---|---|---|
| 数学基础 | 模幂运算 (Multiplicative Group) | 椭圆曲线点乘 (Elliptic Curve) |
| 安全强度 | 要达128位安全,需3072位密钥 | 仅需256位密钥即可达128位安全 |
| 计算性能 | 计算量大,速度慢,更耗CPU | 计算量小,速度快,更节能 |
| 资源占用 | 密钥大,占用更多内存和带宽 | 密钥极小,高效轻量 |
| 前向保密 | ✅ 支持 (由“临时”保证) | ✅ 支持 (由“临时”保证) |
在 TLS 1.3 中的应用
我之前提到,TLS 1.3 强制要求使用临时密钥来保证前向保密,因此它同时支持 DHE 和 ECDHE。但在实际使用中,ECDHE 是绝对的主流,几乎一统天下。
因为 ECDHE 在提供同等安全性的前提下,性能远超 DHE,非常适合现代设备,特别是移动端和物联网这类对 CPU、电量和带宽敏感的场景。例如,X25519 就是目前最流行的 ECDHE 曲线,它不仅能做到极致性能,在安全性上也更易于安全实现。
除非是一些非常受限的旧环境或不支持 ECC 的场景,否则你遇到的现代 HTTPS 连接,用的基本都是 ECDHE。
如果想进一步了解 X25519 这类具体曲线是如何工作的,也可以告诉我。
交换律
你的直觉很敏锐。是的,ECDHE 的核心正是基于椭圆曲线数学结构中的“交换律”,但在密码学术语中,这个“交换律”的表述需要更精确一些。
一、核心公式的“交换律”本质
ECDHE 和 DHE 的密钥协商,都依赖于这样一个等式:
客户端算出的共享密钥 = 服务器端算出的共享密钥
我们对比来看,交换律是如何体现的:
DHE 的交换律
- 公式:
(g^a)^b = (g^b)^a(模幂运算的交换律) - 客户端:有私钥
a,收到公钥g^b,算出(g^b)^a - 服务端:有私钥
b,收到公钥g^a,算出(g^a)^b
- 公式:
ECDHE 的交换律
- 公式:
a(bG) = b(aG)(椭圆曲线标量乘法的交换律) - 客户端:有私钥
a,收到公钥bG,算出a(bG) - 服务端:有私钥
b,收到公钥aG,算出b(aG)
- 公式:
在 ECDHE 中,G 是曲线上的一个公开基点,a 和 b 是各自的私钥(大整数)。a(bG) 和 b(aG) 的结果都是曲线上的同一个点,这个点的坐标,就是共享密钥的原材料。
二、为何通常不叫“乘法”而叫“标量乘法”
你可能会好奇,为什么有“椭圆曲线不是乘法”这种说法。这其实是为了区分底层的数学操作:
- DHE 的
g^a:这是乘法群里的指数运算(把g自乘a次)。 - ECDHE 的
aG:这是椭圆曲线加法群里的标量乘法(把点G与自身相加a次)。
椭圆曲线密码学中,基础操作是“点加”,私钥 a 是一个标量。所以,aG 虽然最终体现为“交换律”,但底层是靠反复“点加”实现的,为了和模幂运算区分,才强调这个操作叫“标量乘法”。
三、这个特性带来的安全价值
这个交换律,是密钥协商的精髓。你算出的 a(bG) 和我算出的 b(aG) 完全相同,我们便有了一个共享秘密。但对只看到 G、aG、bG 的窃听者来说,想通过 aG 或 bG 反推出 a 或 b 是极其困难的,这就是椭圆曲线离散对数难题(ECDLP)。
所以,ECDHE 能够在不安全的信道上,让双方安全地得到一个相同的密钥,正是因为它巧妙地利用了这个数学结构里天然的交换律。
总结一下:ECDHE 完全建立在椭圆曲线的交换律之上,只是这个“乘法”的专业叫法是“标量乘法”,以便和 DHE 的模幂运算相区别。
TLS1.2
为什么 TLS 1.2 需要 2-RTT 才能完成握手,而 TLS 1.3 只用 1-RTT?这背后是握手流程设计上的根本差异:TLS 1.2 的“一问一答”模式必须严格串行,无法提前发送关键材料。
TLS 1.2 的 2-RTT 流程:必须等待的“一问一答”
一个标准的 TLS 1.2 握手(使用 ECDHE)是这样串行的:
🔹 RTT 1:客户端说“你好”,服务器亮明身份并开始密钥协商
- 客户端 → 服务器:ClientHello 发送支持的密码套件、随机数等。
- 服务器 → 客户端:ServerHello, Certificate, ServerKeyExchange, ServerHelloDone
服务器选定套件,发送证书(身份),发送 ServerKeyExchange(包含自己的 ECDHE 临时公钥),最后发送
ServerHelloDone表示“我这波说完了”。
此时,客户端已收到服务器的 ECDHE 公钥,但还无法发送自己的公钥。 因为服务器的消息是一个完整的数据块,客户端必须全部接收并验证(至少验证证书)后,才能继续。
🔹 RTT 2:客户端完成密钥协商,双方确认
- 客户端 → 服务器:ClientKeyExchange, ChangeCipherSpec, Finished
客户端生成自己的 ECDHE 密钥对,将公钥放在 ClientKeyExchange 中发出。此时客户端已算出共享密钥,于是发送
ChangeCipherSpec(通知将加密)和加密的Finished。 - 服务器 → 客户端:ChangeCipherSpec, Finished
服务器用收到的客户端公钥算出共享密钥,解密验证
Finished,然后也发送ChangeCipherSpec和加密的Finished。
至此,握手完成,才能发送应用数据。关键点: 客户端的密钥交换消息(ClientKeyExchange)必须等到服务器的整轮消息结束(ServerHelloDone)之后才能发出,无法提前。这一来一回就是固定的 2-RTT。
为何不能并行?核心瓶颈在于“ServerKeyExchange”和“证书验证”
TLS 1.2 无法在第一个 RTT 内完成密钥协商的根本原因是:
- 服务器必须先把 证书 和 ECDHE 公钥(在 ServerKeyExchange 里)发给客户端。
- 客户端收到后,需要验证证书链、签名,然后才能生成自己的 ECDHE 公钥并返回。
- 这些步骤存在严格的数据依赖:客户端不能凭空生成一个能与服务器匹配的 ECDHE 公钥而不需要服务器的公钥,因为密钥协商需要双方公钥共同参与计算。而且证书验证也是必须的。
因此,TLS 1.2 的通信模式是“半双工”式的:一轮对话说完,另一轮才能开始。即使 0-RTT 恢复在某些实现中存在,但完整的首次握手确实是 2-RTT。
TLS 1.3 的改进:化 2-RTT 为 1-RTT 的魔法
TLS 1.3 能够压缩到 1-RTT,是因为它改变了规则:
客户端预先发送密钥共享 在 ClientHello 中,客户端直接附带了自己猜测的 ECDHE 公钥(针对其支持的曲线组)。如果猜对了服务器会选的曲线,服务器就能立刻使用。
服务器收到后立即计算密钥,后续消息全加密 服务器在 ServerHello 中选择曲线,并发送自己的 ECDHE 公钥。此时双方已拥有对方公钥,可以立即计算出会话密钥。从服务器发回的 ServerHello 之后的所有消息(包括证书、签名等)全部都用这个刚算出的密钥加密。不再需要
ServerKeyExchange明文、ServerHelloDone这类消息。一个 RTT 内完成相互验证 客户端收到服务器的加密消息后,解密并验证证书,然后发送自己的
Finished(也是加密的)。此时服务器回复加密的应用数据或Finished即可。第一个 RTT 往返就完成了所有身份验证和密钥确认,应用数据可以跟随着客户端 Finished 一起发送(在 HTTP 请求中常见)。
用一张表对比会更加直观:
| 握手步骤 | TLS 1.2 (ECDHE) | TLS 1.3 |
|---|---|---|
| 客户端第一轮发送 | ClientHello | ClientHello + 客户端 ECDHE 公钥 |
| 服务器第一轮发送 | ServerHello, Certificate, ServerKeyExchange (服务端 ECDHE 公钥), ServerHelloDone | ServerHello + 服务端 ECDHE 公钥,随后所有消息加密 (Certificate, Finished 等) |
| 此时状态 | 客户端尚未发送自己的公钥,密钥未协商完 | 双方均已算出会话密钥 |
| 客户端第二轮发送 | ClientKeyExchange (客户端 ECDHE 公钥), Finished | Finished (加密) |
| 所需 RTT | 2-RTT | 1-RTT |
简单总结:TLS 1.2 需要 2-RTT,是因为它必须先用第一个往返交换服务器的证书和密钥材料,再用第二个往返让客户端进行密钥交换和确认。这种依赖性无法在 1-RTT 内完成。而 TLS 1.3 利用“客户端猜测密钥共享参数,服务器即时计算并加密后续流量”的设计,完美将依赖关系融合在一个往返中,从而实现了质的飞跃。
Anycast
不,并非任何IP地址都可以用作Anycast IP。
Anycast并非一种特殊的IP地址类型,而是一种将流量路由到“最近”服务器的网络技术。这项技术能否实现,取决于你是在全球公网、私有网络还是IPv6环境下部署。不同场景下的规则和要求各不相同。
1. 公网Anycast:需要IP“所有权”
在互联网上实现Anycast,你不能随意指定一个IP地址,因为:
- 路由广播是核心:其工作机制依赖于通过BGP协议向全球广播一条路由信息,以“吸引”用户流量。这条信息不是单个IP,而是一个IP地址段(IP Prefix,即一组连续的IP地址,例如 /24)。
- 前提是拥有独立IP段:你必须拥有至少一个独立的公网IP地址段(通常是从区域互联网注册机构申请),并且拥有独立的AS号,才有权广播该地址段的路由。
- 关键限制:/24前缀与独立ASN:为防止全球路由表膨胀,互联网服务提供商通常会过滤掉任何长度大于 /24 的路由广播。如果你只有一个零散的IP,你的路由广播就会被丢弃,Anycast便无法工作。同时,你也需要独立的ASN来建立BGP对等互联并进行路由广播。
2. 私有Anycast:受限但灵活
如果你是在自己的私有网络中(例如公司内网、数据中心间),Anycast的门槛会低很多。只要在你可控的网络范围内,除了特定保留地址(如127.0.0.1/8、专用于组播的D类地址224.0.0.0/4等)和已分配给其他设备的地址外,理论上你可以使用任何标准的单播IP地址来配置Anycast。其原理与公网类似,在多个节点配置相同的IP,然后通过内部路由协议(如OSPF)广播该IP的主机路由(/32),路由器就能将请求转发到最近的节点。
3. IPv6 Anycast:原生支持下的新规
与IPv4把Anycast作为“后来补充”不同,IPv6从设计之初就原生支持Anycast。
- 语法无区别:在IPv6中,Anycast地址在格式上与普通单播地址完全相同,无法从地址本身进行区分。
- 配置需明确:正因如此,将一个IPv6地址用作Anycast时,必须在网络节点上进行明确的配置和声明。
- 预留专用地址:IPv6协议标准(RFC 4291)定义了一些功能专用的特定Anycast地址。最典型的是子网路由器任播地址(Subnet-Router Anycast Address),它由子网前缀加上全零的接口ID构成,数据包发往该地址,就会被送达指定子网上的任意一台路由器。
对比总结
下表概括了在各类网络中实现Anycast的技术限制:
| 环境 | 关键限制 |
|---|---|
| 公网 (IPv4) | 必须拥有/24或更短的独立IP地址段及AS号 |
| 公网 (IPv6) | 同样需要独立IP段用于BGP广播 |
| 私有网络 | 受限于网络管理员定义的私有地址范围 |
| IPv6 协议 | 需明确将地址声明为Anycast,且某些特定地址已被预留 |
💡 常见Anycast应用场景
了解这些限制后,会发现Anycast的用途非常广泛,尤其在需要高可用和低延迟的场景中:
- 公共DNS服务:最典型的应用。如Google DNS (
8.8.8.8)和Cloudflare DNS (1.1.1.1)在全球部署数千台服务器,让用户就近访问。 - 内容分发网络(CDN):将用户请求路由到最近边缘节点,加速静态资源响应。
- 分布式拒绝服务(DDoS)攻击防护:将攻击流量分散到全球,防止集中攻击瘫痪单一节点。
💎 总结
- Anycast并非特殊的IP,而是一种**“一地广播,全网就近响应”**的路由技术。
- 在公网上,你需要**“买路”(拥有独立的IP段和AS号)才能实现;在私网里,规则“家定”,更灵活;在IPv6中,它已是“原生居民”**,但遵守新规。
- 如果想了解如何在特定环境(如云平台或私有网络)中配置Anycast,可以补充具体信息,我可以为你做更详细的介绍。
Anycast & Multicast
任播和多播虽然都是一对多的通信,但核心目标完全不同。任播是一对“最近的一台”,而多播是一对“特定的一群”。
可以这样理解:
- 任播:全国连锁快餐店,你去“最近的那家”。
- 多播:有线电视,你“订阅了频道”才能看,信号发给所有订阅者。
它们的关键区别如下:
| 对比维度 | 任播 (Anycast) | 多播 (Multicast) |
|---|---|---|
| 核心模式 | 一对“最近的一个” | 一对“特定的一组” |
| IP地址 | 与单播相同,无法从地址本身区分 | 专用的D类地址 (IPv4: 224.0.0.0/4) |
| 接收方 | 被动,无需显式加入,由路由决定 | 主动,接收方必须显式加入多播组 |
| 拓扑感知 | 强,严重依赖路由拓扑找到“最近”节点 | 弱,不关心位置,只关心组关系 |
| 主要用途 | 服务高可用、负载均衡、就近接入 | 一对多实时分发,如直播、视频会议、IPTV |
| 典型协议 | DNS, CDN (基于UDP/TCP等) | 通常基于UDP (如PIM, IGMP路由协议) |
深入理解两者的工作机制
任播 (Anycast)
任播的关键在于路由协议(如BGP)。多个服务器使用相同的单播IP地址向网络宣告自己。当用户发起请求时,路由协议会根据最短路径、跳数等度量,自动将数据包转发到它认为“最近”的、广播了这个地址的服务器。因此,如果最近的节点宕机,路由会自动收敛,将流量切换到下一个最近的节点,实现天然的高可用和负载分散。
多播 (Multicast)
多播的关键在于“组”的管理。它需要一系列协议支持,如接收方使用IGMP(互联网组管理协议)协议告诉本地路由器:“我想加入这个多播组”,而路由器之间则使用PIM(协议无关多播)等协议构建一个从源到所有接收方的分发树。数据包在源只发一次,由网络中的路由器在分叉点复制,精准分发给所有组员,绝不会发给未加入的无关主机。
💡 为什么任播通常应用于无状态服务?
任播一个关键的使用限制是“路由抖动”。因为路由变化可能导致同一个用户连接的后续数据包,被发往另一个任播节点。
这对于无状态服务(如DNS查询,一次一个UDP包,查完即走)完全无碍。但对于TCP等有状态长连接,如果中途切换节点,新节点没有连接上下文,连接就会中断。不过,如果数据中心间有状态同步,或使用QUIC(连接ID不依赖IP)等现代协议,也能在一定程度上缓解此问题。
简单来说,想就近服务、分散负载就用任播;想定向分发、一对多实时广播就用多播。
根域名服务器
根域名服务器只有13组,这通常被误解为全球只有13台物理服务器。实际上,这个“13”指的是13个逻辑上的身份标识(用字母A到M命名)。它的背后是早期互联网的一个技术限制,而强大的任播(Anycast)技术则让这个“13”的意义远超其数字本身。
📜 历史溯源:从4台到13组的演进
根服务器的数量并非一步到位,而是随着互联网发展逐步增加的:
- 1985年:最初只有4台根服务器。
- 1987-1991年:增加到7台。
- 1993年:增加到8台,开始面临数据包超限问题。
- 1995年:改为统一命名格式
a~m.root-servers.net以节省空间。 - 1997年:最终增加到13组,沿用至今。
⛓️ 核心瓶颈:512字节的“铁律”
这个数字的根源,在于早期DNS协议必须遵守的 512字节数据包大小限制。所有13个根服务器的地址信息必须能塞进一个512字节的包中,原因是:
- 协议基础:DNS主要依赖UDP协议,具有“即发即忘”的高效特性。
- 避免IP分片:早期网络设备处理分片能力弱,为保可靠,DNS规定UDP包上限为512字节。
- 精确计算的结果:一个标准的DNS响应包固定开销(Header等)约416字节,剩下给每个根服务器的地址信息只有96字节。因此最多只能容纳13个(实际经压缩极限可达14-15个,13个是为安全特意留的余量)。
🚀 技术飞跃:任播让“13”不再受限
如果“13”代表物理数量上限,互联网早就崩溃了。拯救它的是任播(Anycast)技术,你可以把它理解为互联网世界的“分身术”:
- 逻辑与物理分离:A到M这13个字母是逻辑根服务器的身份ID。全球有超过1500个物理根服务器实例,都可以共享同一个逻辑IP。
- 智能路由:当查询发出时,BGP路由协议会将其自动导向离你网络距离最近的那个物理节点。
- 巨大优势:这套机制带来了高可用与负载均衡(一个节点故障自动切换)、低延迟(就近接入),以及让物理数量不受限制的高扩展性。
💎 总结
“13组根服务器”并非指物理机器数,而是早期DNS协议为了确保兼容性与稳定性,所做出的巧妙设计。这个在今天看似“小”的数字,恰好是互联网基石稳健性的体现。如今,我们实际拥有的是一张遍布全球、由超过1500个物理节点支撑的“十三罗汉”网络。正是任播技术的赋能,让这套半个世纪前设计的系统,至今仍能稳定、高效地支撑起整个数字世界。
DNS 记录
DNS的记录类型可以看作是不同的“指令”,共同构成了互联网的导航和功能系统。它们种类繁多,可以大致分为常用核心类型、安全验证类型,以及高级功能与新兴类型。
📌 常见核心记录类型
这些是支撑网站访问、邮件收发等基本功能的基础记录,也是最常打交道的类型。
| 记录类型 | 功能说明 | 应用示例 |
|---|---|---|
| A记录 | 域名 → IPv4,最基础、最核心的记录。 | 将 example.com 解析到 93.184.216.34 |
| AAAA记录 | 域名 → IPv6,A记录的IPv6升级版。 | 将 example.com 解析到 2606:2800:220:1:248:1893:25c8:1946 |
| CNAME记录 | 域名 → 域名(别名),将一个域名指向另一个域名。 | 将 www.example.com 指向 example.com,实现统一管理。 |
| MX记录 | 邮件交换,指定负责接收域名的邮件的服务器。 | 将 example.com 的邮件指向 mail.google.com |
| NS记录 | 指定DNS服务器,标识由哪个DNS服务器负责解析你的域名。 | 域名 example.com 的解析由 ns1.dnsprovider.com 负责。 |
| TXT记录 | 存储文本信息,常用于域名身份验证、安全策略声明(SPF/DKIM/DMARC)等。 | 在TXT记录中写入一个特定的验证字符串来证明你对域名的所有权。 |
| SOA记录 | 起始授权,记录DNS区域的版本信息、管理员邮箱、更新规则等核心管理参数。 | DNS区域的管理员是 admin@example.com,刷新时间为3600秒。 |
| PTR记录 | IP → 域名(反向解析),A/AAAA记录的逆向操作,用于验证IP身份。 | 将IP 93.184.216.34 解析回 example.com。 |
🛡️ 安全与验证相关记录类型
随着网络安全日益重要,以下记录类型专门用于增强DNS和域名的安全性。
| 记录类型 | 功能说明 | 应用示例 |
|---|---|---|
| CAA记录 | 指定哪些证书颁发机构有权为该域名签发SSL/TLS证书,防止证书被冒用。 | 仅允许 letsencrypt.org 为 example.com 颁发证书。 |
| DNSSEC相关 | 用于验证DNS数据的真实性和完整性,防止DNS欺骗和缓存投毒。 | |
| RRSIG | 包含资源记录的数字签名,用于验证数据真实性。 | 验证A记录 example.com → 93.184.216.34 确实来自权威DNS服务器。 |
| DNSKEY | 存储区域用于签名的公钥。 | DNS解析器通过DNSKEY中的公钥,验证RRSIG签名是否有效。 |
| DS | 存储DNSKEY记录的摘要,用于在父子域之间建立信任链。 | 验证子域 blog.example.com 的DNSKEY是真实的。 |
| NSEC/NSEC3 | 用于证明某个DNS名称不存在,防止恶意否定应答。 | 权威服务器用NSEC记录告知查询者 test.example.com 这个域名不存在。 |
🚀 高级功能与新兴记录类型
这些记录服务于更复杂的网络架构和现代化的应用需求。
| 记录类型 | 功能说明 | 应用示例 |
|---|---|---|
| SRV记录 | 服务定位,用于定义特定服务(如SIP、LDAP)的服务器主机名和端口。 | 将公司的即时通讯服务 _sip._tcp.example.com 指向 sipserver.example.com 的5060端口。 |
| DNAME记录 | 域名重定向,类似于CNAME,但可以批量重定向一个域名及其所有子域名。 | 设置 example.com 的DNAME指向 example.net 后,blog.example.com 也会自动指向 example.net。 |
| HTTPS/SVCB记录 | 这是HTTP/3的重要组成部分,可提供替代服务的连接信息(如端口、协议、ECH公钥等),优化连接体验。 | 浏览器通过查询example.com的HTTPS记录,发现其支持HTTP/3,从而直接建立QUIC连接。 |
当然,DNS记录类型远不止这些,还有存储地理位置的LOC记录、存放SSH密钥指纹的SSHFP记录等。不过对于多数需求而言,熟悉上面这些已经能覆盖绝大部分场景了。
私有地址选择
A、B、C类私有IP地址并非随意选出,而是一个历史、技术和现实需求相互交织的产物。主要原因是为了缓解IPv4地址枯竭,同时兼容当时大量仍在使用的“有类(Classful)”网络设备。
具体的地址选择,则是一个务实决策的过程,具体考量如下:
- A类 (10.0.0.0/8):原属已退役的ARPANET(互联网前身,1990年正式退役)。为避免它在公网被重新启用引发历史遗留问题,不如直接“回收”到内网。
- B类 (172.16.0.0/12):在当时的B类地址空间里,这是最低的一段连续未分配地址块。
- C类 (192.168.0.0/16):同理,在192.0.0.0/8这个C类大段里,这也是最低的一段连续未分配地址块。
这就是今天A、B、C类私有地址的由来: 10.0.0.0/8 是对历史资产的巧妙复用,而 172.16.0.0/12 和 192.168.0.0/16 则是简单地从可用空间里选取了最“干净”的连续地址块。整个过程就像一个有条不紊的仓库管理员,优先把那些闲置的、好管理的货架区域分配出去,而没有刻意去“挑选”特定号码。
流量 && 拥塞控制
TCP 的传输控制包含两大核心机制:流量控制是为了防止“发送方”淹没“接收方”,而拥塞控制是为了防止“发送方”淹没“网络”。它们共同决定了数据的发送速率,但出发点和作用范围完全不同。
一、流量控制:端到端的平衡
流量控制解决的是通信双方能力不匹配的问题。它是一种由接收方驱动的机制。
1. 核心原理:滑动窗口
TCP 头部有一个“窗口大小”(Window Size)字段,接收方通过它告诉发送方自己还能接收多少数据。这个值被称为接收窗口 (rwnd),代表接收方缓冲区的剩余大小。发送方在发送数据时,会确保已发送但未确认的数据总量不超过 rwnd。
2. 零窗口探测
如果接收方缓冲区满了,它会发送一个 rwnd=0 的窗口通告。此时发送方会停止发送,并启动一个“坚持定时器”,定期发送零窗口探测报文,询问接收方是否已有空间。这避免了死锁。
3. 糊涂窗口综合征 当接收方每次只腾出很少空间(如1字节),发送方立刻填入小数据包,导致网络效率低下。TCP 通过两端算法避免:接收方在窗口很小时延迟发送窗口更新,直到可用空间增大;发送方也避免发送过小的数据段(Nagle算法配合)。这部分在流量控制范畴内。
二、拥塞控制:对网络全局的感知
拥塞控制解决的是发送方与整个网络之间的平衡。网络是共享的,如果所有发送方都拼命发数据,路由器缓存会溢出,导致丢包和延迟剧增。拥塞控制要求每个 TCP 连接自适应地调整发送速率。
发送方维护一个拥塞窗口 (cwnd),它是对网络能承受的数据量的估计。实际发送窗口 = min(rwnd, cwnd)。拥塞控制的核心算法经历了从经典 Reno 到现代 BBR 的演进。
经典 Reno 系列算法 (基于丢包反馈)
Reno 算法将拥塞控制划分为四个阶段,由慢启动、拥塞避免、快速重传和快速恢复组成,通过两个关键变量控制:cwnd 和 慢启动阈值 (ssthresh)。
慢启动 连接建立或超时后,
cwnd初始很小(如1-10 MSS)。每收到一个 ACK,cwnd增加一个 MSS(即每过一个 RTT,cwnd翻倍)。这是一种探测性、指数级的窗口增长速度,直到cwnd >= ssthresh或发生丢包。拥塞避免 当
cwnd >= ssthresh时,进入该阶段,窗口改为每个 RTT 线性增加大约1个 MSS(即cwnd += 1/cwnd),缓慢逼近网络容量,防止骤然拥塞。快速重传 如果发送方收到3个或以上重复ACK,意味着有后续数据到达了接收方,只是中间有一个报文丢失,网络并未完全瘫痪。此时不必等待超时,立刻重传那个丢失的报文。
快速恢复 在快速重传后,网络仍能通信,所以不降到慢启动,而是:
ssthresh设为当前cwnd的一半。cwnd设为ssthresh + 3个报文段(考虑到已触发的重复ACK),然后进入拥塞避免阶段,线性增长。- 如果是超时重传,意味着网络可能严重堵塞,则
ssthresh设为cwnd/2,cwnd重置为初始值,重新进入慢启动。
现代改进与趋势
- CUBIC:Linux 默认算法,替代 Reno。它不再依赖简单的线性增加,而是使用一个三次函数来调整
cwnd,当网络接近饱和时增长更慢,远离饱和点时增长更快,极大地改善了高速长距离网络(如跨洋链路)的利用率,同时保持良好的公平性。 - BBR:谷歌提出的基于瓶颈带宽和往返时间的算法。它不再依赖丢包作为拥塞信号(因为丢包可能只是路由器缓存满后的被动丢弃,此时延迟已高),而是持续估计网络链路的实际可用带宽和最小RTT,直接使发送速率匹配管道速度,能显著降低延迟,且不怕偶然丢包。
三、两者协同与对比
| 维度 | 流量控制 | 拥塞控制 |
|---|---|---|
| 控制对象 | 点到点,发送方 vs 接收方 | 端到网络,发送方 vs 整个网络 |
| 核心变量 | rwnd (接收方通告) | cwnd (发送方自行探测与计算) |
| 触发信号 | 接收方的ACK中的窗口字段 | 丢包(超时/重复ACK)、延迟增大 |
| 目的 | 防止接收方缓冲区溢出 | 防止网络过载,公平分享带宽 |
| 最终速率 | 发送窗口 = min(rwnd, cwnd) |
掌握这些机制,就能理解一个看似简单的文件传输,其背后 TCP 是如何在保证可靠的同时,尽可能高效、公平地利用网络资源。
cwnd
你之所以会觉得“每个 ACK 增加一个 MSS”和“每轮指数级上升”像是两种描述,是因为前者是规则,后者是结果。关键在于:在慢启动阶段,一轮 RTT 内返回的 ACK 数量,恰好等于当前拥塞窗口(cwnd)的大小。
下面我们来具体拆解这个过程。
1. 慢启动的规则
慢启动的核心算法非常简单:每收到一个 ACK,拥塞窗口 cwnd 就增加 1 个 MSS(最大报文段长度)。
这看起来只是线性增长,每确认一个包才多一个包的空间。
2. 指数级增长是如何“涌现”的?
关键在于 TCP 的数据发送方式:发送方一次会发出 cwnd 个报文段(相当于将整个窗口“填满”),然后等待这些报文段的确认。
我们以初始 cwnd = 1 为例,模拟前几轮:
第一轮:cwnd = 1
- 发送方发出 1 个包。
- 经过 1 个 RTT 后,收到这 1 个包的 ACK。
- 根据规则:收到 1 个 ACK →
cwnd = 1 + 1 = 2。
第二轮:cwnd = 2
- 发送方发出 2 个包。
- 经过 1 个 RTT 后,收到这 2 个包的 ACK。
- 根据规则:收到 2 个 ACK →
cwnd = 2 + 2 = 4。
第三轮:cwnd = 4
- 发送方发出 4 个包。
- 收到 4 个 ACK →
cwnd = 4 + 4 = 8。
第四轮:cwnd = 8
- 收到 8 个 ACK →
cwnd = 8 + 8 = 16。
以此类推。
可以看到,每一轮结束后,cwnd 都变成了原来的两倍,呈现出标准的指数级增长(1 → 2 → 4 → 8 → 16…)。
3. 为什么说“每轮指数级”是自然的体现?
根本原因在于:
当前轮次发出的报文数 = 当前
cwnd值
当前轮次收到的 ACK 总数 ≈ 当前cwnd值
因此,每一轮结束后:
新 cwnd = 旧 cwnd + 收到的 ACK 数 ≈ 旧 cwnd + 旧 cwnd = 2 × 旧 cwnd
这就是每个 ACK 加 1 的线性规则,叠加窗口内所有报文全部被确认这一事实后,在每一轮 RTT 边界上自然表现出的指数倍增效果。
4. 这种增长的真实含义
慢启动的指数增长其实非常**“快”:只经过大约 10 个 RTT,窗口就能从 1 增长到约 1024。它的目的就是让 TCP 在连接初期快速逼近网络的实际可用带宽,而不是小心翼翼地线性增长。因此,与其说它是“慢启动”,不如说它是“迅猛的初始探测”**——只是在现代网络面前,这种探测看起来比较“慢”而已(古老术语沿用至今)。
总结
- 规则视角:每收到一个 ACK,
cwnd增加 1 MSS(线性累加)。 - 结果视角:由于窗口大小会决定发出的包数,而发出的包数又决定下一轮收到的 ACK 数,因此每一轮 RTT 结束时,
cwnd都会翻倍,表现出指数级增长。 - 本质原因:ACK 返回的数量与当前窗口大小成正比,导致线性规则在轮次尺度上转化为了指数级上升。
所以,“每个 ACK cwnd 体现为每轮指数级上升”是同一个机制的微观规则和宏观效果,两者完全一致。
CSRF Cross-Site Request Forgery
CSRF(跨站请求伪造) 是一种利用用户已登录身份,在用户不知情的情况下,以用户的名义发起恶意请求的攻击方式。简单说,攻击者可以诱导你点击一个链接或访问一个页面,你的浏览器就自动替你向银行、社交网站等发送转账、改密码的请求。
一、攻击原理:借用你的“身份证”
CSRF 之所以能成功,依赖于两个关键条件:
- 用户已登录目标网站,浏览器保存了该网站的认证凭证(Cookie、Session 等)。
- 攻击者构造了一个恶意的跨站请求,诱使用户点击或自动发起。
典型攻击流程:
- 你登录了银行网站
bank.com,浏览器存下了你的 Session Cookie。 - 你在同一浏览器中,不小心打开了恶意网站
evil.com。 evil.com页面中藏有一段代码,例如一个隐藏的表单或一段脚本,它会向bank.com/transfer发起一个 POST 请求,转账给黑客。- 浏览器在看到这个跨域请求时,会自动带上你之前登录过的
bank.com的 Cookie。 bank.com服务器收到请求,验证了 Cookie,认为是你本人发起的操作,转账成功。
核心问题:浏览器对于跨域请求,出于兼容性考虑,会自动附加目标域的 Cookie,但无法判断这个请求是否是用户真实意愿发起的。
二、常见攻击形式
攻击者可通过多种方式触发恶意请求:
- 隐藏表单自动提交:在
evil.com中嵌入一个不可见的<form>,设置action指向bank.com/transfer,用 JavaScript 自动提交。 - 伪装成 GET 请求的链接:虽然规范上 GET 不应有副作用,但很多早期应用直接用 GET 接受敏感操作。攻击者只需诱导点击
<img src="http://bank.com/changePassword?new=hacked">即可。 - 利用 AJAX 发送请求:如果目标服务器允许简单跨域请求(无自定义 Header),攻击者也可以用 JavaScript 的
fetch或XMLHttpRequest发送 POST 请求,同样会带上 Cookie。
三、危害举例
任何可通过 Web 请求触发的操作都可能被 CSRF 攻击利用:
- 修改账户信息:改密码、改绑邮箱/手机、改密保问题。
- 资金操作:转账、购物、提现。
- 发布内容:以用户名义发帖、发消息、关注他人。
- 权限提升:如果攻击者利用管理员身份发起 CSRF,可以创建新管理员账号,完全控制系统。
四、防御措施(重点)
防御 CSRF 的核心思路是:让服务器能够区分一个请求是“用户真实发起的”还是“跨站伪造的”。
1. 反 CSRF Token(最经典有效)
这是目前最广泛使用的防御手段。
- 服务器生成一个随机、不可预测的 Token,存入用户的 Session 中,并将其嵌入到前端页面(如表单隐藏域、自定义请求头)。
- 当用户提交请求时,必须携带这个 Token。攻击者无法获取到这个 Token(因同源策略限制,跨站脚本无法读取其他站点的内容),因此无法构造出合法的请求。
- 服务器验证请求中的 Token 与 Session 中的是否一致。
2. SameSite Cookie 属性
这是一个由浏览器原生支持的、极其有效的防御机制。
SameSite属性可以告诉浏览器:仅在请求来自同一站点时,才附带 Cookie。SameSite=Strict:完全禁止跨站携带 Cookie,最为严格,但可能影响从第三方跳转过来的用户体验(比如从邮件点链接进入已登录网站会需要重新登录)。SameSite=Lax:相对宽松,允许在顶级导航(如点击链接)时附带 Cookie,但禁止在 POST 表单、iframe、AJAX 等跨站上下文加载时附带。这是现代浏览器的默认值(Chrome、Firefox 等),能阻断绝大多数 CSRF。- 结合使用:
SameSite=Lax+ 关键操作的反 CSRF Token,是目前较佳实践。
3. 验证 Referer / Origin 头
检查请求的来源是否合法。
- Referer 头包含发起请求的页面完整 URL。
- Origin 头包含协议、主机名和端口,没有路径和查询参数,更简单且隐私。对于 AJAX 请求,浏览器总会发送 Origin。
- 服务器可以检验这两个头,确认请求来自自己信任的域名。但依赖 HTTP 头有被伪造或缺失的风险(如某些旧浏览器、隐私设置可能不发送 Referer),一般作为辅助手段。
4. 双重提交 Cookie
将 Token 同时放在 Cookie 和请求参数中,服务器验证两者是否一致。攻击者可以写入 Cookie(通过跨站请求),但无法读取 Cookie 值,故无法让参数与 Cookie 匹配。这种方法不需要服务器存储状态,但安全性略逊于 Session Token。
5. 关键操作强制用户交互
对于极其敏感的操作,可以要求重新输入密码、短信验证码、图形验证码,从根本上杜绝自动化请求。
6. 避免使用 GET 进行副作用操作
严格遵守 RESTful 规范,GET 请求仅用于读取数据。这样,即使攻击者嵌入 <img> 等 GET 请求,也无法造成破坏(因为服务端不接受 GET 执行修改操作)。
五、CSRF 与 XSS 的区别
- XSS(跨站脚本):攻击者在目标网站中注入恶意脚本,能读取同源数据(Cookie、Token 等),破坏性更大。XSS 可以彻底绕过 CSRF 防御(因为能拿到 Token)。
- CSRF:攻击者无法读取目标站点的内容,只是“借用”用户的 Cookie 发起跨站请求。
关系:如果存在 XSS 漏洞,CSRF 防御通常形同虚设;反之,防御 CSRF 的措施不一定能防 XSS。两者需要共同防范。
六、现状与趋势
随着 SameSite=Lax 成为浏览器默认行为,简单的 CSRF 攻击已经越来越难以奏效。很多现代 Web 框架(如 React、Vue 的 CSR 模式)也默认使用 Authorization 请求头(如 Bearer Token)来做身份验证,而非依赖 Cookie,天然免疫 CSRF。但在许多传统的、基于 Cookie/Session 的服务端渲染应用中,CSRF 防护仍然不可或缺。
总结:CSRF 利用了 HTTP 的无状态特性和浏览器自动携带 Cookie 的机制,属于一种“信任上的漏洞”。通过在每一次状态改变请求中加入不可预测的校验因子(CSRF Token),或者利用现代浏览器的 SameSite 机制,就能有效防范。