2017-10-18 39 views
0

Je suis en train de construire un robot qui prend une URL, en extrait des liens et en visite chacun à une certaine profondeur; faire un arbre de chemins sur un site spécifique.Quelle est la meilleure façon de gérer "trop ​​de fichiers ouverts"?

La façon dont je mis en œuvre le parallélisme pour ce crawler est que je visite chaque nouvelle URL trouvée dès qu'il a trouvé comme ceci:

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} 
    } 
} 

Cela fonctionne bien pour les sites relativement petits, mais quand je le lance sur une grand avec beaucoup de lien partout, je commence à obtenir une de ces deux erreurs sur certaines demandes: socket: too many open files et no such host (l'hôte est en effet là).

Quelle est la meilleure façon de gérer cela? Dois-je vérifier ces erreurs et suspendre l'exécution quand je les reçois pendant un certain temps jusqu'à ce que les autres demandes soient terminées? Ou spécifier un nombre maximum de demandes possibles à un certain moment? (ce qui est plus logique pour moi mais ne sait pas comment coder exactement)

+0

Vous êtes confronté à un problème lié à la limite des fichiers ouverts par utilisateur qui est contrôlée par le système d'exploitation. Si vous utilisez Linux/Unix, vous pouvez probablement augmenter la limite en utilisant la commande ulimit -n 4096. Cette commande a un seuil et elle ne peut pas définir le nombre de fichiers ouverts que vous voulez . Donc, si vous voulez pousser plus loin, vous devez modifier le fichier /etc/security/limits.conf et définir dur et une limite douce. –

+2

En outre, vous commencez un goroutine pour chaque lien que vous enconnerez et s'il y en a plusieurs à un certain point, cela va à l'encontre du but de goroutines et prend plus de temps à faire la tâche. Vous devriez essayer d'avoir un nombre fixe de goroutines pour faire le traitement et lire depuis un canal au lieu d'en commencer un nouveau pour chaque lien. Jetez un oeil à https://blog.golang.org/pipelines – Topo

+4

Ou peut-être un modèle comme: https://gobyexample.com/worker-pools? (BTW, votre utilisation 'WaitGroup' est assez étrange, ajoutez 1 pour chaque goroutine, et différer' Done' de chaque goroutine.Tout autre demande des bogues) – JimB

Répondre

0

Les fichiers auxquels il est fait référence dans l'erreur socket: too many open files incluent les threads et les sockets (les demandes http pour charger les pages Web en cours de récupération). Voir ceci question.

La requête DNS échoue également en raison de l'impossibilité de créer un fichier, mais l'erreur signalée est no such host.

Le problème peut être résolu de deux manières:

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

1) Est la solution la plus simple, mais peut-être pas idéal car il ne fait que repousser le problème jusqu'à ce que vous trouviez un site Web qui a plus de liens que la nouvelle limite. Pour Linux, l'utilisation peut définir cette limite avec ulimit -n.

2) Est plus un problème de conception. Nous devons limiter le nombre de requêtes http pouvant être effectuées simultanément. J'ai modifié le code un peu. Le changement le plus important est maxGoRoutines. À chaque appel de raclage lancé, une valeur est insérée dans le canal. Une fois que le canal est plein, l'appel suivant se bloque jusqu'à ce qu'une valeur soit retirée du canal. Une valeur est supprimée du canal chaque fois qu'un appel de raclage se termine.

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"} 
}