Golang中net.DefaultResolver.LookupIP搜索耗时过长问题探讨

Golang中net.DefaultResolver.LookupIP搜索耗时过长问题探讨 Golang 的 net.DefaultResolver.LookupIP 搜索条目耗时很长。

08:20:20.743156 IP 10.219.192.27.38061 > 10.235.128.5.53: 44798+ AAAA? perf-consist-server.perf-system.policy-perf.svc.cluster.local. (79)
08:20:20.744463 IP 10.235.128.5.53 > 10.219.192.27.38061: 44798 NXDomain*- 0/1/0 (172)
08:20:25.747656 IP 10.219.192.27.34446 > 10.235.128.5.53: 23043+ A? perf-consist-server.perf-system.policy-perf.svc.cluster.local. (79)
08:20:25.749002 IP 10.235.128.5.53 > 10.219.192.27.34446: 23043 NXDomain*- 0/1/0 (172)
08:20:25.749128 IP 10.219.192.27.40663 > 10.235.128.5.53: 33088+ AAAA? perf-consist-server.perf-system.svc.cluster.local. (67)
08:20:25.750310 IP 10.235.128.5.53 > 10.219.192.27.40663: 33088*- 0/1/0 (160)
08:20:30.753440 IP 10.219.192.27.50885 > 10.235.128.5.53: 17392+ A? perf-consist-server.perf-system.svc.cluster.local. (67)
08:20:30.754703 IP 10.235.128.5.53 > 10.219.192.27.50885: 17392*- 1/0/0 A 10.21.21.16 (132)

更多关于Golang中net.DefaultResolver.LookupIP搜索耗时过长问题探讨的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

你是否确认问题不在于你的DNS?像nslookup这样的命令需要多长时间?

更多关于Golang中net.DefaultResolver.LookupIP搜索耗时过长问题探讨的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


net.DefaultResolver.LookupIP 的耗时问题通常源于DNS搜索域(search domains)的逐级尝试机制。从你的抓包数据可以看出,DNS解析器依次尝试了以下域名:

  1. perf-consist-server.perf-system.policy-perf.svc.cluster.local. (AAAA记录)
  2. perf-consist-server.perf-system.policy-perf.svc.cluster.local. (A记录)
  3. perf-consist-server.perf-system.svc.cluster.local. (AAAA记录)
  4. perf-consist-server.perf-system.svc.cluster.local. (A记录) ← 最终成功

每次尝试间隔约5秒,总耗时约10秒。这是Go的默认DNS解析器在/etc/resolv.conf配置了多个搜索域时的标准行为。

解决方案

1. 使用自定义解析器并设置超时

package main

import (
    "context"
    "fmt"
    "net"
    "time"
)

func main() {
    // 创建自定义解析器
    resolver := &net.Resolver{
        PreferGo: true,
        Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
            d := net.Dialer{
                Timeout: 2 * time.Second, // 连接超时
            }
            return d.DialContext(ctx, network, "10.235.128.5:53")
        },
    }
    
    ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
    defer cancel()
    
    ips, err := resolver.LookupIP(ctx, "ip4", "perf-consist-server.perf-system.svc.cluster.local")
    if err != nil {
        fmt.Printf("Lookup error: %v\n", err)
        return
    }
    fmt.Printf("IPs: %v\n", ips)
}

2. 完全限定域名(FQDN)解析

package main

import (
    "context"
    "fmt"
    "net"
    "strings"
    "time"
)

func main() {
    hostname := "perf-consist-server.perf-system.svc.cluster.local."
    
    // 确保以点号结尾,避免搜索域追加
    if !strings.HasSuffix(hostname, ".") {
        hostname = hostname + "."
    }
    
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()
    
    ips, err := net.DefaultResolver.LookupIPAddr(ctx, hostname)
    if err != nil {
        fmt.Printf("Lookup error: %v\n", err)
        return
    }
    fmt.Printf("IPs: %v\n", ips)
}

3. 自定义DNS客户端控制搜索域

package main

import (
    "context"
    "fmt"
    "net"
    "time"
)

type CustomResolver struct {
    *net.Resolver
}

func NewCustomResolver(dnsServer string) *CustomResolver {
    return &CustomResolver{
        Resolver: &net.Resolver{
            PreferGo: true,
            Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
                d := net.Dialer{
                    Timeout: 2 * time.Second,
                }
                // 直接使用指定DNS服务器,忽略系统配置的搜索域
                return d.DialContext(ctx, network, dnsServer+":53")
            },
        },
    }
}

func (r *CustomResolver) LookupIPFast(ctx context.Context, host string) ([]net.IP, error) {
    // 直接查询,不尝试搜索域
    return r.LookupIP(ctx, "ip", host)
}

func main() {
    resolver := NewCustomResolver("10.235.128.5")
    
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()
    
    ips, err := resolver.LookupIPFast(ctx, "perf-consist-server.perf-system.svc.cluster.local")
    if err != nil {
        fmt.Printf("Lookup error: %v\n", err)
        return
    }
    fmt.Printf("IPs: %v\n", ips)
}

4. 使用并发查询加速

package main

import (
    "context"
    "fmt"
    "net"
    "sync"
    "time"
)

func concurrentLookup(host string) ([]net.IP, error) {
    ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
    defer cancel()
    
    var wg sync.WaitGroup
    var mu sync.Mutex
    var result []net.IP
    var err error
    
    // 并发查询A和AAAA记录
    recordTypes := []string{"ip4", "ip6"}
    
    for _, rtype := range recordTypes {
        wg.Add(1)
        go func(t string) {
            defer wg.Done()
            
            ips, lookupErr := net.DefaultResolver.LookupIP(ctx, t, host)
            if lookupErr == nil {
                mu.Lock()
                result = append(result, ips...)
                mu.Unlock()
            }
        }(rtype)
    }
    
    wg.Wait()
    
    if len(result) == 0 {
        return nil, fmt.Errorf("no IP found")
    }
    return result, nil
}

func main() {
    ips, err := concurrentLookup("perf-consist-server.perf-system.svc.cluster.local")
    if err != nil {
        fmt.Printf("Lookup error: %v\n", err)
        return
    }
    fmt.Printf("IPs: %v\n", ips)
}

关键点

  1. 搜索域机制:Go的默认解析器会尝试/etc/resolv.conf中的所有搜索域,每个域尝试A和AAAA记录
  2. 超时控制:使用context.WithTimeout限制总耗时
  3. FQDN使用:以点号结尾的域名会跳过搜索域
  4. 自定义解析器:通过net.Resolver可以完全控制DNS查询行为

从你的数据看,直接使用perf-consist-server.perf-system.svc.cluster.local.(注意末尾的点号)可以避免不必要的搜索尝试,将解析时间从10秒减少到毫秒级。

回到顶部