Python'da 100.000 HTTP isteği göndermenin en hızlı yolu nedir?


287

100.000 URL'ye sahip bir dosya açıyorum. Her URL'ye bir HTTP isteği göndermem ve durum kodunu yazdırmam gerekiyor. Ben Python 2.6 kullanıyorum ve şimdiye kadar Python diş / eşzamanlılık uygulayan birçok kafa karıştırıcı yollarına baktı. Python eşzamanlama kütüphanesine bile baktım , ancak bu programı nasıl doğru yazacağımı anlayamıyorum. Benzer bir sorunla karşılaşan var mı? Genel olarak Python'da binlerce görevi mümkün olan en hızlı şekilde nasıl yapacağımı bilmem gerekiyor - sanırım 'eşzamanlı' anlamına geliyor.


47
Yalnızca HEAD isteği yaptığınızdan emin olun (böylece belgenin tamamını indirmeyin). Bakınız: stackoverflow.com/questions/107405/…
Tarnay Kálmán

5
Mükemmel nokta Kalmi. Igor'un istediği tek şey isteğin durumu ise, bu 100K istekleri çok, çok, çok daha hızlı olacaktır. Çok daha hızlı.
Adam Crossland

1
Bunun için konulara ihtiyacınız yok; en etkili yol muhtemelen Twisted gibi eşzamansız bir kitaplık kullanmaktır.
jemfinch

3
burada gevent, twisted ve asyncio tabanlı kod örnekleri (1000000 istekte test edilmiştir)
jfs

4
@ TarnayKálmán farklı durum kodları döndürmek requests.getve requests.head(yani bir sayfa isteği vs bir kafa isteği) mümkün, bu yüzden bu en iyi tavsiye değil
AlexG 10:17

Yanıtlar:


200

Bükümsüz çözüm:

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)

Bu, bükülmüş çözümden biraz daha hızlıdır ve daha az CPU kullanır.


10
@Kalmi, Kuyruğu neden ayarladınız concurrent*2?
Marcel Wilson

8
Bağlantıyı kapatmayı unutmayın conn.close(). Çok fazla http bağlantısı açmak, komut dosyanızı bir noktada durdurabilir ve bellek yer.
Aamir Adnan

4
@hyh, Queuemodül queuePython 3 olarak yeniden adlandırıldı . Bu Python 2 kodudur.
Tarnay Kálmán

3
Bağlantıyı sürdürerek SAME sunucusuyla her seferinde konuşmak isterseniz ne kadar hızlı gidebilirsiniz? Bu, iş parçacıkları arasında veya her iş parçacığı için kalıcı bağlantıyla bile yapılabilir mi?
mdurant

2
@mptevsion, CPython kullanıyorsanız (örneğin) yalnızca "yazdırma durumu, url" yerine "my_global_list.append ((status, url))" yerine geçebilirsiniz. (Çoğu işlem) listeleri GIL nedeniyle CPython'da (ve diğer bazı python uygulamalarında) örtük olarak güvenlidir, bu nedenle bunu yapmak güvenlidir.
Tarnay Kálmán

54

Kasırga eşzamansız ağ kitaplığı kullanan bir çözüm

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()

7
Bu kod engellenmeyen ağ G / Ç kullanıyor ve herhangi bir kısıtlaması yok. On binlerce açık bağlantıya ölçeklenebilir. Tek bir iş parçacığında çalışacak, ancak herhangi bir iplik geçirme çözümüne göre çok daha hızlı olacaktır. Ödeme engellemeyen G / Ç en.wikipedia.org/wiki/Asynchronous_I/O
mher

1
Burada küresel i değişkeni ile neler olduğunu açıklayabilir misiniz? Bir çeşit hata kontrolü?
LittleBobbyTables

4
`` İoloop '' dan ne zaman çıkılacağını belirlemek için bir sayaçtır - yani işiniz bittiğinde.
Michael Dorner

1
@AndrewScottEvans, python 2.7 ve proxy'ler kullandığınızı varsaydı
Dejell

5
@Guy Avraham ddos ​​planınızda yardım almak için iyi şanslar.
Walter

51

Bu yayınlanmıştır 2010 yılından bu yana işler biraz değişti ve diğer tüm cevapları denemedim ama birkaç denedim ve python3.6 kullanarak benim için en iyi çalışmak için buldum.

AWS'de çalışan saniyede yaklaşık 150 benzersiz alan getirebildim.

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())

1
Sadece bilmiyorum çünkü bu gelecekler async / await ile değiştirilebilir mi?
TankorSmash

1
Olabilir, ama daha iyi çalışmak için yukarıda buldum. aiohttp kullanabilirsiniz ama standart lib bir parçası değil ve çok değişiyor. İşe yarıyor ama ben de işe yaramadı. Kullandığımda daha yüksek hata oranları alıyorum ve benim yaşamım için teorik olarak daha iyi çalışması gerektiği gibi görünüyor, bkz: stackoverflow.com/questions/45800857/… Eğer iyi çalışırsa test etmek için lütfen cevabınızı gönderin.
Glen Thompson

1
Bu bir nitpick, ama time1 = time.time()for döngüsünün üstüne ve for döngüsünün time2 = time.time()hemen sonrasına koymak çok daha temiz olduğunu düşünüyorum .
Matt M.

Snippet'inizi test ettim, bir şekilde iki kez yürütüldü. Yanlış bir şey mi yapıyorum? Yoksa iki kez koşmak mı? İkinci durum ise, nasıl iki kez tetiklediğini anlamama yardımcı olabilir misiniz?
Ronnie

1
İki kez çalıştırılmamalıdır. Bunu neden gördüğünüzden emin değilim.
Glen Thompson

40

Konular kesinlikle bu sorunun cevabı değil. Hem hedef hem de çekirdek darboğazlarının yanı sıra, genel hedef "en hızlı yol" ise kabul edilemeyen işlem sınırlarını sağlayacaktır.

Biraz twistedve onun eşzamansız HTTPistemcisi size çok daha iyi sonuçlar verecektir.


ironfroggy: Duygularınıza doğru eğiliyorum. Çözümümü iplik ve kuyruklarla (otomatik muteksler için) uygulamayı denedim, ancak bir kuyruğu 100.000 şeyle doldurmanın ne kadar sürdüğünü hayal edebiliyor musunuz ?? Hala bu konudaki herkesin farklı seçenekleri ve önerileri ile oynuyorum ve belki Twisted iyi bir çözüm olacaktır.
IgorGanapolsky

2
Bir kuyruğu 100 bin öğe ile doldurmaktan kaçınabilirsiniz. Yalnızca girdilerinizden birer birer öğeyi işleyin, ardından her bir öğeye karşılık gelen isteği işlemek için bir iş parçacığı başlatın. (Aşağıda açıkladığım gibi, iş parçacığı sayınız bir eşiğin altında olduğunda HTTP istek iş parçacıklarını başlatmak için bir başlatıcı iş parçacığı kullanın. İş parçacıklarının yanıt vermek için sonuçları bir dikteme eşleme URL'sine yazmasını sağlayın veya bir listeye tuples ekleyin.)
Erik Garnizon

ironfroggy: Ayrıca, Python ipliklerini kullanarak hangi darboğazları bulduğunuzu merak ediyorum. Python iş parçacıkları işletim sistemi çekirdeğiyle nasıl etkileşime giriyor?
Erik Garrison

Epoll reaktörünü taktığınızdan emin olun; aksi halde seçme / anket kullanacaksınız ve bu çok yavaş olacaktır. Ayrıca, aslında aynı anda 100.000 bağlantı açmaya çalışacaksanız (programınızın bu şekilde yazıldığını ve URL'lerin farklı sunucularda olduğunu varsayarak), işletim sisteminizi ayarlamanız gerekir, böylece çalışmaz dosya tanımlayıcıları, geçici bağlantı noktaları, vb. (bir kerede 10.000'den fazla olağanüstü bağlantıya sahip olmadığınızdan emin olmak daha kolaydır).
Mark Nottingham

erikg: Harika bir fikir öneriyorsunuz. Ancak, 200 iplik ile elde edebildiğim en iyi sonuç yakl. 6 dakika. Eminim bunu daha kısa sürede yapmanın yolları vardır ... Mark N: Eğer Twisted gitmeye karar verirsem epoll reaktör kesinlikle faydalıdır. Ancak, betiğim birden çok makineden çalıştırılacaksa, bu Twisted on EACH makinesinin kurulumunu gerektirmez mi? Patronumu bu rotaya ikna edip edemeyeceğimi bilmiyorum ...
IgorGanapolsky

21

Bu eski bir soru olacak ama Python 3.7 de bunu kullanarak yapabilirsiniz asynciove 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))

Bununla ilgili daha fazla bilgi edinebilir ve burada bir örnek görebilirsiniz .


Bu C # async / await ve Kotlin Coroutines benzer mi?
IgorGanapolsky

@IgorGanapolsky, evet, C # async / await'e çok benziyor. Kotlin Coroutines'e aşina değilim.
Marius Stănescu

@sandyp, işe yarayıp yaramadığından emin değilim, ama denemek istiyorsanız, aiohttp için UnixConnector kullanmanız gerekecek. Daha fazla bilgiyi buradan edinebilirsiniz: docs.aiohttp.org/en/stable/client_reference.html#connectors .
Marius Stănescu

Teşekkürler MariusStănescu. Ben de aynen bunu kullandım.
sandyp

Asyncio.gather'i göstermek için +1 (* görevler). İşte kullandığım böyle bir snippet: urls= [fetch(construct_fetch_url(u),idx) for idx, u in enumerate(some_URI_list)] results = await asyncio.gather(*urls)
Ashwini Kumar

19

Grequests kullanın , bu isteklerin bir kombinasyonu + Gevent modülü.

GRequests, zaman uyumsuz HTTP İsteklerini kolayca yapmak için Gevent ile İstekleri kullanmanızı sağlar.

Kullanımı basit:

import grequests

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

Gönderilmemiş İstekler kümesi oluşturun:

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

Hepsini aynı anda gönderin:

>>> grequests.map(rs)
[<Response [200]>, <Response [200]>, <Response [200]>, <Response [200]>, <Response [200]>]

7
gevent şimdi python 3'ü destekliyor
Benjamin Toueg

14
grequests normal isteklerin bir parçası değildir ve büyük ölçüde habersiz görünmektedir
Thom

8

Bu sorunu çözmek için iyi bir yaklaşım, önce bir sonuç elde etmek için gerekli kodu yazmak, daha sonra uygulamayı paralelleştirmek için iş parçacığı kodunu dahil etmektir.

Mükemmel bir dünyada, bu, daha sonra işlenmek üzere sonuçlarını bir sözlüğe veya listeye aktaran 100.000 iş parçacığının aynı anda başlatılması anlamına gelir, ancak uygulamada, bu şekilde kaç paralel HTTP isteği yayınlayabileceğinizle sınırlısınız. Yerel olarak, aynı anda kaç soket açabileceğiniz, Python yorumlayıcınızın kaç yürütme iş parçasına izin vereceği konusunda sınırlarınız var. Uzaktan, tüm istekler bir veya birden fazla sunucuya karşı ise, eşzamanlı bağlantı sayısıyla sınırlı olabilirsiniz. Bu sınırlamalar, muhtemelen komut dosyasını, URL'lerin küçük bir bölümünü herhangi bir anda yok edecek şekilde yazmanızı gerektirecektir (100, bahsedilen başka bir poster gibi, muhtemelen iyi bir iş parçacığı havuzu boyutudur). başarıyla uygulayabilir).

Yukarıdaki sorunu çözmek için bu tasarım desenini takip edebilirsiniz:

  1. Şu anda çalışan iş parçacığı sayısına kadar yeni bir iş parçacığı başlatan bir iş parçacığı başlatın (bunları threading.active_count () aracılığıyla veya iş parçacığı nesnelerini bir veri yapısına iterek izleyebilirsiniz)> = maksimum eşzamanlı istek sayınız (100 diyelim) , sonra kısa bir zaman aşımı süresi için uyur. İşlenecek başka URL olmadığında bu iş parçacığı sonlandırılmalıdır. Böylece, iplik uyanmaya, yeni iplik başlatmaya ve bitene kadar uyumaya devam edecektir.
  2. İstek iş parçacıklarının sonuçlarını daha sonra almak ve çıktı almak için bazı veri yapılarında saklamasını sağlayın. Yapı Sonuçları saklamak durumunda olduğu bir listveya dictCPython içinde yapabilirsiniz güvenle ekleme veya kilitleri olmadan parçacığı benzersiz öğeleri ekleme bir dosyaya yazma veya daha karmaşık çapraz iplik veri etkileşiminde gerektiriyorsa, ama sen kullanmalısınız bu durumu yolsuzluktan korumak için karşılıklı dışlama kilidi .

İplik geçirme modülünü kullanmanızı öneririm . Çalışan iş parçacıklarını başlatmak ve izlemek için kullanabilirsiniz. Python'un diş açma desteği çıplak, ancak probleminizin açıklaması, ihtiyaçlarınız için tamamen yeterli olduğunu gösteriyor.

Son olarak, Python'da yazılmış bir paralel ağ uygulamasının oldukça basit bir uygulamasını görmek istiyorsanız, ssh.py'ye bakın . Birçok SSH bağlantısını paralelleştirmek için Python iş parçacığını kullanan küçük bir kütüphane. Tasarım, iyi bir kaynak olarak bulabileceğiniz gereksinimlerinize yeterince yakın.


1
erikg: denkleminize bir sıraya atmak mantıklı olur (karşılıklı dışlama kilitleme için)? Python'un GIL'inin binlerce iş parçacığı ile oynamaya yönelik olmadığından şüpheleniyorum.
IgorGanapolsky

Çok fazla iş parçacığının oluşmasını önlemek için neden karşılıklı dışlama kilidine ihtiyacınız var? Bu terimi yanlış anladığımı sanıyorum. Bir iş parçacığı kuyruğundaki çalışan iş parçacıklarını izleyebilir, tamamlandığında bunları kaldırabilir ve bahsedilen iş parçacığı sınırına daha fazla ekleyebilirsiniz. Ancak, söz konusu gibi basit bir durumda, mevcut Python işlemindeki etkin iş parçacıklarının sayısını da izleyebilir, bir eşiğin altına düşene kadar bekleyebilir ve açıklandığı gibi eşik değerine kadar daha fazla iş parçacığı başlatabilirsiniz. Sanırım bunu örtük bir kilit olarak düşünebilirsiniz, ancak afaik için açık bir kilit gerekmez.
Erik Garrison

erikg: birden fazla iş parçacığı durumu paylaşmıyor mu? O'Reilly'nin "Unix ve Linux Sistem Yönetimi için Python" kitabında 305: "Neden? Çünkü kuyruk modülü ayrıca mutekslerle verileri açıkça koruma gereğini de hafifletir çünkü kuyruk zaten bir muteks tarafından dahili olarak korunmaktadır." Yine, bu konuda görüşlerinizi memnuniyetle karşılıyoruz.
IgorGanapolsky

Igor: Kesinlikle bir kilit kullanmalısın. Gönderiyi bunu yansıtacak şekilde düzenledim. Bununla birlikte, python ile pratik deneyim, list.append veya hash anahtarının eklenmesi gibi, iş parçacıklarınızdan atomik olarak değiştirdiğiniz veri yapılarını kilitlemenize gerek olmadığını gösterir. Bunun sebebi, list gibi işlemler sağlayan GIL'dir. Bir dereceye kadar atomisite ekler. Şu anda bunu doğrulamak için bir test yapıyorum (bir listeye 0-9999 numaraları eklemek için 10k iş parçacığı kullanın, tüm eklerin çalışıp çalışmadığını kontrol edin). Yaklaşık 100 tekrardan sonra test başarısız oldu.
Erik Garrison

Igor: Bu konuyla ilgili başka bir soru soruldu: stackoverflow.com/questions/2740435/…
Erik Garrison

7

Mümkün olan en iyi performansı elde etmek istiyorsanız, iş parçacıkları yerine Asenkron I / O kullanmayı düşünebilirsiniz. Binlerce işletim sistemi iş parçacığıyla ilişkili ek yük önemsiz değildir ve Python yorumlayıcısı içindeki bağlam geçişi bunun üzerine daha da fazlasını ekler. Diş çekme işi kesinlikle halledecektir, ancak asenkron yolun daha iyi genel performans sağlayacağından şüpheleniyorum.

Özellikle, Twisted kütüphanesinde ( http://www.twistedmatrix.com ) zaman uyumsuz web istemcisini öneririm . Kuşkusuz dik bir öğrenme eğrisi vardır, ancak Twisted'ın eşzamansız programlama tarzını iyi bir şekilde ele aldığınızda kullanımı oldukça kolaydır.

Bir HowTo on Twisted'ın eşzamansız web istemcisi API'sını şu adreste bulabilirsiniz:

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


Rakis: Şu anda eşzamansız ve engellemeyen G / Ç'yi arıyorum. Uygulamadan önce daha iyi öğrenmem gerekiyor. Gönderinizde yapmak istediğim bir yorum, "binlerce işletim sistemi iş parçacığını" ortaya çıkarmanın imkansız (en azından Linux dağıtımım altında). Program bozulmadan önce Python'un yumurtlamanıza izin vereceği maksimum sayıda iş parçacığı vardır. Ve benim durumumda (CentOS 5'te) maksimum iş parçacığı sayısı
303'tür

Bunu bilmek güzel. Bir kerede Python'da bir avuçtan daha fazla yumurtlama denemedim ama bombalanmadan önce bundan daha fazlasını yaratmayı umuyordum.
Rakis

6

Bir çözüm:

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()

Test zamanı:

[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

6
Twisted'i bir threadpool olarak kullanmak, bundan yararlanabileceğiniz avantajların çoğunu görmezden geliyor. Bunun yerine zaman uyumsuz HTTP istemcisini kullanıyor olmalısınız.
Jean-Paul Calderone

1

Bir iş parçacığı havuzu kullanmak iyi bir seçenektir ve bunu oldukça kolaylaştıracaktır. Ne yazık ki, python, iş parçacığı havuzlarını son derece kolaylaştıran standart bir kütüphaneye sahip değil. Ama işte başlamanız gereken iyi bir kütüphane: http://www.chrisarndt.de/projects/threadpool/

Sitelerinden kod örneği:

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

Bu yardımcı olur umarım.


ThreadPool için böyle q_size belirtmenizi öneririm: ThreadPool (poolsize, q_size = 1000) Böylece 100000 WorkRequest nesnesi bellekte kalmaz. "Eğer q_size> 0, iş isteği kuyruğunun boyutu sınırlanır ve iş parçacığı havuzu, kuyruk dolduğunda engellenir ve için putRequestpozitif bir timeoutdeğer kullanmazsanız, daha fazla iş isteği koymaya çalışır ( yönteme bakın ) putRequest."
Tarnay Kálmán

Şimdiye kadar threadpool çözüm uygulamaya çalışıyorum - önerildiği gibi. Ancak, makeRequests işlevindeki parametre listesini anlamıyorum. Some_callable, list_of_args, callback nedir? Belki de yardımcı olacak gerçek bir kod snippet'i görürsem. Bu kütüphanenin yazarının HERHANGİ BİR örnek yayınlamamasına şaşırdım.
IgorGanapolsky

some_callable, tüm işinizin yapıldığı işlevinizdir (http sunucusuna bağlanma). list_of_args, some_callabe öğesine aktarılacak bağımsız değişkenlerdir. geri arama, çalışan iş parçacığı tamamlandığında çağrılacak bir işlevdir. İki argüman alır: işçi nesnesi (kendinizle bununla gerçekten ilgilenmeniz gerekmez) ve işçinin aldığı sonuçlar.
Kevin Wiskia

1

Create epollnesne,
açık, birçok istemci TCP soketleri
istek başlığında biraz daha olmak onların gönderme tampon ayarlamak,
bir istek başlığı gönderir - bu, sadece bir tampon içine yerleştirerek, acil olması halinde soketi kayıt etmelidir epollnesne,
do .pollüzerine epollobect,
ilk 3 okuma her soketten gelen baytları .poll, üzerine
yazın ve sys.stdoutardından \n(yıkamayın) istemci soketini kapatın.

Aynı anda açılan soket sayısını sınırlayın - soketler oluşturulduğunda hataları ele alın. Yeni bir soket yalnızca başka bir kapalıysa oluşturun.
İşletim sistemi sınırlarını ayarlayın.
Birkaç (çok değil) işleme girmeyi deneyin: bu, CPU'nun biraz daha etkili kullanılmasına yardımcı olabilir.


@IgorGanapolsky Olmalı. Aksi halde şaşırırdım. Ama kesinlikle denemeye ihtiyacı var.
George Sovetov

0

Sizin durumunuz için, büyük olasılıkla bir yanıt beklemek için en fazla zaman harcayacağınız için, iş parçacığı muhtemelen işe yarayacaktır. Standart kitaplıkta yardımcı olabilecek Kuyruk gibi yardımcı modüller vardır .

Daha önce dosyaların paralel indirilmesinde de benzer bir şey yaptım ve bu benim için yeterince iyiydi, ama bahsettiğiniz ölçekte değildi.

Göreviniz daha fazla CPU'ya bağlıysa, daha fazla CPU / çekirdek / iş parçacığı (kilitleme işlem başına olduğu için birbirini engellemeyecek daha fazla işlem) kullanmanıza izin verecek olan çoklu işlem modülüne bakmak isteyebilirsiniz.


Bahsetmek istediğim tek şey, birden çok işlemin ortaya çıkmasının, birden çok iş parçacığının ortaya çıkmasından daha pahalı olabileceğidir. Ayrıca, birden çok iş parçacığına karşı birden çok işlemle 100.000 HTTP isteği gönderme konusunda net bir performans kazancı yoktur.
IgorGanapolsky

0

Kullanmayı düşünün Yeldeğirmeni Yeldeğirmeni muhtemelen birçok konuları yapamaz rağmen.

Her biri 40000-60000 bağlantı noktalarını kullanarak giden bağlantıyı bağlayarak 100.000 bağlantı noktası bağlantısı açan 5 makinede elle haddelenmiş bir Python betiği ile yapabilirsiniz.

Ayrıca, her sunucunun ne kadar işleyebileceğine dair bir fikir edinmek için OpenSTA gibi güzel iş parçacıklı bir KG uygulamasıyla örnek bir test yapmak yardımcı olabilir.

Ayrıca, LWP :: ConnCache sınıfıyla sadece basit Perl'i kullanmayı deneyin. Muhtemelen bu şekilde daha fazla performans (daha fazla bağlantı) elde edersiniz.


0

Bu bükülmüş asenkron web istemcisi oldukça hızlı gidiyor.

#!/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)

0

Bu tornadopaketi kullanarak bunu başarmanın en hızlı ve en basit yolu olarak buldum :

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])

-2

En kolay yol Python'un yerleşik iş parçacığı kitaplığını kullanmaktır. "Gerçek" / çekirdek konuları değiller (serileştirme gibi) sorunları var, ama yeterince iyi. Bir kuyruk ve iş parçacığı havuzu istiyorsunuz. Bir seçenek burada , ancak kendiniz yazmak önemsiz. 100.000 çağrının tümünü paralel hale getiremezsiniz, ancak aynı anda 100 (veya daha fazla) çağrıyı kapatabilirsiniz.


7
Python'un konuları örneğin Ruby'nin aksine oldukça gerçektir. Başlık altında, en azından Unix / Linux ve Windows üzerinde yerel işletim sistemi iş parçacıkları olarak uygulanırlar. Belki
GIL'den

2
Eli, Python'un iş parçacıkları konusunda haklı, ancak Pestilence'ın bir iş parçacığı havuzu kullanmak istediğinizi de doğru. Bu durumda yapmak istediğiniz son şey, aynı anda 100K isteğinin her biri için ayrı bir iş parçacığı başlatmaya çalışmaktır.
Adam Crossland

1
Igor, yorumlarda kod snippet'lerini anlamlı bir şekilde yayınlayamazsınız, ancak sorunuzu düzenleyebilir ve oraya ekleyebilirsiniz.
Adam Crossland

Pestilence: Çözümüm için kaç kuyruk ve kuyruk başına iş parçacığı önerirsiniz?
IgorGanapolsky

artı bu bir I / O bağlı görev CPU bağlı değil, GIL büyük ölçüde CPU bağlı görevleri etkiler
PirateApp
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.