Skip to content

第7章 DNS、CDN 与负载均衡


元数据卡

项目内容
难度(中等)
前置第1章 网络分层模型;第4章 HTTP
关键词DNS、A/AAAA/CNAME 记录、递归/迭代查询、Anycast、CDN、边缘节点、L4/L7 负载均衡、一致性哈希、Round Robin
Python3.8+
代码量~100 行

你在哪

"你发现驿道上有一层隐形的导航系统——当你念出 'google.com' 时,DNS(域名解析法阵)把它翻译成驿道坐标。CDN(内容分发驿道)把法术卷轴复制到离你最近的信标塔。负载均衡把魔力流量分配到后端的多个信标塔上。"

上一章我们钻进 HTTP 和 TLS 的世界——学会了怎么发法术请求、怎么法力握手、怎么建一条安全的加密驿道。但有一个问题我们一直假装不存在:

法师的观测镜怎么知道 api.example.com 的信标塔在哪座山上?

你不可能把全世界所有驿道坐标背下来。就算背下来了,坐标也可能随时变。所以我们需要一个驿道地址簿——或者说,一个分布在全球的、每秒处理数亿次法术查询的、有史以来最大的分布式驿道地址簿。

这就是 DNS(Domain Name System)

但这章我们不止聊 DNS。单个服务器扛不住全世界的流量——哪怕你的代码再完美、算法再高效,一台机器每秒能处理的请求有物理上限。于是有了 CDN 把内容推到离用户最近的地方,有了负载均衡把流量分摊到一群服务器上。

这三者(DNS + CDN + 负载均衡)构成了现代互联网的入口骨架。你访问任何一个大型网站,第一秒内这三样东西全在工作。


本章分层

  • 必读:域名如何变成 IP(DNS 递归/迭代查询)、为什么缓存导致 DNS 变更延迟生效(TTL)、CDN 为什么快(边缘节点 + Anycast)、负载均衡怎么分流(L4 vs L7、常见算法)
  • 选读:DNS 记录类型(A/AAAA/CNAME/MX/NS/TXT)、GeoDNS 智能解析、一致性哈希的原理
  • 深水区:BGP Anycast 的路由层原理、迷你 DNS 解析器实现

本章不会要求你掌握

  • DNSSEC 签名链
  • BGP 的具体路由配置
  • 一致性哈希的虚拟节点算法(属于分布式缓存章节)

你的任务

学完本章,你应当能做到:

  1. 解释浏览器从输入 URL 到拿到 IP 地址的全过程——递归查询、缓存、迭代
  2. 看懂 DNS 记录类型:A、AAAA、CNAME、MX、NS、TXT
  3. dignslookup 诊断 DNS 问题
  4. 解释 CDN 的核心原理(边缘节点 + 回源 + Anycast)——理解 Anycast 只需要直觉,不要求 BGP 细节
  5. 对比 L4 和 L7 负载均衡的适用场景
  6. 理解一致性哈希的价值(具体实现属于分布式缓存章节)

遭遇战 → 获得技能

第1战:DNS 分层——全世界最大的分布式 K/V 存储

问题: 你在法师观测镜输入 www.example.com 回车。观测镜需要知道这个法术域名对应的驿道坐标。它去问谁?

答案不是一个服务器,而是一整棵树。

            ┌───────────────────────┐
            │      根 DNS 服务器      │  (13 组逻辑根,任播)
            │      (.)              │
            └────────┬──────────────┘

            ┌────────┴──────────────┐
            │    顶级域 DNS 服务器    │  .com / .org / .cn / .io ...
            │      (TLD)            │
            └────────┬──────────────┘

            ┌────────┴──────────────┐
            │    权威 DNS 服务器      │  example.com 的权威记录
            │  (Authoritative)      │
            └───────────────────────┘

DNS 层次结构(从根到叶):

层级角色例子
根服务器13 组逻辑服务器(物理节点数百个,靠 Anycast 分布全球),知道所有 TLD 服务器的地址a.root-servers.net
TLD 服务器管理顶级域(如 .com.org.cn.io),知道该域下所有权威服务器的地址Verisign 运营 .com
权威服务器管理具体域名,持有最终的 DNS 记录你买域名时配置的 NS 记录指向它
递归解析器替客户端做全部查询工作,一般由 ISP 或公共服务商运营8.8.8.8 (Google), 1.1.1.1 (Cloudflare)

关键区分:递归 vs 迭代

客户端 → 递归解析器(帮你跑完全程)

递归解析器 → 根服务器(告诉我 .com 的服务器在哪)
              ← 返回 .com TLD 的 IP

递归解析器 → .com TLD(告诉我 example.com 的权威服务器在哪)
              ← 返回权威服务器的 IP

递归解析器 → 权威服务器(www.example.com 的 IP 是多少?)
              ← 返回 93.184.216.34

递归解析器 → 客户端(93.184.216.34)
  • 递归查询(Recursive): 客户端对递归解析器说「我要 www.example.com,你查完告诉我」——自己什么也不用做,等人给结果。
  • 迭代查询(Iterative): 递归解析器对各级服务器说「你不知道?那告诉我去问谁」——多次往返,每次拿到下一站的地址。

实际中,递归解析器会缓存中间结果,不需要每次从头查。下一战细聊。

用 Python 模拟一次 DNS 查询(实际调用系统解析器):

python
import socket

def dns_lookup(hostname: str) -> str:
    try:
        ip = socket.gethostbyname(hostname)
        return ip
    except socket.gaierror as e:
        return f"解析失败: {e}"

# socket.gethostbyname 底层调用 getaddrinfo(3),
# 这个函数会走系统配置的解析流程(/etc/resolv.conf → 递归解析器 → 缓存)
print(dns_lookup("www.example.com"))   # 93.184.216.34
print(dns_lookup("google.com"))        # 142.250.80.46(可能因区域不同)

** 实际经验:** gethostbyname() 是旧 API,不支持 IPv6(AAAA 记录)。现代代码用 getaddrinfo() 更稳妥。Python 的 socket.getaddrinfo() 是跨平台的封装。

dig 命令看 DNS 查询全貌(这是排查网络问题时最常用的工具之一):

bash
# +trace 模拟递归解析器一步一步查
dig www.example.com +trace

# 只看特定记录类型
dig www.example.com A           # IPv4
dig www.example.com AAAA        # IPv6
dig example.com MX              # 邮件交换记录
dig example.com NS              # 域名服务器记录

# 使用特定递归解析器
dig @8.8.8.8 www.example.com    # 用 Google 的 DNS 查

第2战:DNS 记录类型与缓存——TTL 是一切取舍的核心

问题: 你改了域名解析法阵记录(比如把 www.example.com 指向新信标塔),为什么等了 24 小时才生效?

因为你改的是权威服务器上的记录。但全世界的递归解析器和浏览器都在缓存旧记录。缓存的有效期由 TTL(Time To Live) 决定——单位是秒。

常见 DNS 记录类型:

类型全称作用例子
AAddress域名 → IPv4example.com → 93.184.216.34
AAAAIPv6 Address域名 → IPv6example.com → 2606:2800:220:1:248:1893:25c8:1946
CNAMECanonical Name域名 → 另一个域名(别名)www.example.com → example.com
MXMail Exchange域名 → 邮件服务器example.com → mail.example.com (优先级 10)
NSName Server域名 → 权威 DNS 服务器example.com → ns1.example.com
TXTText存储任意文本SPF 记录、域名验证
SOAStart of Authority域的权威信息(主服务器、管理员邮箱、序列号、刷新间隔)

CNAME 的特殊之处: CNAME 指向另一个域名,解析器需要额外一次查询才能拿到最终 IP。这意味着:

  • CNAME 记录不能和其他记录共存(一个域名要么是 CNAME,要么有 A/AAAA 记录,二选一)
  • CNAME 的目标域名可以跨域(如 cdn.example.com → cdn.cloudflare.net

TTL 的博弈论:

短 TTL(60 秒): 变更快速生效,但缓存命中率低,权威服务器压力大
长 TTL(86400 秒=1天): 缓存命中率高,但变更要等一天

运维操作的核心原则:修改前降低 TTL → 等旧缓存过期 → 修改记录 → 确认生效后再恢复短 TTL
python
import socket
import time

def resolve_and_time(hostname: str, use_cache: bool = True) -> tuple[str, float]:
    """测量 DNS 解析耗时"""
    start = time.perf_counter()
    # 清空缓存(仅在调试场景)
    if not use_cache:
        socket.setdefaulttimeout(5)
    ip = socket.gethostbyname(hostname)
    elapsed = (time.perf_counter() - start) * 1000
    return ip, elapsed

# 第一次查询(可能需要走完整递归)
ip, ms = resolve_and_time("www.github.com")
print(f"[首次] {ip}{ms:.1f}ms")

# 第二次查询(大概率命中本地 DNS 缓存)
ip, ms = resolve_and_time("www.github.com")
print(f"[缓存] {ip}{ms:.1f}ms")
# 第二次通常比首次快几十倍;如果 TTL 短或缓存过期,差异不明显

第3战:CDN——把世界放在你家门口

问题: 一座法术影音塔在加州有一座信标塔。上海的法师想看 4K 法术影像。魔力流横跨太平洋,延迟至少 120ms,驿道容量成本天价。

怎么办?把法术内容复制到离用户近的地方。这就是 CDN(法术内容分发驿道)。

CDN 的核心架构:

                         ┌──────────────────────┐
                    ┌────┤    源服务器 (Origin)    ├────┐
                    │    └──────────────────────┘     │
                    │                                  │
              ┌─────┴──────┐                   ┌──────┴─────┐
              │  边缘节点 1  │                   │  边缘节点 2  │
              │ (上海)      │    ...             │ (纽约)       │
              └─────┬──────┘                   └──────┬─────┘
                    │                                  │
              ┌─────┴──────┐                   ┌──────┴─────┐
              │  用户 A     │                   │  用户 B     │
              └────────────┘                   └────────────┘

三大要素:

  1. 边缘节点(Edge/POP Node): 部署在全球各地的缓存服务器。用户请求的静态资源(图片、CSS/JS、视频片段)存储在边缘节点上,用户从最近的边缘节点获取,而不是跨越半个地球去源服务器。

  2. 回源(Origin Pull): 如果边缘节点没有用户请求的资源(缓存未命中),它会向源服务器请求一份副本,缓存到本地,然后返回给用户。写入缓存后,同一资源的下一个请求就直接命中边缘了。

  3. Anycast 路由: 多个边缘节点共享同一个 IP 地址。用户的请求自动被路由到「最近」的节点(BGP 层面的最短路径)。这种技术的优势是不需要用户做任何配置——DNS 解析结果和用户地理位置无关,路由由互联网的自治系统自动决定。

Anycast 的与众不同之处(对比 Unicast):

Unicast(传统的单播):
  同一 IP 只在同一台机器上
  你可能离那台机器很远

Anycast(任播):
  同一 IP 分布在全球多台机器上
  BGP 路由协议会把你的请求送到「离你最近」的那台

CDN 的 DNS 层面整合:

用户输入 www.example.com

DNS 解析 → 权威 DNS 返回的其实是 CDN 的边缘节点 IP
            (而不是源服务器的真实 IP)

用户向该 IP 请求资源

命中了边缘缓存? → 直接返回
没命中?         → 回源拉取 + 缓存

用 Python 检测 CDN 的存在(通过查看响应头):

python
import requests

def check_cdn(url: str) -> dict:
    resp = requests.get(url, timeout=5)
    headers = dict(resp.headers)

    cdns = {
        "Cloudflare": "cf-ray",
        "Akamai": "x-akamai-",
        "Fastly": "x-served-by",
        "CloudFront": "x-amz-cf-",
        "阿里云 CDN": "x-cdn-",
        "腾讯云 CDN": "x-cache-from",
    }

    detected = []
    for name, pattern in cdns.items():
        for k in headers:
            if pattern.lower() in k.lower():
                detected.append(name)
                break

    return {
        "url": url,
        "status": resp.status_code,
        "detected_cdn": detected or ["未检测到 CDN"],
        "cache_headers": {
            "cache-control": headers.get("Cache-Control"),
            "age": headers.get("Age"),  # 资源在 CDN 中缓存的秒数
            "x-cache": headers.get("X-Cache"),  # HIT/MISS
        }
    }

# 测试一个典型的 CDN 加速站点
result = check_cdn("https://www.example.com")
print(f"CDN 检测: {result['detected_cdn']}")
print(f"缓存状态: {result['cache_headers']}")

** Java / C++ 差异:** 网络请求库在各语言中 API 不同,但核心流程一致。Java 用 java.net.HttpURLConnection 或 Apache HttpClient,C++ 用 cpprestsdk 或 libcurl。DNS 解析过程对用户层透明——Python 的 requests、Java 的 URL、C++ 的 asio 底层都调用了系统的 getaddrinfo(3)。你不需要为每一层重写协议栈。


第4战:DNS 在 CDN 中的角色——智能解析与 GeoDNS

CDN 的最后一公里依赖 DNS 做流量调度。实现方式叫 GeoDNS——根据发起查询的 IP 地址所在地,返回不同的结果。

用户 A(北京)查询 www.cdn-example.com

GeoDNS → IP 归属查询 → 北京电信

返回最近边缘节点:123.123.123.1(北京边缘)

用户 B(纽约)查询同一域名

GeoDNS → IP 归属查询 → 纽约 Verizon

返回最近边缘节点:98.98.98.1(纽约边缘)

GeoDNS 的局限与 Anycast 的补充:

GeoDNS 在域名解析法阵层面做了一次地理路由。但 DNS 解析结果会被缓存(受 TTL 限制),缓存期间法师如果移动了怎么办?以及,GeoDNS 只能粗粒度调度(法师城/法术灵脉服务商级别),不能做到请求级别的负载感知。

所以大型 CDN 一般两层配合:

  1. GeoDNS → 把用户引导到最近的集群(比如华东 vs 北美)
  2. Anycast → 集群内部自动路由到具体边缘节点

第5战:负载均衡——把流量驯服成一只温顺的羊

问题: 你的驿道信标塔每秒收到 10 万法术请求。一座信标塔每秒只能处理 1 万。怎么办?十座信标塔。问题变成了:法术请求怎么分配到十座信标塔上?

这就是负载均衡器(Load Balancer) 的工作。

L4 vs L7 负载均衡:

特性L4(传输层)L7(应用层)
工作层级基于 IP + 端口基于 HTTP 内容(URL、Header、Cookie)
性能极高(纯内核转发)稍低(需要解析应用层协议)
灵活性低——只根据 IP/PORT 分发高——根据 URL 路径、用户身份、内容类型分发
例子LVS (Linux Virtual Server)、IPVSNginx、HAProxy、Envoy、Traefik
适用场景TCP/UDP 协议的任何服务,或不需要应用层信息HTTP/HTTPS/gRPC,需要内容感知路由

L4 的工作原理: 本质上是一个 NAT 路由器。收到 Client → LB:80 的包,把目标地址改写为 Backend-3:8080,转发。响应回来再改回去。

L7 的额外能力(以 Nginx 为例):

nginx
# L7 负载均衡可以根据 URL 路径分发到不同后端集群
upstream api_servers {
    least_conn;
    server api1.example.com:8080 weight=3;
    server api2.example.com:8080 weight=2;
    server api3.example.com:8080 backup;  # 备用节点
}

upstream static_servers {
    ip_hash;  # 同一 IP 始终去同一后端(session 保持)
    server static1.example.com:8080;
    server static2.example.com:8080;
}

server {
    listen 80;

    location /api/ {
        proxy_pass http://api_servers;
    }
    location /static/ {
        proxy_pass http://static_servers;
    }
}

常见负载均衡算法:

算法原理适用场景缺陷
Round Robin轮流分配。请求 1 → 服务器 A,请求 2 → B,请求 3 → C,然后循环服务器配置相同,请求处理时间相近不感知负载差异
Weighted Round Robin带权重的轮流,权重高的服务器收到更多请求服务器配置不同(比如 4C8G vs 8C16G)权重只能粗调
Least Connections发给当前活跃连接最少的服务器请求处理时间差异大(长连接场景尤佳)需要维护连接计数
IP Hash对客户端 IP 哈希,同一 IP 始终去同一后端需要 session 保持(sticky session)哈希可能不均;后端变化时大量连接偏移
一致性哈希在哈希环上分配,减少增删节点时的重映射缓存集群、分布式存储实现略复杂

深水区:一致性哈希的核心应用场景是分布式缓存(如 Redis 集群),本章只做原理介绍。它的完整算法、虚拟节点优化和运维实践属于专门的分布式缓存章节。

一致性哈希的精髓(也是最大难点):

传统哈希取模:hash("key1") % N,如果 N(服务器数量)变了,绝大部分 key 会映射到不同服务器——缓存失效风暴。

一致性哈希的做法:

把 N 台服务器映射到一个虚拟的环形空间(0 ~ 2^32-1)
1. 计算服务器节点的哈希值,放到环上
2. 每个 key 也计算哈希值,放到环上
3. 顺时针找到第一个服务器节点,就是该 key 的归属
4. 添加/删除一台服务器时,只影响它的邻居节点,
   其他节点的映射不受影响 —— 只有 K/N 的 key 需要迁移
                    ┌───┐
             ┌──────┤ 0 ├──────┐
             │      └───┘      │
             │                 │
      ┌──────┴──────┐  ┌──────┴──────┐
      │  Node C      │  │   key1 → A  │
      │              │  │              │
      └──────┬──────┘  └──────┬──────┘
             │                 │
      ┌──────┴──────┐  ┌──────┴──────┐
      │  key2 → A    │  │   Node A    │
      │              │  │              │
      └──────┬──────┘  └──────┬──────┘
             │                 │
      ┌──────┴──────┐  ┌──────┴──────┐
      │  Node B      │  │   key3 → C  │
      └─────────────┘  └─────────────┘

Python 实现一致性哈希(基础版):

python
import hashlib
import bisect
from typing import Any

class ConsistentHash:
    """一致性哈希环(带虚拟节点)"""

    def __init__(self, nodes: list[Any], replicas: int = 3):
        self.replicas = replicas          # 每个物理节点的虚拟节点数
        self.ring: list[int] = []         # 环上的哈希值(有序)
        self.map: dict[int, Any] = {}     # 哈希值 → 物理节点
        self.nodes = set()

        for node in nodes:
            self.add_node(node)

    def _hash(self, key: str) -> int:
        return int(hashlib.md5(key.encode()).hexdigest()[:8], 16)

    def add_node(self, node: Any):
        self.nodes.add(node)
        for i in range(self.replicas):
            h = self._hash(f"{node}:{i}")
            bisect.insort(self.ring, h)
            self.map[h] = node

    def remove_node(self, node: Any):
        self.nodes.discard(node)
        for i in range(self.replicas):
            h = self._hash(f"{node}:{i}")
            self.ring.remove(h)
            del self.map[h]

    def get_node(self, key: str) -> Any:
        if not self.ring:
            return None
        h = self._hash(key)
        # 二分查找:顺时针找第一个 ≥ h 的位置
        idx = bisect.bisect_left(self.ring, h)
        if idx == len(self.ring):
            idx = 0  # 绕回环起点
        return self.map[self.ring[idx]]

    def stats(self) -> dict:
        """统计各节点负责的 key 数量(用于测试分布均匀性)"""
        counts = {n: 0 for n in self.nodes}
        if not self.ring:
            return counts
        # 每个哈希段属于顺时针遇到的下一个节点
        for i, h in enumerate(self.ring):
            nxt = self.ring[(i + 1) % len(self.ring)]
            node = self.map[h]
            counts[node] += 1
        return counts


# 测试一致性哈希
nodes = ["server-a", "server-b", "server-c"]
ch = ConsistentHash(nodes, replicas=100)  # 增加虚拟节点改善均匀性

# 模拟 1000 个 key 的分布
keys = [f"user:{i}" for i in range(1000)]
distribution: dict[str, int] = {n: 0 for n in nodes}
for k in keys:
    n = ch.get_node(k)
    distribution[n] += 1

print("初始分布:", distribution)

# 移除 server-b
ch.remove_node("server-b")
new_dist: dict[str, int] = {n: 0 for n in ["server-a", "server-c"]}
for k in keys:
    n = ch.get_node(k)
    new_dist[n] += 1

print("移除 server-b 后:", new_dist)
# 只有原本在 server-b 上的 key 迁移到了 a 和 c
# server-a 和 server-c 上原本的 key 不受影响(约 2/3 保持稳定)

虚拟节点(Virtual Node / Replica)的作用:

没有虚拟节点时,环上的少量服务器节点分布可能不均匀——某段环上的 key 太多。加虚拟节点相当于把每个物理节点复制成 N 份随机分布在环上,统计学上大幅改善均匀性。


常见陷阱

项目:迷你 DNS 解析器 + 本地缓存

实现一个命令行工具 py-dns,模拟递归解析器行为:检查本地缓存,命中则返回;未命中则用系统 DNS 解析后缓存结果。

python
#!/usr/bin/env python3
"""
py-dns —— 迷你 DNS 解析器 + 缓存

用法:
    python py-dns.py www.example.com
"""

import sys
import socket
import time
import json
import os

CACHE_FILE = os.path.expanduser("~/.py-dns-cache.json")


def load_cache() -> dict:
    try:
        with open(CACHE_FILE) as f:
            return json.load(f)
    except (FileNotFoundError, json.JSONDecodeError):
        return {}


def save_cache(cache: dict):
    with open(CACHE_FILE, "w") as f:
        json.dump(cache, f, indent=2)


def lookup(hostname: str, ttl_default: int = 300) -> dict:
    """模拟递归解析器(带 TTL 缓存)"""
    cache = load_cache()
    now = time.time()
    hostname = hostname.lower().strip()

    # 检查缓存
    if hostname in cache:
        entry = cache[hostname]
        if now < entry["expires"]:
            remaining = int(entry["expires"] - now)
            return {
                "hostname": hostname,
                "ip": entry["ip"],
                "source": "cache",
                "ttl_remaining": remaining,
            }
        else:
            del cache[hostname]

    # 未命中 → 调用系统解析器
    try:
        ip = socket.gethostbyname(hostname)
    except socket.gaierror as e:
        return {"hostname": hostname, "error": str(e)}

    # 存入缓存(TTL 硬编码为 5 分钟,权威 TTL 信息从 socket 无法获取)
    cache[hostname] = {
        "ip": ip,
        "expires": now + ttl_default,
    }
    save_cache(cache)

    return {
        "hostname": hostname,
        "ip": ip,
        "source": "resolver",
        "ttl": ttl_default,
    }


if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("用法: py-dns.py <hostname>")
        sys.exit(1)

    result = lookup(sys.argv[1])
    if "error" in result:
        print(f"✗ {result['hostname']}: {result['error']}")
    else:
        source = "📦 缓存" if result["source"] == "cache" else "🌐 系统解析"
        extra = f" | TTL 剩余 {result['ttl_remaining']}s" if "ttl_remaining" in result else f" | TTL {result['ttl']}s"
        print(f"{result['hostname']}{result['ip']} ({source}{extra})")

运行示例:

bash
$ python py-dns.py www.example.com
www.example.com 93.184.216.34 (🌐 系统解析 | TTL 300s)

$ python py-dns.py www.example.com
www.example.com 93.184.216.34 (📦 缓存 | TTL 剩余 294s)

这个简陋的缓存系统就是真实 DNS 递归解析器的核心逻辑简化版。当然,真实的 BIND 或 Unbound 要复杂得多——处理并发、EDNS、DNSSEC、缓存淘汰策略等等——但灵魂是一样的。


通关挑战

问题 1(DNS 解析): 假设你修改了 example.com 的 A 记录从 IP_A 改成 IP_B,但你的用户仍然访问到 IP_A。列出所有可能的缓存层级和对应的 TTL 控制策略。

问题 2(CDN): 你的服务需要向全球用户分发静态文件(JS/CSS/图片),文件更新频率每周一次。CDN 的 TTL 应该设多久?如果更新了文件但 CDN 还在返回旧版本,有哪些方法强制刷新?

问题 3(一致性哈希): 5 台服务器,一致性哈希环上均匀分布。移除 1 台后,大约有多少比例的 key 需要迁移?

问题 4(L4 vs L7): 你的 HTTP API 需要根据 X-User-Region 请求头发送不同响应。应该用 L4 还是 L7 负载均衡?为什么?

问题 5(实战调试): 你的同事说「api.example.com 解析出来的 IP 不对」。你用 dig 查正常,但用浏览器访问就是旧 IP。可能是什么原因?怎么排查?


验收标准

技能自检方法
DNS 解析流程能手绘递归解析器到根/TLD/权威服务器的完整查询链
DNS 记录类型能说出 A、AAAA、CNAME、MX、NS 的用途和查询方式
缓存与 TTL能解释 DNS 变更需要等多久生效,以及如何加速
CDN 原理能说清边缘节点、回源、Anycast、GeoDNS 的配合关系
负载均衡算法能对比 Round Robin、最少连接、一致性哈希的适用场景
一致性哈希能手算新增/减少节点时 key 的迁移比例

常见卡点

  1. 把 DNS 缓存和 HTTP 缓存搞混。 DNS 缓存管的是「域名 → IP」的映射,在操作系统或路由器层面;HTTP 缓存管的是「URL → 资源」的映射,在浏览器或 CDN 层面。TTL 是各自独立的。

  2. 以为 TTL 是 DNS 解析的「生效延迟」。 TTL 是缓存有效期,不是 DNS 记录传播时间。权威服务器上的记录一改就生效 —— 问题是缓存里的旧记录要等到 TTL 到期才会去刷新。

  3. 认为 dig +trace 是实际查询路径。 +trace 模拟递归解析过程,但用的是你的本地 DNS 工具而不是递归解析器。真实查询中,递归解析器有缓存、有转发策略,路径可能完全不同。

  4. 一致性哈希的「均匀分布」不是自动的。 少量节点在环上的分布天然不均匀,必须加虚拟节点(replicas)或使用 ketama 算法改进。

  5. 把 Anycast 当作「自动负载均衡」。 Anycast 只在网络层做路由选择,它不知道后端负载状态。如果某个节点过载,Anycast 不会自动绕开它——需要在应用层有健康检查和故障剔除机制。


现在不需要理解

  • DNSSEC 的签名链和 RRSIG 记录细节——知道它存在就行,真实部署场景才会用到
  • EDNS 扩展机制——解决 DNS 报文长度限制的扩展,知道有这个东西即可
  • Envoy、Traefik、Kong 等云原生网关的详细配置——偏运维的负载均衡器配置,是下一层级的技能
  • BGP Anycast 的具体路由配置——那是网络工程师的工作,程序员的掌握程度是「理解原理」足够
  • DNS 的 DoH/DoT(DNS over HTTPS/TLS)——隐私增强协议,概念简单,需要时查文档即可

旅人笔记

  • DNS 是互联网的隐式基础设施——你每次请求都要过它,但平时根本感觉不到它的存在。所有系统瓶颈排查,DNS 都是第一检查项。
  • CDN 的核心哲学是「把内容推到离用户最近处」——延迟是物理定律(光速),不能消除但能缩短。边缘计算的逻辑也一样。
  • 一致性哈希是分布式系统中最重要的设计模式之一。你会在缓存、数据库分片、消息队列分区中反复遇到它。
  • 负载均衡器不是万能的——健康检查、熔断、重试、超时设置每个都要想清楚。一个过载的负载均衡器比一个挂掉的服务器更可怕。
  • TTL 是一切缓存策略的命门:太短⇒压力大,太长⇒更新慢。调 TTL 是分布式系统的日常手艺活。

→ 下一站预告

DNS 帮我们把域名变成了 IP,负载均衡把请求送到具体服务器,CDN 把内容推到边缘。但所有这些都建立在一个假设上:数据能在两个端点之间可靠地传输。下一章,我们下降到数据链路层——看看以太网帧长什么样、MAC 地址怎么工作、交换机怎么阻止冲突域扩散,以及为什么说「网线不通」的诊断要从物理层开始。

Built with VitePress | Software Systems Atlas