当前位置: 首页 > 工具软件 > gonet2 > 使用案例 >

go net.http 每次都查询dns的导致的问题

荀靖
2023-12-01

    在线上部署的一个程序,在某天突然在一天内出现多次 “dial tcp: lookup xxxx.com on 223.x.x.x:53: read udp 180.x.x.x:7792->223.x.x.x:53: i/o timeout” 的问题,导致线上告警触发了多次。后面查找问题,发现 go 每次发起 http 请求都会发起一个 dns 请求来进行域名解析,而我们服务器的dns服务器可能由于网络抖动问题,请求超时,最终导致请求失败触发了告警服务。

    然后在网上进行了发现在 github 上有别人写的一个 dns cache程序,该库github 地址。https://github.com/viki-org/dnscache/blob/master/dnscache.go

    下面对该程序进行代码解析

(1)存储的结构体。

type Resolver struct {

lock sync.RWMutex     // 使用sync的读写锁来进行控制

cache map[string][]net.IP  // 使用map 来进行存储 ,一个域名可以存储多个ip地址。

}

(2)新建一个 dns cache 对象

// 传入一个时延,表示ip的有效时间,过了这个时间则重新进行ip解析

func New(refreshRate time.Duration) *Resolver {

resolver := &Resolver {      // 对该结构体进行初始化,

cache: make(map[string][]net.IP, 64),

}

if refreshRate > 0 {

// 使用一个协程来进行更新ip

go resolver.autoRefresh(refreshRate)

}

return resolver

}

(3) Fetch(string) 函数

// 传入一个域名,返回ip数组

func (r *Resolver) Fetch(address string) ([]net.IP, error) {

r.lock.RLock()

ips, exists := r.cache[address] // 先查看缓存中是否有该域名的ip,

r.lock.RUnlock()

if exists { return ips, nil }

return r.Lookup(address)    // 进行域名的解析并存入到map数组中

}

(4)FetchOne(string) 获取一个域名对应的ip

func (r *Resolver) FetchOne(address string) (net.IP, error) {

// 调用 Fetch(string) 函数

ips, err := r.Fetch(address)

if err != nil || len(ips) == 0 { return nil, err}

return ips[0], nil

}

(5) FetchOneString(string) 返回一个 ip字符串

// 根据域名并返回一个 ip字符串

func (r *Resolver) FetchOneString(address string) (string, error) {

ip, err := r.FetchOne(address)

if err != nil || ip == nil { return "", err }

return ip.String(), nil

}

(6)Refresh() 刷新域名ip

unc (r *Resolver) Refresh() {

i := 0

r.lock.RLock()

addresses := make([]string, len(r.cache))

for key, _ := range r.cache {    // 把cache里的域名赋值到一个[]string中

addresses[i] = key

i++

}

r.lock.RUnlock()

// 为什么先赋值到一个中间[]string中呢?因为域名解析需要时间,如果直接操作 

// for key, _ := range r.cache {

// r.Lookup(key)

// 则会锁住 r.cache 很长时间,不利于并发操作

//}

for _, address := range addresses {

r.Lookup(address) // 循环重新解析cache中的域名

time.Sleep(time.Second * 2)

}

}

(7)Loopup(string) 重新解析域名

func (r *Resolver) Lookup(address string) ([]net.IP, error) {

// 先解析域名,再赋值给 cache,减少锁的粒度,提高并发

ips, err := net.LookupIP(address)

if err != nil { return nil, err }

r.lock.Lock()

r.cache[address] = ips

r.lock.Unlock()

return ips, nil

}

(8) autoRefresh(time.Duration) 定时刷新域名

func (r *Resolver) autoRefresh(rate time.Duration) {

for {

// 独立的协程来运行该函数,每隔 rate 时间进行一次域名的刷新

time.Sleep(rate)

r.Refresh()

}

}

    使用了 dnschache.go 封装的dns cache,在go 语言层上进行了cache缓存,避免每次请求都进行dns解析。而一般来说 linux 服务器本地的 dns服务器也会进行 dns 缓存,避免每次都去远程的 dns 服务器请求。这样在两个层面上都进行了 dns 缓存,从而减少了 dns 查询失败导致的请求失败的概率。

 类似资料: