Quel est le moyen le plus rapide d'envoyer 100 000 requêtes HTTP en Python?

287

J'ouvre un fichier contenant 100 000 URL. Je dois envoyer une demande HTTP à chaque URL et imprimer le code d'état. J'utilise Python 2.6, et jusqu'à présent, j'ai examiné les nombreuses façons confuses que Python implémente le threading / simultanéité. J'ai même regardé la bibliothèque de concours python , mais je ne sais pas comment écrire correctement ce programme. Quelqu'un a-t-il rencontré un problème similaire? Je suppose qu'en général, j'ai besoin de savoir comment effectuer des milliers de tâches en Python aussi rapidement que possible - je suppose que cela signifie «simultanément».

IgorGanapolsky
la source
47
Assurez-vous que vous ne faites que la demande HEAD (afin de ne pas télécharger le document entier). Voir: stackoverflow.com/questions/107405/…
Tarnay Kálmán
5
Excellent point, Kalmi. Si tout ce que Igor veut, c'est le statut de la demande, ces 100 000 requêtes iront beaucoup, beaucoup, beaucoup plus rapidement. Beaucoup plus vite.
Adam Crossland
1
Vous n'avez pas besoin de fils pour cela; le moyen le plus efficace est d'utiliser une bibliothèque asynchrone comme Twisted.
jemfinch
3
voici des exemples de code basés sur gevent, twisted et asyncio (testés sur 1000000 requêtes)
jfs
4
@ TarnayKálmán il est possible requests.getet requests.head(c'est-à-dire une demande de page vs une demande de tête) de renvoyer différents codes d'état, donc ce n'est pas le meilleur conseil
AlexG

Réponses:

200

Solution sans torsion:

from urlparse import urlparse
from threading import Thread
import httplib, sys
from Queue import Queue

concurrent = 200

def doWork():
    while True:
        url = q.get()
        status, url = getStatus(url)
        doSomethingWithResult(status, url)
        q.task_done()

def getStatus(ourl):
    try:
        url = urlparse(ourl)
        conn = httplib.HTTPConnection(url.netloc)   
        conn.request("HEAD", url.path)
        res = conn.getresponse()
        return res.status, ourl
    except:
        return "error", ourl

def doSomethingWithResult(status, url):
    print status, url

q = Queue(concurrent * 2)
for i in range(concurrent):
    t = Thread(target=doWork)
    t.daemon = True
    t.start()
try:
    for url in open('urllist.txt'):
        q.put(url.strip())
    q.join()
except KeyboardInterrupt:
    sys.exit(1)

Celui-ci est légèrement plus rapide que la solution torsadée et utilise moins de CPU.

Tarnay Kálmán
la source
10
@ Kalmi, pourquoi définissez-vous Queue concurrent*2?
Marcel Wilson
8
N'oubliez pas de fermer la connexion conn.close() . L'ouverture d'un trop grand nombre de connexions http peut interrompre votre script à un moment donné et manger de la mémoire.
Aamir Adnan du
4
@hyh, le Queuemodule a été renommé queueen Python 3. Il s'agit du code Python 2.
Tarnay Kálmán
3
À quelle vitesse pouvez-vous aller si vous voulez à chaque fois parler avec le même serveur en conservant la connexion? Cela peut-il même être fait sur plusieurs threads, ou avec une connexion persistante par thread?
mdurant du
2
@mptevsion, si vous utilisez CPython, vous pouvez (par exemple) simplement remplacer "print status, url" par "my_global_list.append ((status, url))". (La plupart des opérations sur) les listes sont implicitement thread-safe en CPython (et certaines autres implémentations de python) en raison du GIL, donc c'est sûr à faire.
Tarnay Kálmán
54

Une solution utilisant la bibliothèque de mise en réseau asynchrone tornado

from tornado import ioloop, httpclient

i = 0

def handle_request(response):
    print(response.code)
    global i
    i -= 1
    if i == 0:
        ioloop.IOLoop.instance().stop()

http_client = httpclient.AsyncHTTPClient()
for url in open('urls.txt'):
    i += 1
    http_client.fetch(url.strip(), handle_request, method='HEAD')
ioloop.IOLoop.instance().start()
mher
la source
7
Ce code utilise des E / S réseau non bloquantes et n'a aucune restriction. Il peut évoluer vers des dizaines de milliers de connexions ouvertes. Il fonctionnera dans un seul thread mais sera plus rapide que n'importe quelle solution de thread. Commander les E / S non bloquantes en.wikipedia.org/wiki/Asynchronous_I/O
mher
1
Pouvez-vous expliquer ce qui se passe ici avec la variable globale i? Une sorte de vérification d'erreur?
LittleBobbyTables
4
C'est un compteur pour déterminer quand quitter le `` ioloop '' - donc quand vous avez terminé.
Michael Dorner
1
@AndrewScottEvans, il a supposé que vous utilisez python 2.7 et des proxys
Dejell
5
@Guy Avraham Bonne chance pour obtenir de l'aide sur votre plan ddos.
Walter
51

Les choses ont beaucoup changé depuis 2010, lorsque cela a été publié et je n'ai pas essayé toutes les autres réponses, mais j'en ai essayé quelques-unes, et j'ai trouvé que cela fonctionnait le mieux pour moi en utilisant python3.6.

J'ai pu récupérer environ 150 domaines uniques par seconde sur AWS.

import pandas as pd
import concurrent.futures
import requests
import time

out = []
CONNECTIONS = 100
TIMEOUT = 5

tlds = open('../data/sample_1k.txt').read().splitlines()
urls = ['http://{}'.format(x) for x in tlds[1:]]

def load_url(url, timeout):
    ans = requests.head(url, timeout=timeout)
    return ans.status_code

with concurrent.futures.ThreadPoolExecutor(max_workers=CONNECTIONS) as executor:
    future_to_url = (executor.submit(load_url, url, TIMEOUT) for url in urls)
    time1 = time.time()
    for future in concurrent.futures.as_completed(future_to_url):
        try:
            data = future.result()
        except Exception as exc:
            data = str(type(exc))
        finally:
            out.append(data)

            print(str(len(out)),end="\r")

    time2 = time.time()

print(f'Took {time2-time1:.2f} s')
print(pd.Series(out).value_counts())
Glen Thompson
la source
1
Je demande seulement parce que je ne sais pas, mais est-ce que ces trucs futurs pourraient être remplacés par async / wait?
TankorSmash
1
C'est possible, mais j'ai trouvé que ce qui précède fonctionnait mieux. vous pouvez utiliser aiohttp mais cela ne fait pas partie de la bibliothèque standard et change beaucoup. Cela fonctionne, mais je ne l'ai pas trouvé aussi. J'obtiens des taux d'erreur plus élevés lorsque je l'utilise et pour la vie de moi, je ne peux pas le faire fonctionner aussi bien que les futurs simultanés bien qu'en théorie il semble que cela devrait mieux fonctionner, voir: stackoverflow.com/questions/45800857/… si vous réussissez, veuillez poster votre réponse afin que je puisse la tester.
Glen Thompson
1
C'est un nitpick, mais je pense que c'est beaucoup plus propre à mettre time1 = time.time()en haut de la boucle for et time2 = time.time()juste après la boucle for.
Matt M.
J'ai testé votre extrait de code, il s'exécute en quelque sorte deux fois. Est-ce que je fais quelque chose de mal? Ou est-il censé fonctionner deux fois? Si c'est le dernier cas, pouvez-vous également m'aider à comprendre comment cela se déclenche deux fois?
Ronnie
1
Il ne devrait pas fonctionner deux fois. Je ne sais pas pourquoi vous voyez ça.
Glen Thompson
40

Les discussions ne sont absolument pas la réponse ici. Ils fourniront à la fois des goulots d'étranglement de processus et de noyau, ainsi que des limites de débit qui ne sont pas acceptables si l'objectif global est "le moyen le plus rapide".

Un peu de twistedet son HTTPclient asynchrone vous donneraient de bien meilleurs résultats.

ironfroggy
la source
ironfroggy: Je penche vers vos sentiments. J'ai essayé d'implémenter ma solution avec des threads et des files d'attente (pour les mutex automatiques), mais pouvez-vous imaginer combien de temps il faut pour remplir une file d'attente avec 100 000 choses ?? Je joue toujours avec différentes options et suggestions de tout le monde sur ce fil, et peut-être que Twisted sera une bonne solution.
IgorGanapolsky
2
Vous pouvez éviter de remplir une file d'attente avec 100 000 objets. Il vous suffit de traiter les éléments un par un à partir de votre saisie, puis de lancer un thread pour traiter la demande correspondant à chaque élément. (Comme je le décris ci-dessous, utilisez un thread de lancement pour démarrer les threads de requête HTTP lorsque votre nombre de threads est inférieur à un certain seuil. Faites en sorte que les threads écrivent les résultats dans une URL de mappage de dict à la réponse, ou ajoutez des tuples à une liste.)
Erik Garrison
ironfroggy: De plus, je suis curieux de savoir quels goulots d'étranglement vous avez trouvés en utilisant des threads Python? Et comment les threads Python interagissent-ils avec le noyau du système d'exploitation?
Erik Garrison
Assurez-vous d'installer le réacteur epoll; sinon vous utiliserez select / poll, et ce sera très lent. De plus, si vous voulez réellement essayer d'ouvrir 100 000 connexions simultanément (en supposant que votre programme est écrit de cette façon et que les URL se trouvent sur des serveurs différents), vous devrez régler votre système d'exploitation pour ne pas manquer. de descripteurs de fichiers, de ports éphémères, etc. (il est probablement plus facile de s'assurer que vous n'avez pas plus de, disons, 10 000 connexions en attente à la fois).
Mark Nottingham
erikg: vous avez recommandé une excellente idée. Cependant, le meilleur résultat que j'ai pu obtenir avec 200 fils était d'env. 6 minutes. Je suis sûr qu'il existe des moyens d'accomplir cela en moins de temps ... Mark N: si Twisted est la voie que je décide d'aller, le réacteur epoll est sûrement utile. Cependant, si mon script doit être exécuté à partir de plusieurs machines, cela ne nécessiterait-il pas l'installation de Twisted sur CHAQUE machine? Je ne sais pas si je peux convaincre mon patron de suivre cette voie ...
IgorGanapolsky
21

Je sais que c'est une vieille question, mais en Python 3.7 vous pouvez le faire en utilisant asyncioet aiohttp.

import asyncio
import aiohttp
from aiohttp import ClientSession, ClientConnectorError

async def fetch_html(url: str, session: ClientSession, **kwargs) -> tuple:
    try:
        resp = await session.request(method="GET", url=url, **kwargs)
    except ClientConnectorError:
        return (url, 404)
    return (url, resp.status)

async def make_requests(urls: set, **kwargs) -> None:
    async with ClientSession() as session:
        tasks = []
        for url in urls:
            tasks.append(
                fetch_html(url=url, session=session, **kwargs)
            )
        results = await asyncio.gather(*tasks)

    for result in results:
        print(f'{result[1]} - {str(result[0])}')

if __name__ == "__main__":
    import pathlib
    import sys

    assert sys.version_info >= (3, 7), "Script requires Python 3.7+."
    here = pathlib.Path(__file__).parent

    with open(here.joinpath("urls.txt")) as infile:
        urls = set(map(str.strip, infile))

    asyncio.run(make_requests(urls=urls))

Vous pouvez en savoir plus à ce sujet et voir un exemple ici .

Marius Stănescu
la source
Est-ce similaire à C # async / wait et Kotlin Coroutines?
IgorGanapolsky
@IgorGanapolsky, oui, c'est très similaire à C # async / wait. Je ne connais pas Kotlin Coroutines.
Marius Stănescu
@sandyp, je ne sais pas si cela fonctionne, mais si vous voulez essayer, vous devrez utiliser UnixConnector pour aiohttp. En savoir plus ici: docs.aiohttp.org/en/stable/client_reference.html#connectors .
Marius Stănescu
Merci @ MariusStănescu. C'est exactement ce que j'ai utilisé.
sandyp
+1 pour afficher asyncio.gather (* tâches). voici un tel extrait que j'ai utilisé: urls= [fetch(construct_fetch_url(u),idx) for idx, u in enumerate(some_URI_list)] results = await asyncio.gather(*urls)
Ashwini Kumar
19

Utilisez les grequests , c'est une combinaison de requêtes + module Gevent.

GRequests vous permet d'utiliser des requêtes avec Gevent pour créer facilement des requêtes HTTP asynchrones.

L'utilisation est simple:

import grequests

urls = [
   'http://www.heroku.com',
   'http://tablib.org',
   'http://httpbin.org',
   'http://python-requests.org',
   'http://kennethreitz.com'
]

Créez un ensemble de demandes non envoyées:

>>> rs = (grequests.get(u) for u in urls)

Envoyez-les tous en même temps:

>>> grequests.map(rs)
[<Response [200]>, <Response [200]>, <Response [200]>, <Response [200]>, <Response [200]>]
Akshay Pratap Singh
la source
7
gevent prend désormais en charge python 3
Benjamin Toueg
15
les grequests ne font pas partie des demandes normales et semblent en grande partie non entretenues
Thom
8

Une bonne approche pour résoudre ce problème consiste à écrire d'abord le code requis pour obtenir un résultat, puis à incorporer du code de thread pour paralléliser l'application.

Dans un monde parfait, cela signifierait simplement démarrer simultanément 100 000 threads qui produisent leurs résultats dans un dictionnaire ou une liste pour un traitement ultérieur, mais en pratique, vous êtes limité dans le nombre de requêtes HTTP parallèles que vous pouvez émettre de cette manière. Localement, vous avez des limites dans le nombre de sockets que vous pouvez ouvrir simultanément, le nombre de threads d'exécution autorisés par votre interpréteur Python. À distance, le nombre de connexions simultanées peut être limité si toutes les demandes concernent un ou plusieurs serveurs. Ces limitations nécessiteront probablement que vous écriviez le script de manière à interroger uniquement une petite fraction des URL à la fois (100, comme une autre affiche l'a mentionné, est probablement une taille de pool de threads décente, bien que vous puissiez constater que vous peut déployer avec succès beaucoup plus).

Vous pouvez suivre ce modèle de conception pour résoudre le problème ci-dessus:

  1. Démarrez un thread qui lance de nouveaux threads de demande jusqu'à ce que le nombre de threads en cours d'exécution (vous pouvez les suivre via threading.active_count () ou en poussant les objets de thread dans une structure de données) est> = votre nombre maximal de requêtes simultanées (par exemple 100) , puis s'endort brièvement. Ce fil devrait se terminer lorsqu'il n'y a plus d'URL à traiter. Ainsi, le thread continuera à se réveiller, à lancer de nouveaux threads et à dormir jusqu'à ce que vous ayez terminé.
  2. Demandez aux threads de demande de stocker leurs résultats dans une certaine structure de données pour une récupération et une sortie ultérieures. Si la structure dans laquelle vous stockez les résultats est un listou dictdans CPython, vous pouvez ajouter ou insérer en toute sécurité des éléments uniques de vos threads sans verrous , mais si vous écrivez dans un fichier ou si vous avez besoin d'une interaction de données cross-thread plus complexe, vous devez utiliser un verrouillage d'exclusion mutuelle pour protéger cet état de la corruption .

Je vous suggère d'utiliser le module de threading . Vous pouvez l'utiliser pour lancer et suivre les threads en cours d'exécution. Le support de threading de Python est nu, mais la description de votre problème suggère qu'il est complètement suffisant pour vos besoins.

Enfin, si vous souhaitez voir une application assez simple d'une application de réseau parallèle écrit en Python, consultez ssh.py . Il s'agit d'une petite bibliothèque qui utilise le threading Python pour paralléliser de nombreuses connexions SSH. La conception est suffisamment proche de vos besoins pour que vous puissiez la trouver comme une bonne ressource.

Erik Garrison
la source
1
erikg: serait-il raisonnable de lancer une file d'attente dans votre équation (pour le verrouillage d'exclusion mutuelle)? Je soupçonne que GIL de Python n'est pas conçu pour jouer avec des milliers de threads.
IgorGanapolsky
Pourquoi avez-vous besoin d'un verrouillage d'exclusion mutuelle pour empêcher la génération d'un trop grand nombre de threads? Je soupçonne que je comprends mal le terme. Vous pouvez suivre les threads en cours d'exécution dans une file d'attente de threads, les supprimer lorsqu'ils se terminent et en ajouter davantage jusqu'à ladite limite de threads. Mais dans un cas simple tel que celui en question, vous pouvez également simplement regarder le nombre de threads actifs dans le processus Python actuel, attendre qu'il tombe en dessous d'un seuil et lancer plus de threads jusqu'au seuil comme décrit. Je suppose que vous pourriez considérer cela comme un verrou implicite, mais aucun verrou explicite n'est requis afaik.
Erik Garrison
erikg: plusieurs threads ne partagent-ils pas l'état? À la page 305 du livre d'O'Reilly "Python pour l'administration système Unix et Linux", il est indiqué: "... l'utilisation du filetage sans files d'attente rend la tâche plus complexe que ce que beaucoup de gens peuvent gérer de façon réaliste. C'est une bien meilleure idée de toujours utiliser la mise en file d'attente si vous trouvez que vous avez besoin d'utiliser des threads. Pourquoi? Parce que le module de file d'attente réduit également la nécessité de protéger explicitement les données avec des mutex car la file d'attente elle-même est déjà protégée en interne par un mutex. " Encore une fois, je salue votre point de vue à ce sujet.
IgorGanapolsky
Igor: Vous avez absolument raison d'utiliser un verrou. J'ai édité le message pour refléter cela. Cela dit, l'expérience pratique avec python suggère que vous n'avez pas besoin de verrouiller les structures de données que vous modifiez atomiquement à partir de vos threads, comme par list.append ou par l'ajout d'une clé de hachage. La raison, je crois, est le GIL, qui fournit des opérations telles que list.append avec un certain degré d'atomicité. J'exécute actuellement un test pour vérifier cela (utilisez des threads 10k pour ajouter les numéros 0-9999 à une liste, vérifiez que tous les ajouts ont fonctionné). Après près de 100 itérations, le test n'a pas échoué.
Erik Garrison
Igor: On me pose une autre question sur ce sujet: stackoverflow.com/questions/2740435/…
Erik Garrison
7

Si vous cherchez à obtenir les meilleures performances possibles, vous pouvez envisager d'utiliser des E / S asynchrones plutôt que des threads. La surcharge associée à des milliers de threads du système d'exploitation n'est pas anodine et le changement de contexte dans l'interpréteur Python en ajoute encore plus. Le filetage fera certainement l'affaire, mais je soupçonne qu'un itinéraire asynchrone fournira de meilleures performances globales.

Plus précisément, je suggère le client Web asynchrone dans la bibliothèque Twisted ( http://www.twistedmatrix.com ). Il a certes une courbe d'apprentissage abrupte mais il est assez facile à utiliser une fois que vous maîtrisez le style de programmation asynchrone de Twisted.

Un HowTo sur l'API client Web asynchrone de Twisted est disponible à l'adresse suivante:

http://twistedmatrix.com/documents/current/web/howto/client.html

Rakis
la source
Rakis: J'étudie actuellement les E / S asynchrones et non bloquantes. J'ai besoin de mieux l'apprendre avant de l'implémenter. Un commentaire que je voudrais faire sur votre article est qu'il est impossible (au moins sous ma distribution Linux) de générer des "milliers de threads OS". Il existe un nombre maximum de threads que Python vous permettra de générer avant que le programme ne se brise. Et dans mon cas (sur CentOS 5), le nombre maximum de threads est de 303.
IgorGanapolsky
C'est bon à savoir. Je n'ai jamais essayé de générer plus d'une poignée en Python à la fois, mais je m'attendais à pouvoir en créer plus avant de bombarder.
Rakis
6

Une solution:

from twisted.internet import reactor, threads
from urlparse import urlparse
import httplib
import itertools


concurrent = 200
finished=itertools.count(1)
reactor.suggestThreadPoolSize(concurrent)

def getStatus(ourl):
    url = urlparse(ourl)
    conn = httplib.HTTPConnection(url.netloc)   
    conn.request("HEAD", url.path)
    res = conn.getresponse()
    return res.status

def processResponse(response,url):
    print response, url
    processedOne()

def processError(error,url):
    print "error", url#, error
    processedOne()

def processedOne():
    if finished.next()==added:
        reactor.stop()

def addTask(url):
    req = threads.deferToThread(getStatus, url)
    req.addCallback(processResponse, url)
    req.addErrback(processError, url)   

added=0
for url in open('urllist.txt'):
    added+=1
    addTask(url.strip())

try:
    reactor.run()
except KeyboardInterrupt:
    reactor.stop()

Temps de test:

[kalmi@ubi1:~] wc -l urllist.txt
10000 urllist.txt
[kalmi@ubi1:~] time python f.py > /dev/null 

real    1m10.682s
user    0m16.020s
sys 0m10.330s
[kalmi@ubi1:~] head -n 6 urllist.txt
http://www.google.com
http://www.bix.hu
http://www.godaddy.com
http://www.google.com
http://www.bix.hu
http://www.godaddy.com
[kalmi@ubi1:~] python f.py | head -n 6
200 http://www.bix.hu
200 http://www.bix.hu
200 http://www.bix.hu
200 http://www.bix.hu
200 http://www.bix.hu
200 http://www.bix.hu

Pingtime:

bix.hu is ~10 ms away from me
godaddy.com: ~170 ms
google.com: ~30 ms
Tarnay Kálmán
la source
6
Utiliser Twisted comme pool de threads ignore la plupart des avantages que vous pouvez en retirer. Vous devez plutôt utiliser le client HTTP asynchrone.
Jean-Paul Calderone
1

L'utilisation d'un pool de threads est une bonne option et rendra cela assez facile. Malheureusement, python n'a pas de bibliothèque standard qui rend les pools de threads ultra faciles. Mais voici une bibliothèque décente qui devrait vous aider à démarrer: http://www.chrisarndt.de/projects/threadpool/

Exemple de code de leur site:

pool = ThreadPool(poolsize)
requests = makeRequests(some_callable, list_of_args, callback)
[pool.putRequest(req) for req in requests]
pool.wait()

J'espère que cela t'aides.

Kevin Wiskia
la source
Je vous suggère de spécifier q_size pour ThreadPool comme ceci: ThreadPool (poolsize, q_size = 1000) Pour que vous n'ayez pas 100 000 objets WorkRequest en mémoire. "Si q_size> 0, la taille de la file d'attente des demandes de travail est limitée et le pool de threads se bloque lorsque la file d'attente est pleine et il essaie d'y placer plus de demandes de travail (voir putRequestméthode), sauf si vous utilisez également une timeoutvaleur positive pour putRequest."
Tarnay Kálmán
Jusqu'à présent, j'essaie d'implémenter la solution threadpool - comme suggéré. Cependant, je ne comprends pas la liste des paramètres dans la fonction makeRequests. Qu'est-ce que some_callable, list_of_args, callback? Peut-être que si je voyais un véritable extrait de code qui aiderait. Je suis surpris que l'auteur de cette bibliothèque n'ait publié AUCUN exemple.
IgorGanapolsky
some_callable est votre fonction dans laquelle tout votre travail est effectué (connexion au serveur http). list_of_args est des arguments qui seront passés dans some_callabe. callback est une fonction qui sera appelée lorsque le thread de travail sera terminé. Il faut deux arguments, l'objet de travail (vous n'avez pas vraiment besoin de vous préoccuper de cela) et les résultats que le travailleur a récupérés.
Kevin Wiskia
1

Créez un epollobjet,
ouvrez de nombreuses sockets TCP client,
ajustez leurs tampons d'envoi pour être un peu plus que l'en-tête de demande,
envoyez un en-tête de demande - cela devrait être immédiat, il suffit de le placer dans un tampon, d'enregistrer le socket dans l' epollobjet, de le
faire .pollsur epollobect, de
lire en premier 3 octets de chaque socket .poll,
écrivez-les sys.stdoutpuis\n (ne pas vider), fermez le socket client.

Limitez le nombre de sockets ouvertes simultanément - gérez les erreurs lors de la création de sockets. Créez un nouveau socket uniquement si un autre est fermé.
Ajustez les limites du système d'exploitation.
Essayez de vous lancer dans quelques (peu) processus: cela peut aider à utiliser le processeur un peu plus efficacement.

George Sovetov
la source
@IgorGanapolsky Doit être. Je serais surpris autrement. Mais il a certainement besoin d'expérimentation.
George Sovetov
0

Pour votre cas, le filetage fera probablement l'affaire, car vous passerez probablement le plus de temps à attendre une réponse. Il existe des modules utiles comme Queue dans la bibliothèque standard qui pourraient vous aider.

J'ai fait une chose similaire avec le téléchargement parallèle de fichiers auparavant et c'était assez bon pour moi, mais ce n'était pas à l'échelle dont vous parlez.

Si votre tâche était plus liée au CPU, vous voudrez peut-être regarder le module multiprocessing , qui vous permettra d'utiliser plus de CPU / cores / threads (plus de processus qui ne se bloqueront pas puisque le verrouillage est par processus)

Mattias Nilsson
la source
La seule chose que je voudrais mentionner est que la génération de plusieurs processus peut être plus coûteuse que la génération de plusieurs threads. En outre, il n'y a pas de gain de performances clair lors de l'envoi de 100 000 requêtes HTTP avec plusieurs processus par rapport à plusieurs threads.
IgorGanapolsky
0

Pensez à utiliser Windmill , bien que Windmill ne puisse probablement pas faire autant de threads.

Vous pouvez le faire avec un script Python roulé à la main sur 5 machines, chacune se connectant sortante à l'aide des ports 40000-60000, ouvrant 100 000 connexions de port.

En outre, il peut être utile de faire un exemple de test avec une application d'assurance qualité bien filetée telle que OpenSTA afin de se faire une idée de la capacité de chaque serveur.

Aussi, essayez de regarder simplement en utilisant Perl simple avec la classe LWP :: ConnCache. Vous obtiendrez probablement plus de performances (plus de connexions) de cette façon.

djangofan
la source
0

Ce client Web asynchrone tordu va assez vite.

#!/usr/bin/python2.7

from twisted.internet import reactor
from twisted.internet.defer import Deferred, DeferredList, DeferredLock
from twisted.internet.defer import inlineCallbacks
from twisted.web.client import Agent, HTTPConnectionPool
from twisted.web.http_headers import Headers
from pprint import pprint
from collections import defaultdict
from urlparse import urlparse
from random import randrange
import fileinput

pool = HTTPConnectionPool(reactor)
pool.maxPersistentPerHost = 16
agent = Agent(reactor, pool)
locks = defaultdict(DeferredLock)
codes = {}

def getLock(url, simultaneous = 1):
    return locks[urlparse(url).netloc, randrange(simultaneous)]

@inlineCallbacks
def getMapping(url):
    # Limit ourselves to 4 simultaneous connections per host
    # Tweak this number, but it should be no larger than pool.maxPersistentPerHost 
    lock = getLock(url,4)
    yield lock.acquire()
    try:
        resp = yield agent.request('HEAD', url)
        codes[url] = resp.code
    except Exception as e:
        codes[url] = str(e)
    finally:
        lock.release()


dl = DeferredList(getMapping(url.strip()) for url in fileinput.input())
dl.addCallback(lambda _: reactor.stop())

reactor.run()
pprint(codes)
Robᵩ
la source
0

J'ai trouvé que l'utilisation du tornadopackage était le moyen le plus rapide et le plus simple d'y parvenir:

from tornado import ioloop, httpclient, gen


def main(urls):
    """
    Asynchronously download the HTML contents of a list of URLs.
    :param urls: A list of URLs to download.
    :return: List of response objects, one for each URL.
    """

    @gen.coroutine
    def fetch_and_handle():
        httpclient.AsyncHTTPClient.configure(None, defaults=dict(user_agent='MyUserAgent'))
        http_client = httpclient.AsyncHTTPClient()
        waiter = gen.WaitIterator(*[http_client.fetch(url, raise_error=False, method='HEAD')
                                    for url in urls])
        results = []
        # Wait for the jobs to complete
        while not waiter.done():
            try:
                response = yield waiter.next()
            except httpclient.HTTPError as e:
                print(f'Non-200 HTTP response returned: {e}')
                continue
            except Exception as e:
                print(f'An unexpected error occurred querying: {e}')
                continue
            else:
                print(f'URL \'{response.request.url}\' has status code <{response.code}>')
                results.append(response)
        return results

    loop = ioloop.IOLoop.current()
    web_pages = loop.run_sync(fetch_and_handle)

    return web_pages

my_urls = ['url1.com', 'url2.com', 'url100000.com']
responses = main(my_urls)
print(responses[0])
RDRR
la source
-2

La manière la plus simple serait d'utiliser la bibliothèque de threads intégrée de Python. Ce ne sont pas de «vrais» threads / noyau Ils ont des problèmes (comme la sérialisation), mais sont assez bons. Vous souhaitez un pool de files d'attente et de threads. Une option est ici , mais il est trivial d'écrire la vôtre. Vous ne pouvez pas paralléliser tous les 100 000 appels, mais vous pouvez en lancer 100 (ou plus) en même temps.

pestilence669
la source
7
Les threads de Python sont bien réels, contrairement à Ruby par exemple. Sous le capot, ils sont implémentés en tant que threads natifs du système d'exploitation, au moins sur Unix / Linux et Windows. Peut-être que vous faites référence au GIL, mais cela ne rend pas les fils moins réels ...
Eli Bendersky
2
Eli a raison sur les threads de Python, mais le point de Pestilence selon lequel vous voudriez utiliser un pool de threads est également correct. La dernière chose que vous voudriez faire dans ce cas est d'essayer de démarrer un thread séparé pour chacune des 100 000 requêtes simultanément.
Adam Crossland
1
Igor, vous ne pouvez pas publier sensiblement d'extraits de code dans les commentaires, mais vous pouvez modifier votre question et les y ajouter.
Adam Crossland
Pestilence: combien de files d'attente et de threads par file d'attente recommanderiez-vous pour ma solution?
IgorGanapolsky
de plus, il s'agit d'une tâche liée aux E / S non liée au processeur, le GIL affecte largement les tâches liées au processeur
PirateApp