Apa cara tercepat untuk mengirim 100.000 permintaan HTTP dengan Python?

287

Saya membuka file yang memiliki 100.000 URL. Saya perlu mengirim permintaan HTTP ke setiap URL dan mencetak kode status. Saya menggunakan Python 2.6, dan sejauh ini melihat banyak cara membingungkan Python mengimplementasikan threading / concurrency. Saya bahkan telah melihat pustaka persetujuan python , tetapi tidak tahu bagaimana menulis program ini dengan benar. Adakah yang mengalami masalah serupa? Saya kira secara umum saya perlu tahu bagaimana melakukan ribuan tugas dengan Python secepat mungkin - saya kira itu berarti 'bersamaan'.

IgorGanapolsky
sumber
47
Pastikan Anda hanya melakukan permintaan KEPALA (sehingga Anda tidak mengunduh seluruh dokumen). Lihat: stackoverflow.com/questions/107405/…
Tarnay Kálmán
5
Poin bagus, Kalmi. Jika semua Igor inginkan adalah status permintaan, permintaan 100 ribu ini akan berjalan jauh, jauh, lebih cepat. Jauh lebih cepat.
Adam Crossland
1
Anda tidak perlu utas untuk ini; cara paling efisien adalah kemungkinan menggunakan pustaka asinkron seperti Twisted.
jemfinch
3
berikut adalah contoh kode berbasis - gevent, twisted, dan asyncio (diuji pada 1000000 permintaan)
jfs
4
@ TarnayKálmán dimungkinkan untuk requests.getdan requests.head(mis. Permintaan halaman vs permintaan kepala) untuk mengembalikan kode status yang berbeda, jadi ini bukan saran terbaik
AlexG

Jawaban:

200

Solusi twistedless:

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)

Yang ini sedikit lebih cepat daripada solusi memutar dan menggunakan lebih sedikit CPU.

Tarnay Kálmán
sumber
10
@ Kalmi, mengapa Anda mengatur Antrian concurrent*2?
Marcel Wilson
8
Jangan lupa untuk menutup koneksi conn.close() . Membuka terlalu banyak koneksi http dapat menghentikan skrip Anda di beberapa titik dan memakan memori.
Aamir Adnan
4
@hyh, Queuemodul telah diubah namanya menjadi queuePython 3. Ini adalah kode Python 2.
Tarnay Kálmán
3
Seberapa cepat Anda dapat pergi jika Anda ingin berbicara dengan server SAMA setiap kali, dengan mempertahankan koneksi? Apakah ini dapat dilakukan melalui thread, atau dengan satu koneksi persisten?
mdurant
2
@mptevsion, jika Anda menggunakan CPython, Anda bisa (misalnya) mengganti "status cetak, url" dengan "my_global_list.append ((status, url))". (Sebagian besar operasi pada) daftar secara implisit thread-safe di CPython (dan beberapa implementasi python lainnya) karena GIL, jadi ini aman untuk dilakukan.
Tarnay Kálmán
54

Solusi menggunakan tornado pustaka jaringan tidak sinkron

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()
saya
sumber
7
Kode ini menggunakan I / O jaringan non-pemblokiran dan tidak memiliki batasan apa pun. Ini dapat menskala hingga puluhan ribu koneksi terbuka. Ini akan berjalan dalam satu utas tetapi akan menjadi cara yang lebih cepat daripada solusi threading apa pun. Checkout non-pemblokiran I / O en.wikipedia.org/wiki/Asynchronous_I/O
mher
1
Bisakah Anda menjelaskan apa yang terjadi di sini dengan variabel global saya? Semacam pengecekan kesalahan?
LittleBobbyTables
4
Ini adalah penghitung untuk menentukan kapan harus keluar dari `` ioloop` - jadi ketika Anda selesai.
Michael Dorner
1
@AndrewScottEvans diasumsikan bahwa Anda menggunakan python 2.7 dan proxy
Dejell
5
@Beli Avraham Semoga beruntung mendapatkan bantuan pada rencana ddos ​​Anda.
Walter
51

Hal-hal telah berubah sedikit sejak 2010 ketika ini diposting dan saya belum mencoba semua jawaban lain tetapi saya telah mencoba beberapa, dan saya menemukan ini berfungsi terbaik untuk saya menggunakan python3.6.

Saya dapat mengambil sekitar ~ 150 domain unik per detik yang berjalan di 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
sumber
1
Saya hanya bertanya karena saya tidak tahu tetapi bisakah barang berjangka ini diganti dengan async / tunggu?
TankorSmash
1
Bisa, tetapi saya telah menemukan hal di atas berfungsi lebih baik. Anda bisa menggunakan aiohttp tetapi bukan bagian dari lib standar dan berubah cukup banyak. Itu memang berhasil, tetapi saya belum menemukannya juga. Saya mendapatkan tingkat kesalahan yang lebih tinggi ketika saya menggunakannya dan untuk kehidupan saya, saya tidak bisa membuatnya bekerja serta masa depan bersamaan meskipun secara teori Tampaknya itu harus bekerja lebih baik, lihat: stackoverflow.com/questions/45800857/… jika Anda membuatnya bekerja dengan baik, silakan kirim jawaban Anda sehingga saya bisa mengujinya.
Glen Thompson
1
Ini adalah nitpick, tapi saya pikir ini jauh lebih bersih untuk diletakkan time1 = time.time()di atas for loop dan time2 = time.time()setelah for for loop.
Matt M.
Saya menguji cuplikan Anda, entah bagaimana itu dijalankan dua kali. Apakah saya melakukan sesuatu yang salah? Atau apakah itu dimaksudkan untuk menjalankan dua kali? Jika ini kasus terakhir, dapatkah Anda membantu saya untuk memahami bagaimana pemicunya dua kali?
Ronnie
1
Seharusnya tidak berjalan dua kali. Tidak yakin mengapa Anda melihatnya.
Glen Thompson
40

Utas sama sekali bukan jawaban di sini. Mereka akan memberikan bottlenecks proses dan kernel, serta batas throughput yang tidak dapat diterima jika tujuan keseluruhan adalah "cara tercepat".

Sedikit twisteddan HTTPklien asinkronnya akan memberi Anda hasil yang jauh lebih baik.

ironfroggy
sumber
ironfroggy: Saya condong ke arah perasaan Anda. Saya mencoba menerapkan solusi saya dengan utas dan antrian (untuk mutex otomatis), tetapi dapatkah Anda bayangkan berapa lama untuk mengisi antrian dengan 100.000 hal? Saya masih bermain-main dengan berbagai opsi dan saran oleh semua orang di utas ini, dan mungkin Twisted akan menjadi solusi yang baik.
IgorGanapolsky
2
Anda dapat menghindari mengisi antrian dengan 100 ribu hal. Cukup proses item satu per satu dari input Anda, kemudian luncurkan utas untuk memproses permintaan yang terkait dengan setiap item. (Seperti yang saya jelaskan di bawah, gunakan utas peluncur untuk memulai utas permintaan HTTP saat jumlah utas Anda di bawah ambang batas. Buat utas menuliskan hasilnya ke URL pemetaan dict untuk merespons, atau tambahkan tupel ke daftar.)
Erik Garrison
ironfroggy: Juga, saya ingin tahu tentang bottleneck apa yang Anda temukan menggunakan utas Python? Dan bagaimana cara thread Python berinteraksi dengan kernel OS?
Erik Garrison
Pastikan Anda menginstal reaktor epoll; kalau tidak, Anda akan menggunakan pilih / polling, dan itu akan sangat lambat. Juga, jika Anda benar-benar akan mencoba untuk membuka 100.000 koneksi secara bersamaan (dengan asumsi program Anda ditulis dengan cara itu, dan URL-nya ada di server yang berbeda), Anda harus menyetel OS Anda sehingga Anda tidak akan kehabisan deskriptor file, porta fana, dll. (mungkin lebih mudah untuk memastikan bahwa Anda tidak memiliki lebih dari, katakanlah, 10.000 koneksi luar biasa sekaligus).
Mark Nottingham
erikg: Anda merekomendasikan ide yang bagus. Namun, hasil terbaik yang dapat saya capai dengan 200 utas adalah sekitar. 6 menit. Saya yakin ada cara untuk mencapai ini dalam waktu yang lebih singkat ... Mark N: jika Twisted adalah cara saya memutuskan untuk pergi, maka reaktor epoll pasti berguna. Namun, jika skrip saya akan dijalankan dari beberapa mesin, bukankah itu mengharuskan instalasi mesin Twisted on EACH? Saya tidak tahu apakah saya bisa meyakinkan bos saya untuk menempuh rute itu ...
IgorGanapolsky
21

Saya tahu ini adalah pertanyaan lama, tetapi dengan Python 3.7 Anda bisa melakukan ini menggunakan asynciodan 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))

Anda dapat membaca lebih lanjut tentang hal ini dan melihat contohnya di sini .

Marius Stănescu
sumber
Apakah ini mirip dengan C # async / menunggu dan Kotlin Coroutines?
IgorGanapolsky
@IgorGanapolsky, ya, ini sangat mirip dengan C # async / tunggu. Saya tidak terbiasa dengan Kotlin Coroutines.
Marius Stănescu
@sandyp, saya tidak yakin apakah itu berfungsi, tetapi jika Anda ingin mencoba Anda harus menggunakan UnixConnector untuk aiohttp. Baca lebih lanjut di sini: docs.aiohttp.org/en/stable/client_reference.html#connectors .
Marius Stănescu
Terima kasih @ MariusStănescu. Itulah yang saya gunakan.
sandyp
+1 untuk menampilkan asyncio.gather (* tugas). di sini adalah salah satu cuplikan seperti yang saya gunakan: urls= [fetch(construct_fetch_url(u),idx) for idx, u in enumerate(some_URI_list)] results = await asyncio.gather(*urls)
Ashwini Kumar
19

Gunakan grequests , ini kombinasi dari permintaan + modul Gevent.

GRequests memungkinkan Anda menggunakan Permintaan dengan Gevent untuk membuat Permintaan HTTP asinkron dengan mudah.

Penggunaannya sederhana:

import grequests

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

Buat satu set Permintaan yang belum terkirim:

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

Kirim semuanya sekaligus:

>>> grequests.map(rs)
[<Response [200]>, <Response [200]>, <Response [200]>, <Response [200]>, <Response [200]>]
Akshay Pratap Singh
sumber
7
gevent sekarang mendukung python 3
Benjamin Toueg
14
grequests bukan bagian dari permintaan normal dan tampaknya sebagian besar tidak diolah
Thom
8

Pendekatan yang baik untuk menyelesaikan masalah ini adalah dengan terlebih dahulu menulis kode yang diperlukan untuk mendapatkan satu hasil, kemudian memasukkan kode threading untuk memparalelkan aplikasi.

Dalam dunia yang sempurna ini berarti secara simultan memulai 100.000 utas yang menampilkan hasilnya ke kamus atau daftar untuk diproses nanti, tetapi dalam praktiknya Anda terbatas pada berapa banyak permintaan HTTP paralel yang dapat Anda terbitkan dengan cara ini. Secara lokal, Anda memiliki batasan dalam berapa banyak soket yang dapat Anda buka secara bersamaan, berapa banyak utas eksekusi yang diizinkan oleh penerjemah Python Anda. Secara jarak jauh, Anda mungkin terbatas dalam jumlah koneksi simultan jika semua permintaan bertentangan dengan satu server, atau banyak. Batasan-batasan ini mungkin mengharuskan Anda menulis skrip sedemikian rupa sehingga hanya menyurvei sebagian kecil dari URL pada suatu waktu (100, seperti poster lain yang disebutkan, mungkin merupakan ukuran kumpulan thread yang layak, meskipun Anda mungkin menemukan bahwa Anda dapat berhasil menyebarkan lebih banyak).

Anda dapat mengikuti pola desain ini untuk menyelesaikan masalah di atas:

  1. Mulai utas yang meluncurkan utas permintaan baru hingga jumlah utas yang saat ini berjalan (Anda dapat melacaknya melalui threading.active_count () atau dengan mendorong objek utas ke dalam struktur data) adalah> = jumlah maksimum permintaan simultan Anda (katakan 100) , lalu tidur sebentar. Utas ini harus berakhir ketika tidak ada lagi URL untuk diproses. Dengan demikian, utas akan terus bangun, meluncurkan utas baru, dan tidur sampai Anda selesai.
  2. Mintalah utas permintaan menyimpan hasilnya dalam beberapa struktur data untuk pengambilan dan keluaran nanti. Jika struktur tempat Anda menyimpan hasilnya adalah listatau dictdalam CPython, Anda dapat dengan aman menambahkan atau memasukkan item unik dari utas Anda tanpa kunci , tetapi jika Anda menulis ke file atau memerlukan interaksi data cross-thread yang lebih kompleks, Anda harus menggunakan saling pengecualian mengunci untuk melindungi negara ini dari korupsi .

Saya sarankan Anda menggunakan modul threading . Anda dapat menggunakannya untuk meluncurkan dan melacak utas yang sedang berjalan. Dukungan threading Python kosong, tetapi deskripsi masalah Anda menunjukkan bahwa itu sepenuhnya cukup untuk kebutuhan Anda.

Akhirnya, jika Anda ingin melihat aplikasi langsung yang cukup dari aplikasi jaringan paralel ditulis dengan Python, memeriksa ssh.py . Ini adalah perpustakaan kecil yang menggunakan threading Python untuk memparalelkan banyak koneksi SSH. Desainnya cukup dekat dengan kebutuhan Anda sehingga Anda mungkin menganggapnya sebagai sumber yang bagus.

Erik Garrison
sumber
1
erikg: apakah memasukkan antrian ke persamaan Anda masuk akal (untuk penguncian mutual-exclusion)? Saya menduga bahwa GIL Python tidak diarahkan untuk bermain dengan ribuan utas.
IgorGanapolsky
Mengapa Anda perlu penguncian saling pengecualian untuk mencegah pembuatan terlalu banyak utas? Saya kira saya salah paham dengan istilah itu. Anda dapat melacak utas yang sedang berjalan dalam antrian utas, menghapusnya saat sudah selesai dan menambahkan lebih hingga batas utas tersebut. Tetapi dalam kasus sederhana seperti yang dipermasalahkan, Anda juga bisa menyaksikan jumlah utas aktif dalam proses Python saat ini, tunggu hingga jatuh di bawah ambang batas, dan luncurkan lebih banyak utas ke ambang sebagaimana dijelaskan. Saya kira Anda bisa menganggap ini sebagai kunci implisit, tetapi tidak ada kunci eksplisit yang diperlukan afaik.
Erik Garrison
erikg: bukankah banyak thread berbagi status? Pada halaman 305 dalam buku O'Reilly "Python untuk Unix dan Sistem Administrasi Linux" menyatakan: "... menggunakan threading tanpa antrian membuatnya lebih kompleks daripada yang dapat ditangani secara realistis oleh banyak orang. Adalah ide yang jauh lebih baik untuk selalu menggunakan antrian modul jika Anda menemukan Anda perlu menggunakan utas. Mengapa? Karena modul antrian juga mengurangi kebutuhan untuk secara eksplisit melindungi data dengan mutex karena antrian itu sendiri sudah dilindungi secara internal oleh mutex. " Sekali lagi, saya menyambut pandangan Anda tentang ini.
IgorGanapolsky
Igor: Anda memang benar bahwa Anda harus menggunakan kunci. Saya telah mengedit posting untuk mencerminkan ini. Yang mengatakan, pengalaman praktis dengan python menunjukkan bahwa Anda tidak perlu mengunci struktur data yang Anda modifikasi secara atomis dari utas Anda, seperti dengan list.append atau dengan penambahan kunci hash. Alasannya, saya percaya, adalah GIL, yang menyediakan operasi seperti list.append dengan tingkat atomicity. Saat ini saya sedang menjalankan tes untuk memverifikasi ini (gunakan utas 10k untuk menambahkan angka 0-9999 ke daftar, periksa apakah semua lampiran berfungsi). Setelah hampir 100 iterasi tes belum gagal.
Erik Garrison
Igor: Saya ditanya pertanyaan lain tentang topik ini: stackoverflow.com/questions/2740435/…
Erik Garrison
7

Jika Anda mencari untuk mendapatkan kinerja terbaik, Anda mungkin ingin mempertimbangkan untuk menggunakan Asynchronous I / O daripada utas. Overhead yang terkait dengan ribuan utas OS adalah non-sepele dan konteks beralih dalam juru bahasa Python menambahkan lebih banyak di atasnya. Threading pasti akan menyelesaikan pekerjaan tetapi saya menduga bahwa rute yang tidak sinkron akan memberikan kinerja keseluruhan yang lebih baik.

Secara khusus, saya sarankan klien web async di perpustakaan Twisted ( http://www.twistedmatrix.com ). Ini memiliki kurva belajar yang jelas curam tetapi cukup mudah digunakan setelah Anda mendapatkan pegangan yang baik pada gaya pemrograman asinkron Twisted.

API klien web asinkron Twisted's HowTo tersedia di:

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

Rakis
sumber
Rakis: Saat ini saya sedang mencari I / O yang asinkron dan tidak menghalangi. Saya perlu mempelajarinya lebih baik sebelum saya menerapkannya. Satu komentar yang ingin saya sampaikan pada posting Anda adalah bahwa tidak mungkin (setidaknya di bawah distribusi Linux saya) untuk menelurkan "ribuan utas OS". Ada jumlah maksimum utas yang Python akan memungkinkan Anda untuk menelurkan sebelum program istirahat. Dan dalam kasus saya (pada CentOS 5) jumlah maksimum utas adalah 303.
IgorGanapolsky
Senang mendengarnya. Saya tidak pernah mencoba menelurkan lebih dari segelintir Python sekaligus, tetapi saya berharap dapat membuat lebih dari itu sebelum dibom.
Rakis
6

Sebuah solusi:

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

Waktu tes:

[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
sumber
6
Menggunakan Twisted sebagai threadpool mengabaikan sebagian besar manfaat yang bisa Anda dapatkan darinya. Anda harus menggunakan klien HTTP async sebagai gantinya.
Jean-Paul Calderone
1

Menggunakan thread pool adalah pilihan yang baik, dan akan membuat ini cukup mudah. Sayangnya, python tidak memiliki pustaka standar yang membuat thread thread sangat mudah. Tapi di sini ada perpustakaan yang layak yang harus Anda mulai: http://www.chrisarndt.de/projects/threadpool/

Contoh kode dari situs mereka:

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

Semoga ini membantu.

Kevin Wiskia
sumber
Saya menyarankan Anda menentukan q_size untuk ThreadPool seperti ini: ThreadPool (poolsize, q_size = 1000) Sehingga Anda tidak akan memiliki 100000 objek WorkRequest dalam memori. "Jika q_size> 0 ukuran antrian permintaan pekerjaan terbatas dan utas kolam memblokir ketika antrian penuh dan mencoba memasukkan lebih banyak permintaan kerja di dalamnya (lihat putRequestmetode), kecuali jika Anda juga menggunakan timeoutnilai positif untuk putRequest."
Tarnay Kálmán
Sejauh ini saya mencoba menerapkan solusi threadpool - seperti yang disarankan. Namun, saya tidak mengerti daftar parameter dalam fungsi makeRequests. Apa itu some_callable, list_of_args, callback? Mungkin jika saya melihat potongan kode nyata yang akan membantu. Saya terkejut bahwa penulis perpustakaan itu tidak memposting contoh APAPUN.
IgorGanapolsky
some_callable adalah fungsi Anda untuk menyelesaikan semua pekerjaan Anda (menghubungkan ke server http). list_of_args adalah argumen yang akan diteruskan ke some_callabe. callback adalah fungsi yang akan dipanggil saat utas pekerja selesai. Dibutuhkan dua argumen, objek pekerja (tidak perlu mengkhawatirkan diri Anda dengan hal ini), dan hasil yang diperoleh pekerja.
Kevin Wiskia
1

Buat epollobjek,
terbuka banyak soket klien TCP,
menyesuaikan buffer kirim mereka untuk menjadi sedikit lebih dari header permintaan,
mengirim header permintaan - itu harus segera, hanya menempatkan ke dalam buffer, daftar soket di epollobjek,
lakukan .pollpada epollobect,
baca pertama 3 byte dari setiap soket dari .poll,
tulis mereka sys.stdoutdiikuti \n(jangan siram), tutup soket klien.

Batasi jumlah soket yang dibuka secara bersamaan - menangani kesalahan saat soket dibuat. Buat soket baru hanya jika yang lain ditutup.
Sesuaikan batas OS.
Coba gunakan beberapa proses (tidak banyak): ini mungkin membantu menggunakan CPU sedikit lebih efektif.

George Sovetov
sumber
@IgorGanapolsky Harus. Saya akan terkejut sebaliknya. Tapi itu tentu perlu eksperimen.
George Sovetov
0

Untuk kasus Anda, threading mungkin akan melakukan trik karena Anda mungkin akan menghabiskan sebagian besar waktu menunggu jawaban. Ada modul bermanfaat seperti Antrian di perpustakaan standar yang mungkin membantu.

Saya melakukan hal yang sama dengan mengunduh file secara paralel sebelumnya dan itu cukup baik untuk saya, tetapi itu tidak pada skala yang Anda bicarakan.

Jika tugas Anda lebih terikat CPU, Anda mungkin ingin melihat modul multiprosesing , yang akan memungkinkan Anda untuk memanfaatkan lebih banyak CPU / core / utas (lebih banyak proses yang tidak akan memblokir satu sama lain karena penguncian adalah per proses)

Mattias Nilsson
sumber
Satu-satunya hal yang ingin saya sebutkan adalah bahwa menelurkan banyak proses mungkin lebih mahal daripada menelurkan banyak utas. Juga, tidak ada keuntungan kinerja yang jelas dalam mengirimkan 100.000 permintaan HTTP dengan banyak proses vs. beberapa utas.
IgorGanapolsky
0

Pertimbangkan untuk menggunakan Windmill , walaupun Windmill mungkin tidak dapat melakukan banyak thread.

Anda bisa melakukannya dengan skrip Python digulung tangan pada 5 mesin, masing-masing menghubungkan keluar menggunakan port 40000-60000, membuka 100.000 koneksi port.

Juga, mungkin membantu untuk melakukan uji sampel dengan aplikasi QA yang diulir dengan baik seperti OpenSTA untuk mendapatkan gambaran tentang seberapa banyak yang dapat ditangani oleh setiap server.

Selain itu, coba lihat menggunakan Perl sederhana dengan kelas LWP :: ConnCache. Anda mungkin akan mendapatkan lebih banyak kinerja (lebih banyak koneksi) dengan cara itu.

Djangofan
sumber
0

Klien web bengkok async ini berjalan cukup cepat.

#!/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ᵩ
sumber
0

Saya menemukan bahwa menggunakan tornadopaket menjadi cara tercepat dan termudah untuk mencapai ini:

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
sumber
-2

Cara termudah adalah dengan menggunakan pustaka threading bawaan Python. Mereka bukan "nyata" / utas kernel. Mereka memiliki masalah (seperti serialisasi), tetapi cukup baik. Anda ingin antrian & kumpulan utas. Satu opsi ada di sini , tetapi sepele untuk menulis sendiri. Anda tidak dapat memparalelkan semua 100.000 panggilan, tetapi Anda dapat mematikan 100 (atau lebih) dari mereka secara bersamaan.

pestilence669
sumber
7
Utas Python cukup nyata, berbeda dengan Ruby misalnya. Di bawah tenda mereka diimplementasikan sebagai utas OS asli, setidaknya di Unix / Linux dan Windows. Mungkin Anda mengacu pada GIL, tetapi itu tidak membuat utasnya menjadi kurang nyata ...
Eli Bendersky
2
Eli benar tentang utas Python, tetapi poin Pestilence bahwa Anda ingin menggunakan kumpulan utas juga benar. Hal terakhir yang ingin Anda lakukan dalam kasus ini adalah mencoba memulai utas terpisah untuk masing-masing permintaan 100 ribu secara bersamaan.
Adam Crossland
1
Igor, Anda tidak dapat memposting potongan kode secara pantas dalam komentar, tetapi Anda dapat mengedit pertanyaan Anda dan menambahkannya di sana.
Adam Crossland
Sampar: berapa banyak antrian dan utas per antrian yang akan Anda rekomendasikan untuk solusi saya?
IgorGanapolsky
ditambah ini adalah tugas I / O terikat tidak terikat CPU, GIL sebagian besar mempengaruhi tugas terikat CPU
PirateApp