この記事には幾らか正しくない部分がありました。後で訂正していきますが、ひとまず shogo82148 さんの解説記事も確認下さい。
http.Client
はリクエスト毎に名前を引くので連続したアクセスはあまり速くない。
Goのhttp.Clientで名前解決結果cacheする楽な方法ないかな
— fujiwara (@fujiwara) December 7, 2016
Go 1.8 からは Resolver が提供されるので、自前で簡単に名前引きのキャッシュを実装出来る。
Go 1.9 だった様です。
Go 1.8 Release Notes - The Go Programming Language
DRAFT RELEASE NOTES - Introduction to Go 1.8 Go 1.8 is not yet released. These are work-in-progress ...
https://beta.golang.org/doc/go1.8#more_context
ただそれまで待てないという人には nett がオススメ。
GitHub - abursavich/nett: Package nett steals from the standard library's net package and provides a dialer with a pluggable host resolver.
Package nett steals from the standard library's net package and provides a dialer with a pluggable host resolver.
https://github.com/abursavich/nett
これを http.Client
の Dialer
として設定すれば、指定期間内の名前引きをキャッシュできる。
dialer := &nett.Dialer{
// Cache successful DNS lookups for five minutes
// using DefaultResolver to fill the cache.
Resolver: &nett.CacheResolver{TTL: 5 * time.Minute},
// Concurrently dial an IPv4 and an IPv6 address and
// return the connection that is established first.
IPFilter: nett.DualStack,
// Give up after ten seconds including DNS resolution.
Timeout: 10 * time.Second,
}
client := &http.Client{
Transport: &http.Transport{
// Use the Dialer.
Dial: dialer.Dial,
},
}
urls := []string{
"https://www.google.com/search?q=golang",
"https://www.google.com/search?q=godoc",
"https://www.google.com/search?q=golang-nuts",
}
for _, url := range urls {
resp, err := client.Get(url)
if err != nil {
panic(err)
}
io.Copy(ioutil.Discard, resp.Body)
resp.Body.Close()
}
※ README から引用
実際にどれくらいパフォーマンスが出るかベンチマークを取ってみました。
package main
import (
"io"
"io/ioutil"
"net/http"
"testing"
"time"
"github.com/abursavich/nett"
)
var (
urls = []string{
"https://www.google.com/search?q=golang",
"https://www.google.com/search?q=godoc",
"https://www.google.com/search?q=golang-nuts",
}
)
func BenchmarkNet(b *testing.B) {
b.ResetTimer()
client := http.DefaultClient
for _, url := range urls {
resp, err := client.Get(url)
if err != nil {
panic(err)
}
io.Copy(ioutil.Discard, resp.Body)
resp.Body.Close()
}
}
func BenchmarkNett(b *testing.B) {
b.ResetTimer()
dialer := &nett.Dialer{
Resolver: &nett.CacheResolver{TTL: 5 * time.Minute},
IPFilter: nett.DualStack,
Timeout: 10 * time.Second,
}
client := &http.Client{
Transport: &http.Transport{
Dial: dialer.Dial,
Proxy: http.ProxyFromEnvironment,
},
}
for _, url := range urls {
resp, err := client.Get(url)
if err != nil {
panic(err)
}
io.Copy(ioutil.Discard, resp.Body)
resp.Body.Close()
}
}
URL 3つしかアクセスしていませんが、如実に結果が出ています。(およそ2倍)
BenchmarkNet-4 1 1053105300 ns/op
BenchmarkNett-4 2 506050600 ns/op
PASS
ok _/C_/dev/go-sandbox/slowdns 3.268s
リクエスト毎に毎回設定していられないって人はデフォルトの http.DefaultTransport
を書き換えてしまえばシステム全体がこの恩恵を得られます。
http.DefaultTransport.(*http.Transport).Dialer = &nett.Dialer{
Resolver: &nett.CacheResolver{TTL: 5 * time.Minute},
IPFilter: nett.DualStack,
Timeout: 10 * time.Second,
}
追記
この後、他の環境でベンチマークを取り直してみましたが、結果が出ない事もある様です。
$ go test -count=3 -test.bench BenchmarkNetP
BenchmarkNetP-4 1 1116008300 ns/op
BenchmarkNetP-4 2 900440950 ns/op
BenchmarkNetP-4 2 890325100 ns/op
PASS
ok github.com/mattn/go-sandbox/nett 6.844s
$ go test -count=3 -test.bench BenchmarkNettP
BenchmarkNettP-4 2 952601050 ns/op
BenchmarkNettP-4 2 960558450 ns/op
BenchmarkNettP-4 1 1010136400 ns/op
PASS
ok github.com/mattn/go-sandbox/nett 6.925s