DNS负载均衡实战:我把电商大促的宕机时间从47分钟干到0的配置全记录

30秒速览

  • DNS轮询不靠谱必须配合健康检查
  • 地理路由能砍掉70%以上的跨国延迟
  • 权重分流是服务器迁移的神器
  • TTL设置要按业务需求精细调整
  • Anycast+DNS混合方案打游戏延迟减半
  • 完整的DNS监控要看三层指标

“DNS轮询就是垃圾”——这是我三年前的错误认知

2019年双十一,我在某跨境电商平台负责运维,当时天真地以为DNS轮询就能搞定流量高峰。结果大促开始47分钟后,主服务器直接宕机,连带整个CDN雪崩。事后分析才发现,简单的DNS轮询根本不知道后端服务器死活,所有请求还在往已经挂掉的节点上怼。

那次事故后我彻底重新认识了DNS负载均衡。现在给物流公司WMS系统做架构升级时,我用了完全不同的方案。以下是真实配置代码(基于AWS Route 53):

# 健康检查配置 - 每30秒检测一次HTTP 200状态
resource "aws_route53_health_check" "api_healthcheck" {
  fqdn              = "api.example.com"
  port              = 443
  type              = "HTTPS"
  resource_path     = "/health"
  failure_threshold = 3  # 连续3次失败才标记为不健康
  request_interval  = 30 # 30秒间隔
  
  # 必须设置SNI否则TLS握手会失败
  enable_sni        = true
  regions           = ["us-west-1", "eu-west-1"]
}

这个配置的关键在于failure_thresholdrequest_interval的平衡。太敏感会导致误判(比如网络抖动),太迟钝又失去意义。经过压测,3次失败+30秒间隔的组合在假阳性和响应速度之间取得了最佳平衡。

地理路由让日本用户请求不再绕道新加坡

物流公司的日本分部一直抱怨系统延迟高,原来DNS默认把请求都解析到新加坡机房。用GeoDNS改造后,延迟直接从380ms降到92ms。Route 53的配置比想象中简单:

# 地理路由策略 - 日本用户定向到东京机房
resource "aws_route53_record" "jp_record" {
  zone_id = aws_route53_zone.main.zone_id
  name    = "api.example.com"
  type    = "A"
  
  # 日本地区专属设置
  geolocation_routing_policy {
    country = "JP"
  }
  set_identifier = "tokyo"
  alias {
    name                   = "alb-jp-tokyo-123456789.ap-northeast-1.elb.amazonaws.com"
    zone_id                = "Z14GRHDCWA56QT"
    evaluate_target_health = true
  }
}

踩坑警告:千万别用CIDR块做地理匹配!我最初尝试用IP段匹配,结果AWS的IP库更新不及时,导致部分用户被错误路由。直接使用国家代码反而更可靠。

权重分流让我优雅应对服务器升级

上周三凌晨做服务器迁移时,我用权重分配实现了无缝过渡。旧集群权重从100逐步降到0,新集群从0升到100,整个过程零停机。监控显示请求成功率始终保持在99.99%以上:

时间 旧集群权重 新集群权重 错误率
00:00 100 0 0.01%
00:30 75 25 0.008%
01:00 50 50 0.005%
02:00 0 100 0.003%

对应的Terraform配置片段:

# 权重分流配置 - 新旧集群过渡
resource "aws_route53_record" "weighted_record" {
  zone_id = aws_route53_zone.main.zone_id
  name    = "api.example.com"
  type    = "A"
  ttl     = 60  # 故意设短TTL方便快速切换
  
  weighted_routing_policy {
    weight = var.cluster_weight # 通过变量动态控制
  }
  set_identifier = "legacy-cluster"
  records        = ["10.0.1.1"]
}

TTL设置是个技术活——30秒与300秒的天壤之别

DNS缓存是把双刃剑。给视频直播平台做优化时,我把TTL从默认的300秒改成30秒,故障转移时间缩短了87%。但代价是DNS查询量暴涨5倍,差点被Cloudflare收费升级。

最终方案是按业务类型区分TTL:

  • 支付API:30秒(快速故障转移)
  • 商品图片CDN:3600秒(减少查询开销)
  • 用户会话服务:300秒(平衡点)

实测发现Python的DNS缓存会无视TTL,必须强制刷新:

import dns.resolver

def resolve_with_ttl(domain):
    """强制遵守TTL的DNS查询"""
    resolver = dns.resolver.Resolver()
    resolver.cache = dns.resolver.LRUCache()  # 使用LRU缓存而非默认缓存
    answer = resolver.resolve(domain)
    return answer.rrset[0].address

当DNS遇上Anycast:给全球游戏服务器加速的邪道玩法

给某FPS游戏做全球部署时,传统DNS方案在玩家匹配场景下表现糟糕。最终我用Anycast+DNS的混合方案,将亚洲玩家的平均延迟从142ms压到64ms。核心配置如下:

# Anycast BGP通告配置
router bgp 65530
 no synchronization
 bgp log-neighbor-changes
 network 203.0.113.0/24
 neighbor 192.0.2.1 remote-as 64512
 neighbor 192.0.2.1 route-map ANYCAST_OUT out
!
route-map ANYCAST_OUT permit 10
 set community 64512:666
 set local-preference 200

配合DNS的延迟路由策略:

# 延迟路由配置
resource "aws_route53_record" "latency_record" {
  zone_id = aws_route53_zone.main.zone_id
  name    = "game.example.com"
  type    = "A"
  
  latency_routing_policy {
    region = "ap-southeast-1"
  }
  set_identifier = "singapore"
  alias {
    name    = "anycast-sgp-123.elb.amazonaws.com"
    zone_id = "Z35SXDOTRQ7X7K"
  }
}

这个方案最骚的地方在于:当Anycast节点过载时,DNS会自动把流量切到其他区域,形成双重负载均衡。不过调试BGP的时候我差点把东京机房搞下线三次,不建议网络小白尝试。

监控DNS?99%的人只做了半吊子检测

别以为ping个域名就叫监控了!我在Kubernetes集群里部署的完整监控方案包括:

# Prometheus黑盒监控配置
- module: dns
  prober: dns
  dns:
    preferred_ip_protocol: "ip4"
    query_name: "api.example.com"
    query_type: "A"
    valid_rcodes:
      - NOERROR
    validate_answer_rrs:
      fail_if_none_matches_regexp:
        - "10.0..*"  # 必须返回内网IP段
    validate_authority_rrs:
      fail_if_not_matches_regexp:
        - "ns-d+.awsdns-d+.org"  # 必须使用AWS权威DNS

配合Grafana看板监控这些关键指标:

  • DNS解析延迟百分位(P99>200ms就报警)
  • TTL违反率(客户端缓存时间超过设定TTL的比例)
  • 地理路由错误率(日本用户被解析到美国节点的比例)

去年圣诞节大促时,这个监控系统提前15分钟发现了DNS查询量异常增长,及时扩容DNS服务器避免了一场灾难。

DNS轮询的致命缺陷解剖

那次双十一事故后,我用WireShark抓包分析才发现,传统DNS轮询至少有三大致命伤:

# 这是当时灾难性的DNS配置示例
www.example.com.  300 IN  A   192.0.2.1
www.example.com.  300 IN  A   192.0.2.2
www.example.com.  300 IN  A   192.0.2.3

第一是TTL(300秒)导致切换延迟。当192.0.2.1服务器挂掉时,由于DNS缓存,仍有数百万客户端在持续向这个IP发送请求。更可怕的是第二点——DNS服务器完全不知道后端服务器的健康状态,即便配置了监控告警,从发现问题到人工干预至少需要5分钟。

血泪教训:客户端缓存雪崩

最致命的是第三点:某些运营商DNS会私自延长TTL。我们后来抓包发现某省移动DNS竟然将300秒TTL擅自改为3600秒,导致该省用户持续1小时访问故障节点。这解释了为什么当时监控图上会出现区域性雪崩——某个地区的用户集体失效后,他们的重试请求又集中压垮了其他节点。

现代DNS负载均衡方案演进

现在的方案采用三层防御体系:

  1. 智能DNS解析:基于GeoDNS的位置路由
  2. 健康检查熔断:30秒间隔的HTTP健康检查
  3. 流量染色:通过EDNS Client Subnet传递客户端真实IP

物流公司WMS实战案例

在华东仓库上线新系统时,我们配置了这样的健康检查策略:

health_check {
    type = "https"
    port = 443
    path = "/api/health"
    interval = 30s
    timeout = 5s
    unhealthy_threshold = 2
    healthy_threshold = 3
    expected_codes = [200, 302]
}

配合Consul实现服务发现后,任何节点异常都能在60秒内自动从DNS记录中剔除。这套方案在618大促期间实现了100%可用性。

TCP协议层的优化技巧

除了DNS层面,传输层也有大文章可做:

  • SYN Cookie防护:在Linux内核参数中启用net.ipv4.tcp_syncookies = 1
  • 连接追踪优化:调整nf_conntrack_tcp_timeout_established为1200秒
  • 端口复用:设置net.ipv4.tcp_tw_reuse = 1缓解TIME_WAIT状态

实测表明,仅TCP优化就使单节点QPS从12k提升到18k。这是我们在测试环境用wrk压测的数据:

# 优化前
Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    85.12ms   26.33ms 342.56ms   85.32%
    Req/Sec     1.20k   181.23     1.45k    86.33%

# 优化后
Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    53.67ms   18.92ms 298.43ms   89.01%
    Req/Sec     1.81k   202.15     2.12k    91.07%

混合云场景下的特殊处理

当系统跨AWS和阿里云部署时,我们发现DNS解析会出现跨云延迟。最终解决方案是:

  1. 在AWS Route53和阿里云DNS分别配置智能解析
  2. 通过Cloudflare Argo Tunnel建立私有通道
  3. 使用Envoy做全局负载均衡

这是Envoy的关键配置片段:

clusters:
- name: hybrid_cluster
  connect_timeout: 1s
  type: LOGICAL_DNS
  dns_lookup_family: V4_ONLY
  load_assignment:
    cluster_name: hybrid_cluster
    endpoints:
    - lb_endpoints:
      - endpoint:
          address:
            socket_address:
              address: api.aws.example.com
              port_value: 443
      - endpoint:
          address:
            socket_address:
              address: api.aliyun.example.com
              port_value: 443

第三章:DNS健康检查的魔鬼细节

说出来你可能不信,我们最初用AWS Route53做健康检查时,连续三天凌晨触发误报警。监控系统显示所有节点集体下线,但实际服务器活得好好的——问题出在TCP检查端口用了8080,而我们的安全组配置有个愚蠢的时间段策略:凌晨3点自动关闭非标准端口。

// 错误配置示例(千万别学):
{
  "HealthCheckConfig": {
    "Type": "TCP",
    "Port": 8080,
    "RequestInterval": 30,
    "FailureThreshold": 3
  },
  "SecurityGroupRules": [
    {
      "PortRange": "80",
      "TimePolicy": "AlwaysOn"
    },
    {
      "PortRange": "8080", 
      "TimePolicy": "WorkingHours" // 就是这个坑货!
    }
  ]
}

3.1 HTTP检查的报文玄学

后来我们改用HTTP检查,又栽进新的坑。某次部署后健康检查突然大面积失败,查了半小时才发现Nginx配置里多了个add_header X-Frame-Options DENY;——Route53的健康检查请求居然会被这个安全头拦截!更离谱的是不同DNS服务商对HTTP检查的实现天差地别:

  • 阿里云云解析:默认不带Host头,必须手动配置
  • Google Cloud DNS:遇到30x跳转直接判定失败
  • Azure Traffic Manager:对HTTP/2支持时好时坏

第四章:混合云场景下的DNS骚操作

去年给某车企做车联网升级时遇到个经典问题:他们既有阿里云上的AI训练集群,又有本地数据中心的车辆通信服务器。当我们在DNS里同时配置A记录和AAAA记录时,某些安卓车机的TCP栈会优先尝试IPv6,但他们的内网IPv6隧道根本没调通…

血泪教训: 混合云环境一定要用dig +short example.com ANY看全量解析结果,我们后来专门写了个检测脚本:
def check_dns_mixed_environment(domain):
    import dns.resolver
    answers = dns.resolver.resolve(domain, 'ANY')
    has_ipv4 = any(r.rdtype == 1 for r in answers)
    has_ipv6 = any(r.rdtype == 28 for r in answers)
    
    if has_ipv4 and has_ipv6:
        print("⚠️ 双栈环境需检查客户端IPv6可达性")
        if "aws" in domain:
            print("建议启用Route53的'Evaluate Target Health'功能")
    elif has_ipv4:
        print("✅ 纯IPv4环境")
    else:
        print(" 纯IPv6环境需特殊处理")

4.1 地理位置路由的坑

你以为按国家分流很简单?我们给东南亚业务配置GeoDNS时,发现新加坡用户总被路由到美国节点。抓包才发现当地ISP的DNS出口IP全被识别为美国——后来不得不手动维护一个IP库覆盖规则:

ISP名称 错误识别国家 实际覆盖区域 修正方案
Singtel Mobile US 新加坡/马来西亚 ASN覆盖
Telkomsel GB 印尼 EDNS Client Subnet

第五章:客户端侧的隐藏战争

做过移动端优化的都知道,DNS缓存是个薛定谔的存在。我们实测发现:

  • iOS在WiFi切换蜂窝数据时必定刷新DNS
  • 某些MIUI版本会无视TTL强制缓存24小时
  • Chrome浏览器对失败DNS记录有特殊缓存逻辑

最坑的是某次故障复盘时,发现用户投诉的”无法访问”居然是因为他们的企业路由器把我们的DNS响应包中的TC(Truncated)位错误置1,导致客户端放弃请求——这种问题你让服务端怎么防?

发表评论