土法炼钢兴趣小组的算法知识备份

【网络工程】全局负载均衡:GSLB、DNS 调度与 Anycast

文章导航

分类入口
network
标签入口
#gslb#dns#anycast#bgp#load-balancing

目录

前两篇讲的 L4 和 L7 负载均衡都工作在单站点内部——将流量分发给同一数据中心的多台服务器。但当你的服务部署在多个地理位置(北京、上海、广州、新加坡),就需要一个跨地域的流量调度层——这就是全局负载均衡(Global Server Load Balancing,GSLB)的职责。

GSLB 要回答的核心问题是:这个用户的请求应该发往哪个数据中心?依据是什么?出错时怎么切换?

一、GSLB 的调度维度

1.1 为什么需要全局调度

跨地域部署的动机通常是以下一种或多种:

动机 说明 典型场景
降低延迟 用户就近访问,减少网络往返时间 CDN、全球化 SaaS
容灾 某个数据中心故障时流量切换到其他中心 金融、电商
合规 数据必须存储和处理在特定地区 GDPR、数据本地化
容量扩展 单数据中心容量不足,多中心分担 大流量应用

1.2 调度依据

GSLB 的调度决策可以基于多种因素:

用户请求 → GSLB 决策引擎
  │
  ├── 地理位置 (Geography)
  │   └── 用户在哪个城市/国家?→ 选最近的数据中心
  │
  ├── 网络距离 (Network Proximity)
  │   └── 用户到各数据中心的延迟/跳数?→ 选网络最近的
  │
  ├── 负载状况 (Load)
  │   └── 各数据中心的当前负载?→ 选负载最低的
  │
  ├── 健康状态 (Health)
  │   └── 哪些数据中心是健康的?→ 排除故障中心
  │
  └── 业务规则 (Policy)
      └── 数据主权要求?灰度规则?→ 按策略路由

二、DNS-based GSLB

2.1 GeoDNS 原理

最常见的 GSLB 实现是基于 DNS 的地理路由——根据用户的 DNS Resolver IP 判断地理位置,返回最近数据中心的 IP:

用户 (北京) → Local DNS Resolver (北京 ISP)
  → 递归查询 app.example.com
  → 权威 DNS (GSLB)
  → 检查 Resolver IP: 202.106.x.x → 判定为北京
  → 返回北京数据中心 IP: 10.1.1.100

用户 (广州) → Local DNS Resolver (广州 ISP)
  → 递归查询 app.example.com
  → 权威 DNS (GSLB)
  → 检查 Resolver IP: 183.60.x.x → 判定为广州
  → 返回广州数据中心 IP: 10.3.1.100

2.2 GeoDNS 的精度问题

GeoDNS 基于 IP 地理数据库(如 MaxMind GeoIP2)将 Resolver IP 映射到地理位置。但这有几个精度问题:

问题 1:用户和 Resolver 不在同一位置

实际场景:
  用户 (广州) → Google DNS 8.8.8.8 (美国 PoP)
  → GSLB 看到的 Resolver IP 是美国的
  → 返回美国数据中心 IP
  → 用户跨太平洋访问!延迟 200ms+

解决方案:EDNS Client Subnet (ECS)
  用户 (广州) → Google DNS + ECS: 183.60.0.0/20
  → GSLB 看到客户端子网 183.60.0.0/20
  → 判定为广州
  → 返回广州数据中心 IP ✓

EDNS Client Subnet(RFC 7871)让递归 DNS 服务器在查询中携带客户端的部分 IP(通常 /24 或 /20),让权威 DNS 能够基于真实客户端位置做决策:

# 用 dig 测试 ECS 支持
dig @8.8.8.8 app.example.com +subnet=183.60.0.0/20

# 查看响应中的 ECS 信息
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
; CLIENT-SUBNET: 183.60.0.0/20/24  ← scope /24 表示缓存粒度

问题 2:IP 地理数据库不准确

移动网络的出口 IP 可能和用户实际位置不同;VPN 用户的 IP 反映的是 VPN 出口位置。GeoIP 数据库对部分 IP 段的准确率只有 60-70%。

问题 3:DNS 缓存导致切换延迟

DNS 记录有 TTL(生存时间)。即使 GSLB 已经切换了 IP,缓存了旧 IP 的 Resolver 要等到 TTL 过期才会重新查询:

故障发生: T=0
GSLB 切换: T=10s(检测到故障并更新 DNS)
TTL 过期: T=300s(如果 TTL=300)
所有用户切换完成: T=300s+

这 5 分钟内,缓存了旧 IP 的用户仍然访问故障数据中心

2.3 TTL 策略

TTL 值 故障切换速度 DNS 查询负载 适用场景
30s 最快 最高 高可用要求极高
60-120s 推荐默认值
300s 普通 Web 服务
3600s 最低 静态内容/CDN

生产建议:

2.4 GeoDNS 实战配置

# 使用 PowerDNS + GeoIP Backend
# /etc/pdns/pdns.conf
launch=geoip
geoip-database-files=/usr/share/GeoIP/GeoLite2-City.mmdb
geoip-zones-file=/etc/pdns/geo-zones.yaml
# /etc/pdns/geo-zones.yaml
domains:
- domain: example.com
  ttl: 60
  records:
    app.example.com:
      - soc: "0.0.0.0/0"           # 默认
        content:
          - type: A
            value: 10.2.1.100       # 上海(默认)
      - soc: "%cn"                  # 中国
        content:
          - type: A
            value: 10.1.1.100       # 北京
      - soc: "%cn-gd"              # 广东省
        content:
          - type: A
            value: 10.3.1.100       # 广州
      - soc: "%sg"                  # 新加坡
        content:
          - type: A
            value: 10.4.1.100       # 新加坡
# 使用 BIND + GeoIP ACL
# /etc/bind/named.conf
acl "china-north" {
    202.106.0.0/16;    # 北京联通
    61.135.0.0/16;     # 北京电信
    // ... 更多中国北方 IP 段
};

acl "china-south" {
    183.60.0.0/16;     # 广东电信
    14.17.0.0/16;      # 广东联通
    // ... 更多中国南方 IP 段
};

view "china-north" {
    match-clients { china-north; };
    zone "example.com" {
        type master;
        file "/etc/bind/zones/example.com.north";
    };
};

view "china-south" {
    match-clients { china-south; };
    zone "example.com" {
        type master;
        file "/etc/bind/zones/example.com.south";
    };
};

2.5 DNS Failover 的多种模式

除了加权路由,DNS-based GSLB 还支持多种故障转移模式:

模式 1:主备故障转移(Active-Passive Failover)

正常: app.example.com → 10.1.1.100 (北京,PRIMARY)
故障: app.example.com → 10.2.1.100 (上海,SECONDARY)

适用于成本敏感场景——备站点只在主站点故障时启用,平时不承担流量。

模式 2:加权轮询(Weighted Round Robin)

app.example.com → 10.1.1.100 (权重 60)
app.example.com → 10.2.1.100 (权重 40)

DNS 服务器根据权重比例返回不同记录。客户端每次查询可能得到不同 IP。

模式 3:延迟路由(Latency-Based Routing)

用户 (日本) → 探测各站点延迟
  北京: 45ms
  上海: 38ms
  新加坡: 62ms
  → 返回上海 IP (最低延迟)

AWS Route 53 等云 DNS 服务通过在全球部署探测节点,持续测量用户到各数据中心的延迟,返回延迟最低的站点。比 GeoDNS 更精确,因为网络距离不等于地理距离。

模式 4:多值应答(Multi-Value Answer)

# DNS 返回多个 IP,客户端自行选择
dig app.example.com +short
10.1.1.100
10.2.1.100
10.3.1.100

# 每个 IP 关联独立健康检查
# 不健康的 IP 自动从应答中移除

多值应答让客户端拥有选择权——现代浏览器实现了 Happy Eyeballs 算法,会并行尝试多个 IP,选择最快响应的那个。

2.6 自建 GSLB 方案

除了云厂商的托管 DNS 服务,也可以使用开源软件自建 GSLB:

# 方案 1:PowerDNS + Lua 脚本实现动态调度
# /etc/pdns/gslb.lua
function preresolve(dq)
    if dq.qname:equal(newDN("app.example.com")) then
        -- 获取各站点健康状态
        local bj_healthy = checkHealth("10.1.1.100")
        local sh_healthy = checkHealth("10.2.1.100")

        -- 获取客户端子网(ECS)
        local ecs = dq.ecs
        local region = geoLookup(ecs or dq.remoteaddr)

        -- 调度逻辑
        if region == "north" and bj_healthy then
            dq:addAnswer(pdns.A, "10.1.1.100", 60)
        elseif sh_healthy then
            dq:addAnswer(pdns.A, "10.2.1.100", 60)
        elseif bj_healthy then
            dq:addAnswer(pdns.A, "10.1.1.100", 60)
        else
            -- 所有站点不健康,返回降级页面 IP
            dq:addAnswer(pdns.A, "10.0.0.1", 30)
        end
        return true
    end
    return false
end
# 方案 2:CoreDNS + 自定义插件
# Corefile
example.com {
    gslb {
        zone example.com
        record app {
            backend 10.1.1.100 {
                region china-north
                weight 60
                healthcheck http://10.1.1.100:8080/health 5s
            }
            backend 10.2.1.100 {
                region china-south
                weight 40
                healthcheck http://10.2.1.100:8080/health 5s
            }
        }
        geoip /usr/share/GeoIP/GeoLite2-City.mmdb
        ecs true
    }
    log
    errors
}
# 方案 3:使用 gdnsd(专为 GeoDNS 设计)
# /etc/gdnsd/config
options => {
    listen => [ 0.0.0.0 ]
    dns_port => 53
    edns_client_subnet => true
}

service_types => {
    http_check => {
        plugin => http_status
        url_path => /health
        interval => 5
        timeout => 3
    }
}

# /etc/gdnsd/zones/example.com
$TTL 60
@ SOA ns1.example.com. admin.example.com. (
    2024010101 3600 900 604800 60 )
@ NS ns1.example.com.
@ NS ns2.example.com.
app DYNA geoip!mymap

自建 GSLB 的优势是完全可控,但运维负担大——需要自己维护 GeoIP 数据库更新、健康检查系统和 DNS 基础设施的高可用。中小规模团队建议使用云 DNS 服务。

三、Anycast

3.1 Anycast 原理

Anycast 用同一个 IP 地址在多个物理位置同时宣告(通过 BGP),路由器根据 BGP 路由选择将流量发送到最近的节点:

                同一 IP: 203.0.113.100

   ┌──────────────┐    ┌──────────────┐    ┌──────────────┐
   │  北京 PoP     │    │  上海 PoP     │    │  新加坡 PoP  │
   │  BGP 宣告     │    │  BGP 宣告     │    │  BGP 宣告    │
   │ 203.0.113.100│    │ 203.0.113.100│    │ 203.0.113.100│
   └──────┬───────┘    └──────┬───────┘    └──────┬───────┘
          │                   │                   │
    北京用户 →            上海用户 →          新加坡用户 →
    路由到北京 PoP       路由到上海 PoP      路由到新加坡 PoP

3.2 Anycast 与 Unicast 的对比

维度 DNS-based GSLB (Unicast) Anycast
IP 数量 每个站点不同 IP 所有站点相同 IP
调度层 DNS 层 BGP 路由层
切换速度 受 TTL 影响(秒-分钟) BGP 收敛(秒级)
TCP 支持 好(连接固定到一个 IP) 有风险(路由变化可能导致连接重置)
UDP 支持 非常好(无状态)
精度 依赖 GeoIP 数据库 由 BGP 拓扑决定
配置复杂度 高(需要 BGP 对等)

3.3 Anycast 的 TCP 问题

Anycast 对 UDP 协议(如 DNS)非常友好,但对 TCP 有一个固有问题——路由漂移(Route Flapping)。当 BGP 路由变化时,已建立的 TCP 连接可能被路由到不同的 PoP,导致连接重置:

T=0: 用户 TCP 连接路由到北京 PoP
T=5: BGP 路由变化(北京 PoP 链路闪断)
T=6: 后续数据包路由到上海 PoP
     上海 PoP 没有这个连接的状态 → RST
     用户连接断开 ✗

解决方案

  1. ECMP + 哈希:在路由器上使用基于五元组的 ECMP 哈希,保证同一连接始终走同一路径。
  2. Anycast + Unicast 混合:DNS 查询用 Anycast,TCP 连接用 Unicast。CDN 常用这种方式。
  3. QUIC:QUIC 使用 Connection ID 而非五元组标识连接,天然容忍路由变化。

3.4 Anycast 的故障转移

Anycast 的故障转移是自动的——当一个 PoP 停止 BGP 宣告时,该 PoP 的流量自动路由到下一个最近的 PoP:

# PoP 节点的 BGP 配置 (BIRD)
# /etc/bird/bird.conf
router id 10.0.1.1;

protocol static anycast_routes {
    route 203.0.113.100/32 blackhole;  # Anycast 路由
}

protocol bgp upstream1 {
    local as 65001;
    neighbor 10.0.0.1 as 64512;
    export filter {
        if net = 203.0.113.100/32 then accept;
        reject;
    };
}

# 健康检查:如果本地服务不健康,撤回 BGP 宣告
protocol static {
    route 203.0.113.100/32 blackhole;
}

# 使用 health check 脚本控制路由宣告
# 健康时: birdc enable anycast_routes
# 不健康时: birdc disable anycast_routes
#!/bin/bash
# anycast_healthcheck.sh — Anycast 健康检查脚本
# 检查本地服务是否正常,控制 BGP 路由宣告

SERVICE_URL="http://localhost:8080/health"
CHECK_INTERVAL=3
FAIL_THRESHOLD=3
CONSECUTIVE_FAILS=0

while true; do
    HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
        --connect-timeout 2 --max-time 5 "$SERVICE_URL")

    if [ "$HTTP_CODE" = "200" ]; then
        CONSECUTIVE_FAILS=0
        # 确保路由已宣告
        birdc show protocols anycast_routes | grep -q "up" || \
            birdc enable anycast_routes
    else
        CONSECUTIVE_FAILS=$((CONSECUTIVE_FAILS + 1))
        if [ "$CONSECUTIVE_FAILS" -ge "$FAIL_THRESHOLD" ]; then
            echo "$(date): 连续失败 $CONSECUTIVE_FAILS 次,撤回 BGP 宣告"
            birdc disable anycast_routes
        fi
    fi

    sleep "$CHECK_INTERVAL"
done

3.5 BGP 社区在流量调度中的应用

BGP Community 是 Anycast 精细化流量调度的核心工具。通过设置不同的 Community 值,可以控制路由的传播范围和优先级:

# BIRD 配置:使用 BGP Community 控制路由传播
# /etc/bird/bird.conf

# 定义 Community 常量
define LOCAL_PREF_HIGH = (65001, 100);   # 高优先级
define LOCAL_PREF_LOW  = (65001, 50);    # 低优先级
define NO_EXPORT       = (65535, 65281); # 不向外部对等体宣告
define BLACKHOLE       = (65001, 666);   # 黑洞路由(DDoS 防御)

protocol bgp upstream1 {
    local as 65001;
    neighbor 10.0.0.1 as 64512;
    export filter {
        if net = 203.0.113.100/32 then {
            bgp_community.add(LOCAL_PREF_HIGH);
            accept;
        }
        reject;
    };
}

# 备用上游:设置低优先级,正常情况下不使用
protocol bgp upstream2 {
    local as 65001;
    neighbor 10.0.0.2 as 64513;
    export filter {
        if net = 203.0.113.100/32 then {
            # AS Path Prepend 降低优先级
            bgp_path.prepend(65001);
            bgp_path.prepend(65001);
            bgp_community.add(LOCAL_PREF_LOW);
            accept;
        }
        reject;
    };
}

实际应用中,BGP Community 常用于以下场景:

场景 Community 值 效果
地区限制 (ISP, region) 路由只在特定地区传播
优先级控制 (ISP, local-pref) 设置上游路由器的 Local Preference
黑洞路由 (ISP, 666) 上游丢弃匹配流量(DDoS 防御)
不宣告 no-export 路由不向外部 AS 传播
# 验证 BGP Community
birdc show route 203.0.113.100/32 all
# 输出中应包含设置的 Community 值

# 使用 looking glass 验证全球路由状态
# https://lg.he.net/
# https://bgp.tools/prefix/203.0.113.100

3.6 Anycast 性能测量

部署 Anycast 后需要持续监控路由质量,确保用户被路由到最优 PoP:

# 使用 RIPE Atlas 进行全球 Anycast 覆盖测量
# 创建一个从全球 100 个探针到 Anycast IP 的 traceroute 测量
curl -X POST "https://atlas.ripe.net/api/v2/measurements/" \
    -H "Authorization: Key YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
        "definitions": [{
            "type": "traceroute",
            "af": 4,
            "target": "203.0.113.100",
            "description": "Anycast coverage test",
            "protocol": "ICMP"
        }],
        "probes": [{
            "type": "area",
            "value": "WW",
            "requested": 100
        }]
    }'
#!/usr/bin/env python3
"""分析 Anycast 路由分布,验证用户是否被路由到最近的 PoP"""

import json
import subprocess
from collections import Counter

def analyze_anycast_distribution(target_ip, dns_servers):
    """从多个 DNS 视角分析 Anycast IP 的路由分布"""
    pop_distribution = Counter()

    for dns in dns_servers:
        # 通过 HTTP 头判断 Anycast 路由到哪个 PoP
        result = subprocess.run(
            ["curl", "-s", "--connect-timeout", "5",
             "--resolve", f"app.example.com:80:{target_ip}",
             "http://app.example.com/debug/pop"],
            capture_output=True, text=True
        )
        if result.returncode == 0:
            pop = result.stdout.strip()
            pop_distribution[pop] += 1

    # 输出分布
    total = sum(pop_distribution.values())
    print(f"\nAnycast PoP 分布 (总计 {total} 次探测):")
    for pop, count in pop_distribution.most_common():
        pct = count / total * 100
        bar = "█" * int(pct / 2)
        print(f"  {pop:15s}: {count:3d} ({pct:5.1f}%) {bar}")

if __name__ == "__main__":
    dns_list = [
        "8.8.8.8", "1.1.1.1", "223.5.5.5",
        "114.114.114.114", "208.67.222.222",
    ]
    analyze_anycast_distribution("203.0.113.100", dns_list)

四、多活架构的流量调度

4.1 多活模式

模式 说明 复杂度 适用场景
主备(Active-Passive) 一个中心服务,另一个待命 灾备需求
双活(Active-Active) 两个中心同时服务 容量扩展 + 容灾
多活(Multi-Active) 三个以上中心同时服务 极高 全球化服务

4.2 流量切换策略

正常状态:
  北京: 60% 流量
  上海: 40% 流量

故障切换(北京故障):
  Step 1: GSLB 检测到北京不健康
  Step 2: 更新 DNS(移除北京 IP 或降低权重)
  Step 3: 上海接收 100% 流量

  关键考量:
  - 上海是否有足够容量承接 100% 流量?
  - 数据同步是否跟上?是否存在数据不一致窗口?
  - 切换后用户 session 是否丢失?

容量规划:多活架构中,每个数据中心必须预留足够容量应对其他中心的故障。如果两个中心各承担 50% 流量,则每个中心的实际容量需要达到 100%——这意味着在正常状态下有 50% 的资源”浪费”。

常见的折中方案是降级切换:故障时允许部分非关键功能降级运行,减少容量需求。

4.3 DNS 权重调整实现

# 使用 AWS Route 53 加权路由
aws route53 change-resource-record-sets \
    --hosted-zone-id Z1234567890 \
    --change-batch '{
        "Changes": [
            {
                "Action": "UPSERT",
                "ResourceRecordSet": {
                    "Name": "app.example.com",
                    "Type": "A",
                    "SetIdentifier": "beijing",
                    "Weight": 60,
                    "TTL": 60,
                    "ResourceRecords": [{"Value": "10.1.1.100"}]
                }
            },
            {
                "Action": "UPSERT",
                "ResourceRecordSet": {
                    "Name": "app.example.com",
                    "Type": "A",
                    "SetIdentifier": "shanghai",
                    "Weight": 40,
                    "TTL": 60,
                    "ResourceRecords": [{"Value": "10.2.1.100"}]
                }
            }
        ]
    }'

# 故障切换:将北京权重设为 0
aws route53 change-resource-record-sets \
    --hosted-zone-id Z1234567890 \
    --change-batch '{
        "Changes": [{
            "Action": "UPSERT",
            "ResourceRecordSet": {
                "Name": "app.example.com",
                "Type": "A",
                "SetIdentifier": "beijing",
                "Weight": 0,
                "TTL": 60,
                "ResourceRecords": [{"Value": "10.1.1.100"}]
            }
        }]
    }'

4.4 健康检查驱动的自动切换

# AWS Route 53 健康检查 + 故障转移路由
# 1. 创建健康检查
HealthCheck:
  Type: HTTP
  FullyQualifiedDomainName: app-beijing.example.com
  Port: 443
  ResourcePath: /readyz
  RequestInterval: 10       # 每 10 秒检查一次
  FailureThreshold: 3       # 连续失败 3 次判定不健康

# 2. 配置故障转移路由策略
ResourceRecordSet:
  Name: app.example.com
  Type: A
  Failover: PRIMARY          # 主记录
  HealthCheckId: hc-beijing
  TTL: 60
  ResourceRecords:
    - Value: 10.1.1.100      # 北京

ResourceRecordSet:
  Name: app.example.com
  Type: A
  Failover: SECONDARY        # 备记录
  TTL: 60
  ResourceRecords:
    - Value: 10.2.1.100      # 上海(不需要健康检查)

五、GSLB 方案对比

5.1 综合对比

特性 DNS-based GSLB Anycast HTTP 重定向 混合方案
切换速度 秒-分钟(受 TTL) 秒级(BGP 收敛) 实时 秒级
对 TCP 友好 有风险
部署复杂度 高(需要 BGP)
精度 依赖 GeoIP + ECS 由 BGP 拓扑决定 可用客户端 IP 最高
适用场景 大多数 Web 服务 DNS 服务/CDN 小流量场景 大规模全球化

5.2 推荐架构

小规模(<3 个数据中心):
  DNS-based GSLB(Route 53 / CloudFlare)
  + 各站点 L4/L7 LB

中等规模(3-5 个数据中心):
  GeoDNS + Anycast DNS
  + 各站点 L4/L7 LB
  + 自动健康检查和故障转移

大规模(5+ 个数据中心):
  Anycast + BGP Anycast
  + ECMP 哈希保证连接亲和
  + 分层架构(Anycast→L4→L7→App)

六、GSLB 监控与可观测性

6.1 关键监控指标

GSLB 系统的可观测性往往被忽视,直到故障切换时才发现监控缺失。以下是必须监控的指标:

指标类别 具体指标 告警阈值
DNS 解析 各站点的 DNS 查询分布比例 偏离预期权重 >15%
DNS 延迟 权威 DNS 的查询响应时间 P99 > 100ms
健康检查 各站点健康状态变化次数 频繁抖动 >5次/小时
流量分布 各站点实际请求量比例 与 DNS 权重不匹配
故障切换 切换耗时(从检测到生效) > 120s
ECS 覆盖 带 ECS 信息的查询占比 < 50%
# Prometheus + Blackbox Exporter 监控 GSLB
# prometheus.yml
scrape_configs:
  - job_name: 'dns_gslb'
    scrape_interval: 15s
    metrics_path: /probe
    params:
      module: [dns_gslb_check]
    static_configs:
      - targets:
          - 8.8.8.8       # 从 Google DNS 视角探测
          - 1.1.1.1       # 从 Cloudflare DNS 视角探测
          - 223.5.5.5     # 从阿里 DNS 视角探测
    relabel_configs:
      - source_labels: [__address__]
        target_label: __param_target
      - source_labels: [__param_target]
        target_label: instance
      - target_label: __address__
        replacement: blackbox-exporter:9115

# blackbox.yml
modules:
  dns_gslb_check:
    prober: dns
    timeout: 5s
    dns:
      query_name: app.example.com
      query_type: A
      transport_protocol: udp
      preferred_ip_protocol: ip4
      valid_rcodes:
        - NOERROR

6.2 DNS 流量分析

#!/bin/bash
# gslb_monitor.sh — 持续监控 GSLB 解析结果分布

TARGET="app.example.com"
RESOLVERS=("8.8.8.8" "1.1.1.1" "223.5.5.5" "114.114.114.114")
SAMPLES=50

echo "$(date): GSLB 解析分布监控"
echo "目标域名: $TARGET"
echo "采样次数: $SAMPLES per resolver"
echo "---"

for resolver in "${RESOLVERS[@]}"; do
    echo -e "\nResolver: $resolver"
    for i in $(seq 1 $SAMPLES); do
        dig +short @"$resolver" "$TARGET" 2>/dev/null
    done | sort | uniq -c | sort -rn | while read count ip; do
        pct=$((count * 100 / SAMPLES))
        printf "  %-20s %3d/%d (%d%%)\n" "$ip" "$count" "$SAMPLES" "$pct"
    done
done

# 告警判断:如果某个 IP 占比为 0,说明可能被移除或故障
echo -e "\n--- 异常检测 ---"
expected_ips=("10.1.1.100" "10.2.1.100")
for resolver in "${RESOLVERS[@]}"; do
    results=$(dig +short @"$resolver" "$TARGET" 2>/dev/null | sort -u)
    for expected in "${expected_ips[@]}"; do
        if ! echo "$results" | grep -q "$expected"; then
            echo "警告: $resolver 未返回 $expected"
        fi
    done
done

6.3 故障切换演练

GSLB 故障切换必须定期演练,否则真正故障时可能暴露配置问题:

# 故障切换演练检查清单
#
# 演练前:
# 1. 确认备站点容量充足
# 2. 通知相关团队
# 3. 准备回滚方案
# 4. 开启详细日志
#
# 演练步骤:
# 1. 记录当前流量分布基线
# 2. 手动将主站点设为不健康(或降低 DNS 权重到 0)
# 3. 观察 DNS 解析变化
# 4. 验证流量已切换到备站点
# 5. 检查用户体验指标(错误率、延迟)
# 6. 恢复主站点
# 7. 验证流量恢复到正常分布
#
# 记录:
# - 故障检测耗时
# - DNS 切换耗时
# - 流量完全迁移耗时
# - 期间错误率变化
# - 备站点性能变化

# 执行切换
echo "Step 1: 记录当前基线"
dig +short app.example.com @8.8.8.8
curl -s https://app.example.com/metrics | grep http_requests_total

echo "Step 2: 模拟主站点故障"
# Route 53: 将主站点权重设为 0
# 自建 DNS: 修改健康检查返回 503

echo "Step 3: 监控切换过程"
while true; do
    echo "$(date +%H:%M:%S) $(dig +short app.example.com @8.8.8.8)"
    sleep 5
done

七、排查 GSLB 问题

7.1 DNS 调度验证

# 从不同位置验证 DNS 解析结果
# 使用不同的 DNS Resolver
for dns in 8.8.8.8 1.1.1.1 223.5.5.5 114.114.114.114; do
    echo "=== Resolver: $dns ==="
    dig @$dns app.example.com +short
done

# 验证 ECS 支持
dig @8.8.8.8 app.example.com +subnet=202.106.0.0/16    # 模拟北京
dig @8.8.8.8 app.example.com +subnet=183.60.0.0/16     # 模拟广州
dig @8.8.8.8 app.example.com +subnet=1.0.0.0/16        # 模拟新加坡

# 查看 TTL 变化(监控缓存行为)
watch -n 1 'dig @8.8.8.8 app.example.com +noall +answer'

# 追踪 DNS 解析链路
dig app.example.com +trace

# 检查 GSLB 返回的权重分布
for i in $(seq 1 100); do
    dig +short @8.8.8.8 app.example.com
done | sort | uniq -c | sort -rn
# 结果应接近配置的权重比例

7.2 Anycast 路径验证

# 验证 Anycast IP 路由到哪个 PoP
# 通过 HTTP 头判断
curl -s -H "Host: app.example.com" http://203.0.113.100/debug \
    | jq '{pop: .server_location, ip: .server_ip}'

# 通过 traceroute 观察路径
traceroute -n 203.0.113.100

# 多地区测试(使用在线工具或 RIPE Atlas)
# ripe-atlas measure --type traceroute --target 203.0.113.100 \
#     --from-area WW --probes 50

八、总结

  1. DNS-based GSLB 是大多数场景的起点。它简单、成熟、所有云厂商都支持。使用 Route 53、CloudFlare 或自建 PowerDNS,配合健康检查和自动故障转移,可以覆盖大部分跨地域调度需求。

  2. EDNS Client Subnet 是 GeoDNS 精度的关键。没有 ECS,使用公共 DNS(8.8.8.8、1.1.1.1)的用户可能被路由到错误的数据中心。确保你的权威 DNS 支持 ECS。

  3. Anycast 最适合 UDP 服务(DNS)和 CDN。对 TCP 服务慎用 Anycast——BGP 路由变化可能导致连接中断。QUIC 的 Connection ID 机制让 Anycast + TCP 成为可能。

  4. TTL 决定了故障切换速度的下限。60 秒 TTL 意味着最坏情况下 60 秒才能完成切换。对于高可用要求极高的服务,使用 30 秒 TTL + Anycast 做兜底。

  5. 多活架构的核心挑战不是调度,而是数据。GSLB 解决的是”流量去哪里”的问题,但”数据一致性”和”跨中心同步”才是多活架构的真正难题。


参考文献


上一篇:健康检查与故障转移:主动探测、被动检测与优雅处理

下一篇:负载均衡工程实践:会话保持、灰度发布与容量规划

同主题继续阅读

把当前热点继续串成多页阅读,而不是停在单篇消费。

2026-04-22 · network

网络工程索引

汇总本站网络工程系列文章,覆盖分层模型、以太网、IP、TCP、DNS、TLS、HTTP/2/3、CDN、BGP 与故障诊断。

2025-07-27 · network

【网络工程】DDoS 防御架构:容量型、协议型与应用层攻击

DDoS 攻击分为容量型、协议型和应用层三大类,防御策略截然不同。本文从攻击分类学出发,系统讲解 SYN Flood 的 SYN Cookie 防御、UDP 反射放大的 BGP Flowspec 清洗、HTTP Flood 的速率限制与行为分析,以及 Anycast 清洗中心的工作原理,构建从边缘到源站的多层 DDoS 防御体系。

2025-08-31 · network

【网络工程】HAProxy 工程:高级配置、ACL 与运维

系统讲解 HAProxy 的工程实践:Frontend/Backend/Listen 配置模型、ACL 规则引擎的高级用法、Runtime API 的动态管理能力、多线程模型与性能调优、SSL 终止与健康检查策略,建立 HAProxy 从配置到生产运维的完整体系。


By .