我试图在go中重写一个网络爬虫(最初用gevent编写的python)。但是我已经碰壁了,无论我做什么,我都能获得快速的高内存消耗。例如,以下简单代码:
package main
import (
"bufio"
"fmt"
"os"
"net/http"
"io"
"strings"
"time"
)
func readLine(in *bufio.Reader, domains chan<- string) {
for conc := 0; conc < 500; conc++ {
input, err := in.ReadString('\n')
if err == io.EOF {
break
}
if err != nil {
fmt.Fprintf(os.Stderr, "read(stdin): %s\n", err)
os.Exit(1)
}
input = strings.TrimSpace(input)
if input == "" {
continue
}
domain := input
domains <- domain
}
}
func get(domains <-chan string) {
url := <-domains
URLresp, err := http.Get(url)
if err != nil {
fmt.Println(err)
}
if err == nil {
fmt.Println(url," OK")
URLresp.Body.Close()
}
}
func main() {
domains := make(chan string, 500)
inFile, _ := os.Open("F:\\PATH\\TO\\LIST_OF_URLS_SEPARATED_BY_NEWLINE.txt")
in := bufio.NewReader(inFile)
for {
go readLine(in, domains)
for i := 0; i < 500; i++ { go get(domains) }
time.Sleep(100000000)
}
}
我尝试过pprof,但它似乎只说我只使用50mb的堆空间,而资源监控的内存消耗却在暴涨。
我还尝试创建一个没有Keep Alive的自定义http传输,因为我发现net / http保存连接以供重用,但没有运气。
答案 0 :(得分:3)
让我们考虑您的代码有什么问题,重点关注您的main()
功能。
func main() {
domains := make(chan string, 500)
这很好。您创建一个缓冲通道来处理域列表输入。没问题。
inFile, _ := os.Open("F:\\PATH\\TO\\LIST_OF_URLS_SEPARATED_BY_NEWLINE.txt")
您打开输入文件。你永远不应该忽视错误,但我们暂时忽略它。
in := bufio.NewReader(inFile)
for {
在这里你开始一个无限循环。为什么呢?
go readLine(in, domains)
在这里,您可以阅读in
文件中接下来的500行,并将它们传递到domains
频道,但是您在后台执行此操作,这意味着下一行将在{readLine
之前执行1}}有机会完成。
for i := 0; i < 500; i++ { go get(domains) }
在这里,您可以并行呼叫get(domains)
500次。但如上所述,您在readLine
完成之前执行此操作,因此(至少在第一次通过外循环时),对get()
的大多数调用都将失败,因为domains
频道很可能是空的。 get()
函数无法正确处理此案例,但我会留下您的考虑。
time.Sleep(100000000)
然后在再次开始无限循环之前你睡了0.1秒。
}
}
无限循环将再次尝试再次在后台读取文件中的下500个项目。如果第一次调用readLine
需要超过0.1秒才能完成,那么您将同时尝试读取该文件的两个readLine
副本,这可能会导致恐慌。
假设这个行为正如您所期望的那样(虽然它肯定而且显然不是这样),在读完文件中的所有URL后,程序将永远继续,每0.1生成一个额外的501个例程。一次性例程尝试从文件中读取更多行,发现没有更多行,并立即退出。其他500个例程将永远等待从domains
频道读取不存在的结果。这是你的记忆&#34;泄漏&#34;。
答案 1 :(得分:-3)
问题是golang net Dial缺少默认超时。它会因为不让goroutines死亡而占用资源。以下作品:
c := &http.Client{
Transport: &http.Transport{
DisableKeepAlives: true,
Dial: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
TLSHandshakeTimeout: 10 * time.Second,
ResponseHeaderTimeout: 10 * time.Second,
ExpectContinueTimeout: 1 * time.Second,}}
URLresp, err := c.Get(url)