Cách nhanh nhất để gửi 100.000 yêu cầu HTTP trong Python là gì?


286

Tôi đang mở một tệp có 100.000 URL. Tôi cần gửi yêu cầu HTTP đến từng URL và in mã trạng thái. Tôi đang sử dụng Python 2.6 và cho đến nay đã xem xét nhiều cách khó hiểu Python thực hiện xâu chuỗi / đồng thời. Tôi thậm chí đã xem thư viện đồng quy python , nhưng không thể tìm ra cách viết chương trình này một cách chính xác. Có ai gặp phải một vấn đề tương tự? Tôi đoán nói chung tôi cần biết cách thực hiện hàng ngàn tác vụ trong Python nhanh nhất có thể - tôi cho rằng điều đó có nghĩa là 'đồng thời'.


47
Đảm bảo rằng bạn chỉ thực hiện yêu cầu CHÍNH (để bạn không tải xuống toàn bộ tài liệu). Xem: stackoverflow.com/questions/107405/
Mạnh

5
Điểm tuyệt vời, Kalmi. Nếu tất cả những gì Igor muốn là trạng thái của yêu cầu, những yêu cầu 100K này sẽ diễn ra nhanh hơn, nhiều hơn, nhanh hơn nhiều. Nhanh hơn nhiều.
Adam Crossland

1
Bạn không cần chủ đề cho việc này; cách hiệu quả nhất có thể là sử dụng một thư viện không đồng bộ như Twisted.
jemfinch

3
đây là các ví dụ mã dựa trên gevent, xoắn và asyncio (được thử nghiệm trên 1000000 yêu cầu)
jfs

4
@ TarnayKálmán có thể requests.getrequests.head(nghĩa là yêu cầu trang so với yêu cầu chính) để trả về các mã trạng thái khác nhau, vì vậy đây không phải là lời khuyên tốt nhất
AlexG

Câu trả lời:


201

Giải pháp xoắn:

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)

Cái này nhanh hơn một chút so với giải pháp xoắn và sử dụng ít CPU hơn.


10
@Kalmi, tại sao bạn lại đặt Queue thành concurrent*2?
Marcel Wilson

8
Đừng quên đóng kết nối conn.close() . Mở quá nhiều kết nối http có thể làm dừng tập lệnh của bạn tại một số điểm và ăn bộ nhớ.
Aamir Adnan

4
@hyh, Queuemô-đun đã được đổi tên thành queuePython 3. Đây là mã Python 2.
Tarnay Kálmán

3
Bạn có thể đi nhanh hơn bao nhiêu nếu bạn muốn nói chuyện với máy chủ SAME mỗi lần, bằng cách duy trì kết nối? Điều này thậm chí có thể được thực hiện trên các luồng, hoặc với một kết nối liên tục cho mỗi luồng?
mdurant

2
@mptevsion, nếu bạn đang sử dụng CPython, bạn có thể (ví dụ) chỉ cần thay thế "trạng thái in, url" bằng "my_global_list.append ((status, url))". (Hầu hết các hoạt động trên) danh sách đều được xử lý an toàn theo luồng trong CPython (và một số triển khai python khác) do GIL, vì vậy điều này là an toàn để thực hiện.
Tarnay Kálmán

54

Một giải pháp sử dụng thư viện mạng không đồng bộ 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()

7
Mã này đang sử dụng I / O mạng không chặn và không có bất kỳ hạn chế nào. Nó có thể mở rộng đến hàng chục ngàn kết nối mở. Nó sẽ chạy trong một luồng duy nhất nhưng sẽ nhanh hơn bất kỳ giải pháp luồng nào. Thanh toán không chặn I / O en.wikipedia.org/wiki/Asynyncous_I/O
vào

1
Bạn có thể giải thích những gì đang xảy ra ở đây với biến i toàn cầu? Một số loại kiểm tra lỗi?
LittleBulkTables

4
Nó là một bộ đếm để xác định khi nào thoát khỏi `` ioloop` - vì vậy khi nào bạn hoàn thành.
Michael D Corner

1
@AndrewScottEvans cho rằng bạn đang sử dụng python 2.7 và proxy
Dejell

5
@Guy Avraham Chúc may mắn nhận được sự giúp đỡ về kế hoạch ddos ​​của bạn.
Walter

50

Mọi thứ đã thay đổi khá nhiều kể từ năm 2010 khi điều này được đăng và tôi chưa thử tất cả các câu trả lời khác nhưng tôi đã thử một vài câu và tôi thấy điều này hoạt động tốt nhất với tôi khi sử dụng python3.6.

Tôi đã có thể tìm nạp khoảng 150 tên miền duy nhất mỗi giây chạy trên 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())

1
Tôi chỉ hỏi vì tôi không biết nhưng liệu những thứ tương lai này có thể được thay thế bằng async / await không?
TankorSmash

1
Nó có thể, nhưng tôi đã tìm thấy ở trên để làm việc tốt hơn. bạn có thể sử dụng aiohttp nhưng nó không phải là một phần của lib tiêu chuẩn và đang thay đổi khá nhiều. Nó hoạt động nhưng tôi không thấy nó hoạt động tốt. Tôi nhận được tỷ lệ lỗi cao hơn khi tôi sử dụng nó và trong suốt cuộc đời tôi, tôi không thể làm cho nó hoạt động tốt như tương lai đồng thời mặc dù trên lý thuyết Có vẻ như nó sẽ hoạt động tốt hơn, xem: stackoverflow.com/questions/45800857/ nếu bạn làm cho nó hoạt động tốt xin vui lòng gửi câu trả lời của bạn để tôi có thể kiểm tra nó.
Glen Thompson

1
Đây là một nitpick, nhưng tôi nghĩ rằng nó sẽ sạch hơn rất nhiều khi đặt time1 = time.time()ở đầu vòng lặp for và time2 = time.time()ngay sau vòng lặp for.
Matt M.

Tôi đã thử đoạn trích của bạn, bằng cách nào đó nó thực thi hai lần. Tôi có làm điều gì sai? Hoặc nó có nghĩa là để chạy hai lần? Nếu đó là trường hợp sau, bạn cũng có thể giúp tôi hiểu làm thế nào để nó kích hoạt hai lần?
Ronnie

1
Nó không nên chạy hai lần. Không chắc chắn tại sao bạn đang nhìn thấy điều đó.
Glen Thompson

40

Chủ đề hoàn toàn không phải là câu trả lời ở đây. Họ sẽ cung cấp cả tắc nghẽn quy trình và nhân, cũng như giới hạn thông lượng không được chấp nhận nếu mục tiêu chung là "cách nhanh nhất".

Một chút twistedHTTPkhách hàng không đồng bộ của nó sẽ cho bạn kết quả tốt hơn nhiều.


ironfroggy: Tôi đang nghiêng về tình cảm của bạn. Tôi đã thử thực hiện giải pháp của mình với các luồng và hàng đợi (đối với các biến đổi tự động), nhưng bạn có thể tưởng tượng được mất bao lâu để đưa vào hàng đợi với 100.000 thứ không ?? Tôi vẫn đang chơi xung quanh với các tùy chọn và đề xuất khác nhau của mọi người về chủ đề này và có lẽ Twisted sẽ là một giải pháp tốt.
IgorGanapolsky

2
Bạn có thể tránh việc xếp hàng với 100k thứ. Chỉ cần xử lý từng mục một từ đầu vào của bạn, sau đó khởi chạy một chuỗi để xử lý yêu cầu tương ứng với từng mục. (Như tôi đã mô tả dưới đây, sử dụng một sợi phóng để bắt đầu chủ đề yêu cầu HTTP khi đếm chủ đề của bạn là bên dưới một số ngưỡng Làm cho bài viết kết quả ra vào một URL mapping dict để phản ứng, hoặc các bộ append vào một danh sách..)
Erik Garrison

ironfroggy: Ngoài ra, tôi tò mò về những tắc nghẽn mà bạn đã tìm thấy khi sử dụng các chủ đề Python? Và làm thế nào để các chủ đề Python tương tác với nhân hệ điều hành?
Erik Garrison

Hãy chắc chắn rằng bạn cài đặt lò phản ứng epoll; nếu không, bạn sẽ sử dụng select / poll, và nó sẽ rất chậm. Ngoài ra, nếu bạn thực sự cố gắng mở 100.000 kết nối đồng thời (giả sử chương trình của bạn được viết theo cách đó và các URL nằm trên các máy chủ khác nhau), bạn sẽ cần điều chỉnh HĐH để không bị hết mô tả tập tin, cổng phù du, v.v. (có thể dễ dàng hơn để đảm bảo rằng bạn không có nhiều hơn, giả sử, 10.000 kết nối nổi bật cùng một lúc).
Đánh dấu

erikg: bạn đã đề xuất một ý tưởng tuyệt vời. Tuy nhiên, kết quả tốt nhất tôi có thể đạt được với 200 chủ đề là xấp xỉ. 6 phút. Tôi chắc chắn có nhiều cách để thực hiện điều này trong thời gian ngắn hơn ... Mark N: nếu Twisted là cách tôi quyết định đi, thì lò phản ứng epoll chắc chắn hữu ích. Tuy nhiên, nếu tập lệnh của tôi sẽ được chạy từ nhiều máy, điều đó có bắt buộc phải cài đặt Twisted trên máy EACH không? Tôi không biết liệu tôi có thể thuyết phục sếp của mình đi theo con đường đó không ...
IgorGanapolsky

20

Tôi biết đây là một câu hỏi cũ, nhưng trong Python 3.7 bạn có thể làm điều này bằng cách sử dụng asyncioaiohttp.

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

Bạn có thể đọc thêm về nó và xem một ví dụ ở đây .


Điều này có giống với C # async / await và Kotlin Coroutines không?
IgorGanapolsky

@IgorGanapolsky, vâng, nó rất giống với C # async / await. Tôi không quen thuộc với Quân đoàn Kotlin.
Marius Stănescu

@sandyp, tôi không chắc nó có hoạt động không, nhưng nếu bạn muốn thử, bạn sẽ phải sử dụng UnixConnector cho aiohttp. Đọc thêm tại đây: docs.aiohttp.org/en/urdy/client_Vference.html#connector .
Marius Stănescu

Cảm ơn @ MariusStănescu. Đó chính xác là những gì tôi đã sử dụng.
sandp

+1 để hiển thị asyncio.gather (* tác vụ). đây là một đoạn như vậy mà tôi đã sử dụng: urls= [fetch(construct_fetch_url(u),idx) for idx, u in enumerate(some_URI_list)] results = await asyncio.gather(*urls)
Ashwini Kumar

19

Sử dụng grequests , đó là sự kết hợp của các yêu cầu + mô đun Gevent.

GRequests cho phép bạn sử dụng các Yêu cầu với Gevent để thực hiện các Yêu cầu HTTP không đồng bộ một cách dễ dàng.

Cách sử dụng rất đơn giản:

import grequests

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

Tạo một tập hợp các Yêu cầu chưa gửi:

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

Gửi tất cả chúng cùng một lúc:

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

7
gevent hiện hỗ trợ python 3
Benjamin Toueg 13/08/2015

14
grequests không phải là một phần của các yêu cầu thông thường và dường như phần lớn không được nhắc
Thom

8

Một cách tiếp cận tốt để giải quyết vấn đề này là trước tiên hãy viết mã cần thiết để có được một kết quả, sau đó kết hợp mã luồng để song song hóa ứng dụng.

Trong một thế giới hoàn hảo, điều này đơn giản có nghĩa là đồng thời bắt đầu 100.000 luồng kết quả đưa ra kết quả của chúng vào một từ điển hoặc danh sách để xử lý sau này, nhưng trong thực tế, bạn bị giới hạn về số lượng yêu cầu HTTP song song bạn có thể đưa ra theo cách này. Tại địa phương, bạn có giới hạn về số lượng ổ cắm bạn có thể mở đồng thời, bao nhiêu luồng thực thi mà trình thông dịch Python của bạn sẽ cho phép. Từ xa, bạn có thể bị giới hạn số lượng kết nối đồng thời nếu tất cả các yêu cầu chống lại một máy chủ hoặc nhiều máy chủ. Những hạn chế này có thể sẽ khiến bạn phải viết tập lệnh theo cách chỉ thăm dò một phần nhỏ các URL bất kỳ lúc nào (100, như một poster khác được đề cập, có thể là kích thước nhóm chủ đề khá, mặc dù bạn có thể thấy rằng bạn có thể triển khai thành công nhiều hơn nữa).

Bạn có thể làm theo mẫu thiết kế này để giải quyết vấn đề trên:

  1. Bắt đầu một luồng khởi chạy các luồng yêu cầu mới cho đến khi số luồng hiện đang chạy (bạn có thể theo dõi chúng thông qua threading.active_count () hoặc bằng cách đẩy các đối tượng luồng vào cấu trúc dữ liệu) là> = số lượng yêu cầu đồng thời tối đa của bạn (giả sử 100) , sau đó ngủ trong một thời gian ngắn. Chuỗi này sẽ chấm dứt khi không còn URL nào để xử lý. Do đó, chuỗi sẽ tiếp tục thức dậy, khởi chạy các luồng mới và ngủ cho đến khi bạn kết thúc.
  2. Có các luồng yêu cầu lưu trữ kết quả của chúng trong một số cấu trúc dữ liệu để lấy và xuất sau này. Nếu cấu trúc bạn đang lưu trữ kết quả là một listhoặc dicttrong CPython, bạn có thể nối hoặc chèn các mục duy nhất từ ​​các luồng của mình một cách an toàn mà không cần khóa , nhưng nếu bạn ghi vào tệp hoặc yêu cầu tương tác dữ liệu chéo phức tạp hơn, bạn nên sử dụng khóa loại trừ lẫn nhau để bảo vệ nhà nước này khỏi tham nhũng .

Tôi sẽ đề nghị bạn sử dụng các mô-đun luồng . Bạn có thể sử dụng nó để khởi chạy và theo dõi các chủ đề đang chạy. Hỗ trợ luồng của Python hoàn toàn trống rỗng, nhưng mô tả về vấn đề của bạn cho thấy rằng nó hoàn toàn đủ cho nhu cầu của bạn.

Cuối cùng, nếu bạn muốn xem một ứng dụng đơn giản xinh đẹp của một ứng dụng mạng song song viết bằng Python, hãy kiểm tra ssh.py . Đó là một thư viện nhỏ sử dụng luồng Python để song song nhiều kết nối SSH. Thiết kế đủ gần với yêu cầu của bạn mà bạn có thể thấy nó là một tài nguyên tốt.


1
erikg: việc ném hàng đợi vào phương trình của bạn có hợp lý không (đối với khóa loại trừ lẫn nhau)? Tôi nghi ngờ rằng GIL của Python không hướng tới việc chơi với hàng ngàn luồng.
IgorGanapolsky

Tại sao bạn cần khóa loại trừ lẫn nhau để ngăn chặn việc tạo quá nhiều luồng? Tôi nghi ngờ tôi hiểu nhầm thuật ngữ. Bạn có thể theo dõi các luồng đang chạy trong hàng đợi luồng, loại bỏ chúng khi chúng hoàn thành và thêm nhiều hơn đến giới hạn luồng đã nói. Nhưng trong một trường hợp đơn giản, chẳng hạn như câu hỏi bạn cũng có thể xem số lượng luồng hoạt động trong quy trình Python hiện tại, đợi cho đến khi nó giảm xuống dưới ngưỡng và khởi chạy nhiều luồng hơn đến ngưỡng như mô tả. Tôi đoán bạn có thể coi đây là một khóa ẩn, nhưng không yêu cầu khóa rõ ràng afaik.
Erik Garrison

erikg: không nhiều chủ đề chia sẻ trạng thái? Trên trang 304 trong cuốn sách "Python cho Quản trị hệ thống Unix và Linux" của O'Reilly có ghi: "... việc sử dụng luồng mà không có hàng đợi làm cho nó phức tạp hơn nhiều người có thể xử lý một cách thực tế. mô-đun nếu bạn thấy bạn cần sử dụng các luồng. Tại sao? Bởi vì mô-đun hàng đợi cũng giảm bớt sự cần thiết phải bảo vệ dữ liệu bằng các mutexes vì ​​chính hàng đợi đã được bảo vệ bởi một mutex. " Một lần nữa, tôi hoan nghênh quan điểm của bạn về điều này.
IgorGanapolsky

Igor: Bạn hoàn toàn đúng khi sử dụng khóa. Tôi đã chỉnh sửa bài viết để phản ánh điều này. Điều đó nói rằng, kinh nghiệm thực tế với python cho thấy rằng bạn không cần phải khóa các cấu trúc dữ liệu mà bạn sửa đổi nguyên bản từ các luồng của mình, chẳng hạn như list.append hoặc bằng cách thêm khóa băm. Lý do, tôi tin rằng, là GIL, cung cấp các hoạt động như list.append với một mức độ nguyên tử. Tôi hiện đang chạy thử nghiệm để xác minh điều này (sử dụng các luồng 10k để nối các số 0-9999 vào danh sách, kiểm tra xem tất cả các phụ lục có hoạt động không). Sau gần 100 lần lặp lại, bài kiểm tra đã không thất bại.
Erik Garrison

Igor: Tôi đã hỏi một câu hỏi khác về chủ đề này: stackoverflow.com/questions/2740435/
Erik Garrison

7

Nếu bạn đang tìm kiếm để có được hiệu suất tốt nhất có thể, bạn có thể muốn xem xét sử dụng I / O không đồng bộ thay vì các luồng. Chi phí hoạt động liên kết với hàng ngàn luồng hệ điều hành là không tầm thường và việc chuyển đổi ngữ cảnh trong trình thông dịch Python còn bổ sung nhiều hơn nữa trên đầu trang. Việc phân luồng chắc chắn sẽ hoàn thành công việc nhưng tôi nghi ngờ rằng một tuyến không đồng bộ sẽ cung cấp hiệu suất tổng thể tốt hơn.

Cụ thể, tôi đề xuất ứng dụng web async trong thư viện Twisted ( http://www.twistedmatrix.com ). Nó có một đường cong học tập dốc được thừa nhận nhưng nó khá dễ sử dụng một khi bạn nắm được cách tốt trong phong cách lập trình không đồng bộ của Twisted.

API ứng dụng khách không đồng bộ của HowTo trên Twisted có sẵn tại:

http://twistedmatrix.com/document/cản/web/howto/client.html


Rakis: Tôi hiện đang xem xét I / O không đồng bộ và không chặn. Tôi cần học nó tốt hơn trước khi tôi thực hiện nó. Một nhận xét tôi muốn đưa ra trên bài đăng của mình là không thể (ít nhất là theo bản phân phối Linux của tôi) để sinh ra "hàng ngàn luồng hệ điều hành". Có số lượng chủ đề tối đa mà Python sẽ cho phép bạn sinh ra trước khi chương trình bị phá vỡ. Và trong trường hợp của tôi (trên CentOS 5) số lượng chủ đề tối đa là 303.
IgorGanapolsky

Đó là điều tốt để biết. Tôi chưa bao giờ thử sinh sản nhiều hơn một số ít Python trong một lần nhưng tôi dự kiến ​​sẽ có thể tạo ra nhiều hơn thế trước khi nó bị đánh bom.
Rakis

6

Một giải pháp:

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

Thời gian thử nghiệm:

[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

Thời gian

bix.hu is ~10 ms away from me
godaddy.com: ~170 ms
google.com: ~30 ms

6
Sử dụng Twisted như một luồng xử lý là bỏ qua hầu hết các lợi ích bạn có thể nhận được từ nó. Bạn nên sử dụng máy khách HTTP async thay thế.
Jean-Paul Calderone

1

Sử dụng một nhóm luồng là một lựa chọn tốt, và sẽ làm cho điều này khá dễ dàng. Thật không may, python không có một thư viện tiêu chuẩn giúp cho các nhóm luồng cực kỳ dễ dàng. Nhưng đây là một thư viện phong nha sẽ giúp bạn bắt đầu: http://www.chrisarndt.de/projects/threadpool/

Mã ví dụ từ trang web của họ:

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

Hi vọng điêu nay co ich.


Tôi khuyên bạn nên chỉ định q_size cho ThreadPool như thế này: ThreadPool (Poolsize, q_size = 1000) để bạn sẽ không có 100000 đối tượng WorkRequest trong bộ nhớ. "Nếu q_size> 0 kích thước của hàng đợi yêu cầu công việc bị giới hạn và khối nhóm luồng khi hàng đợi đầy và nó cố gắng đặt thêm các yêu cầu công việc vào đó (xem putRequestphương thức), trừ khi bạn cũng sử dụng timeoutgiá trị dương cho putRequest."
Tarnay Kálmán

Cho đến nay tôi đang cố gắng thực hiện giải pháp threadpool - như đề xuất. Tuy nhiên, tôi không hiểu danh sách tham số trong hàm makeRequests. Một số_callable, list_of_args, gọi lại là gì? Có lẽ nếu tôi thấy một đoạn mã thực sự có ích. Tôi ngạc nhiên rằng tác giả của thư viện đó đã không đăng bất kỳ ví dụ nào.
IgorGanapolsky

some_callable là chức năng của bạn mà tất cả công việc của bạn được thực hiện (kết nối với máy chủ http). list_of_args là các đối số sẽ được truyền vào some_callabe. gọi lại là một chức năng sẽ được gọi khi luồng công nhân được thực hiện. Phải có hai đối số, đối tượng worker (không cần quan tâm đến bản thân bạn với điều này thực sự) và kết quả mà công nhân lấy được.
Kevin Wiskia

1

Tạo epollđối tượng,
mở nhiều socket TCP của máy khách,
điều chỉnh bộ đệm gửi của chúng nhiều hơn một chút so với tiêu đề yêu cầu,
gửi tiêu đề yêu cầu - cần ngay lập tức, chỉ cần đặt vào bộ đệm, đăng ký socket trong epollđối tượng,
thực hiện .polltrên epollobect,
đọc 3 đầu tiên byte từ mỗi socket từ .poll,
ghi chúng sys.stdouttheo sau \n(không tuôn ra), đóng socket của máy khách.

Giới hạn số lượng ổ cắm được mở đồng thời - xử lý lỗi khi ổ cắm được tạo. Tạo một ổ cắm mới chỉ khi một cái khác được đóng lại.
Điều chỉnh giới hạn hệ điều hành.
Hãy thử chuyển sang một vài (không nhiều) quy trình: điều này có thể giúp sử dụng CPU hiệu quả hơn một chút.


@IgorGanapolsky Phải. Tôi sẽ ngạc nhiên khác. Nhưng nó chắc chắn cần thử nghiệm.
George Sovetov

0

Đối với trường hợp của bạn, phân luồng có thể sẽ thực hiện thủ thuật vì có lẽ bạn sẽ dành nhiều thời gian nhất để chờ phản hồi. Có các mô-đun hữu ích như Hàng đợi trong thư viện chuẩn có thể giúp ích.

Tôi đã làm một điều tương tự với việc tải xuống các tệp song song trước đó và nó đủ tốt cho tôi, nhưng nó không ở quy mô mà bạn đang nói đến.

Nếu tác vụ của bạn bị ràng buộc nhiều CPU hơn, bạn có thể muốn xem xét mô-đun đa xử lý , điều này sẽ cho phép bạn sử dụng nhiều CPU / lõi / luồng hơn (nhiều quá trình sẽ không chặn nhau vì khóa là trên mỗi tiến trình)


Điều duy nhất tôi muốn đề cập là sinh ra nhiều quá trình có thể tốn kém hơn so với sinh ra nhiều luồng. Ngoài ra, không có hiệu suất rõ ràng trong việc gửi 100.000 yêu cầu HTTP với nhiều quy trình so với nhiều luồng.
IgorGanapolsky

0

Cân nhắc sử dụng Cối xay gió , mặc dù Cối xay gió có thể không làm được nhiều chủ đề.

Bạn có thể làm điều đó với một tập lệnh Python cuộn bằng tay trên 5 máy, mỗi máy kết nối ra ngoài bằng các cổng 40000-60000, mở 100.000 kết nối cổng.

Ngoài ra, có thể giúp thực hiện một thử nghiệm mẫu với ứng dụng QA có luồng độc đáo như OpenSTA để có ý tưởng về mỗi máy chủ có thể xử lý được bao nhiêu.

Ngoài ra, hãy thử xem xét chỉ sử dụng Perl đơn giản với lớp LWP :: ConnCache. Có thể bạn sẽ nhận được nhiều hiệu suất hơn (nhiều kết nối hơn) theo cách đó.


0

Máy khách web async xoắn này đi khá nhanh.

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

Tôi thấy rằng sử dụng tornadogói là cách nhanh nhất và đơn giản nhất để đạt được điều này:

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

Cách dễ nhất là sử dụng thư viện luồng tích hợp của Python. Chúng không phải là "thực" / chuỗi nhân Chúng có vấn đề (như tuần tự hóa), nhưng đủ tốt. Bạn muốn có một hàng đợi & chuỗi chủ đề. Một lựa chọn là ở đây , nhưng nó là tầm thường để viết của riêng bạn. Bạn không thể song song tất cả 100.000 cuộc gọi, nhưng bạn có thể loại bỏ 100 (hoặc hơn) cuộc gọi đó cùng một lúc.


7
Các chủ đề của Python hoàn toàn có thật, trái ngược với Ruby chẳng hạn. Dưới vỏ bọc, chúng được triển khai như các luồng hệ điều hành gốc, ít nhất là trên Unix / Linux và Windows. Có thể bạn đang đề cập đến GIL, nhưng nó không làm cho các chủ đề trở nên kém thực tế ...
Eli Bendersky

2
Eli nói đúng về các luồng của Python, nhưng quan điểm của Pestilence rằng bạn muốn sử dụng nhóm luồng cũng đúng. Điều cuối cùng mà bạn muốn làm trong trường hợp này là cố gắng bắt đầu một luồng riêng biệt cho mỗi yêu cầu 100K đồng thời.
Adam Crossland

1
Igor, bạn không thể đăng đoạn mã một cách hợp lý trong các bình luận, nhưng bạn có thể chỉnh sửa câu hỏi của mình và thêm chúng vào đó.
Adam Crossland

Pestilence: bạn muốn giới thiệu bao nhiêu hàng đợi và chủ đề cho mỗi hàng đợi cho giải pháp của tôi?
IgorGanapolsky

cộng với đây là nhiệm vụ ràng buộc I / O không bị ràng buộc CPU, GIL ảnh hưởng lớn đến các nhiệm vụ bị ràng buộc CPU
PirateApp
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.