【问题标题】:What's the best way to handle "too many open files"?处理“打开的文件太多”的最佳方法是什么?
【发布时间】:2017-10-18 20:25:54
【问题描述】:

我正在构建一个爬虫,它获取一个 URL,从中提取链接,然后将每个链接访问到一定深度;在特定站点上制作路径树。

我为这个爬虫实现并行的方式是,一旦找到每个新的 URL,我就会像这样访问它:

func main() {
    link := "https://example.com"

    wg := new(sync.WaitGroup)
    wg.Add(1)

    q := make(chan string)
    go deduplicate(q, wg)
    q <- link
    wg.Wait()
}

func deduplicate(ch chan string, wg *sync.WaitGroup) {
    for link := range ch {
        // seen is a global variable that holds all seen URLs
        if seen[link] {
            wg.Done()
            continue
        }
        seen[link] = true
        go crawl(link, ch, wg)
    }
}

func crawl(link string, q chan string, wg *sync.WaitGroup) {
    // handle the link and create a variable "links" containing the links found inside the page
    wg.Add(len(links))
    for _, l := range links {
        q <- l}
    }
}

这适用于相对较小的站点,但是当我在一个到处都有很多链接的大型站点上运行它时,我开始在某些请求中收到以下两个错误之一:socket: too many open filesno such host(主机是确实存在)。

处理此问题的最佳方法是什么?我是否应该检查这些错误并在收到它们一段时间后暂停执行,直到其他请求完成?或者在特定时间指定最大可能请求数? (这对我来说更有意义,但不确定如何准确编码)

【问题讨论】:

  • 您面临的问题与操作系统控制的每个用户打开文件的限制有关。如果您使用的是 Linux/Unix,您可能可以使用 ulimit -n 4096 命令增加限制。这个命令有一个阈值,它不能设置你想要的打开文件的数量。因此,如果您想进一步推动它,则需要修改 /etc/security/limits.conf 文件并设置硬限制和软限制。
  • 另外,你正在为你遇到的每个链接启动一个 goroutine,如果有的话,在某些时候它们中的许多会破坏 goroutines 的目的,并且实际上需要更长的时间来完成任务。您应该尝试使用固定数量的 goroutine 来进行处理并从通道读取,而不是为每个链接启动一个新的。看看blog.golang.org/pipelines
  • 或者可能是这样的模式:gobyexample.com/worker-pools? (顺便说一句,您的 WaitGroup 用法很奇怪。为每个 goroutine 添加 1,并在每个 goroutine 中延迟 Done。其他任何东西都在要求错误)
  • 处理它的最佳方法取决于您想做什么。这是一个设计决策,而不是技术问题。

标签: go concurrency parallel-processing


【解决方案1】:

错误socket: too many open files 中引用的文件包括线程和套接字(加载被抓取网页的http 请求)。 看到这个question

DNS 查询也很可能由于无法创建文件而失败,但是报告的错误是 no such host

问题可以通过两种方式解决:

1) Increase the maximum number of open file handles
2) Limit the maximum number of concurrent `crawl` calls

1) 是最简单的解决方案,但可能并不理想,因为它只会推迟问题,直到您找到一个链接超过新限制的网站。对于 linux 使用可以使用ulimit -n 设置此限制。

2) 更多的是设计问题。我们需要限制可以同时进行的 http 请求的数量。我稍微修改了代码。最重要的变化是 maxGoRoutines。每次开始的抓取调用都会将一个值插入到通道中。一旦通道已满,下一个调用将阻塞,直到从通道中删除一个值。每次抓取调用完成时,都会从通道中删除一个值。

package main

import (
    "fmt"
    "sync"
    "time"
)

func main() {
    link := "https://example.com"

    wg := new(sync.WaitGroup)
    wg.Add(1)

    q := make(chan string)
    go deduplicate(q, wg)
    q <- link
    fmt.Println("waiting")
    wg.Wait()
}

//This is the maximum number of concurrent scraping calls running
var MaxCount = 100
var maxGoRoutines = make(chan struct{}, MaxCount)

func deduplicate(ch chan string, wg *sync.WaitGroup) {
    seen := make(map[string]bool)
    for link := range ch {
        // seen is a global variable that holds all seen URLs
        if seen[link] {
            wg.Done()
            continue
        }
        seen[link] = true
        wg.Add(1)
        go crawl(link, ch, wg)
    }
}

func crawl(link string, q chan string, wg *sync.WaitGroup) {
    //This allows us to know when all the requests are done, so that we can quit
    defer wg.Done()

    links := doCrawl(link)

    for _, l := range links {
        q <- l
    }
}

func doCrawl(link string) []string {
    //This limits the maximum number of concurrent scraping requests
    maxGoRoutines <- struct{}{}
    defer func() { <-maxGoRoutines }()

    // handle the link and create a variable "links" containing the links found inside the page
    time.Sleep(time.Second)
    return []string{link + "a", link + "b"}
}

【讨论】:

    猜你喜欢
    • 2018-12-03
    • 2016-05-26
    • 1970-01-01
    • 2010-09-06
    • 2011-02-26
    • 2015-05-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多