Python에서 100,000 개의 HTTP 요청을 보내는 가장 빠른 방법은 무엇입니까?


287

URL이 100,000 개인 파일을 열었습니다. 각 URL에 HTTP 요청을 보내고 상태 코드를 인쇄해야합니다. 저는 Python 2.6을 사용하고 있으며 지금까지 Python이 스레딩 / 동시성을 구현하는 많은 혼란스러운 방법을 살펴 보았습니다. 파이썬 동시성 라이브러리를 보았지만이 프로그램을 올바르게 작성하는 방법을 알 수는 없습니다. 누구나 비슷한 문제를 겪었습니까? 필자는 일반적으로 가능한 빨리 파이썬에서 수천 개의 작업을 수행하는 방법을 알아야한다고 생각합니다. '동시'를 의미한다고 생각합니다.


47
HEAD 요청 만 수행해야합니다 (전체 문서를 다운로드하지 않도록). 참조 : stackoverflow.com/questions/107405/...
Tarnay 칼만

5
칼미. 모든 Igor가 원하는 것이 요청의 상태라면, 이러한 100K 요청은 훨씬 더 빠르게 진행됩니다. 훨씬 빠릅니다.
Adam Crossland

1
이를 위해 스레드가 필요하지 않습니다. 가장 효율적인 방법은 Twisted와 같은 비동기 라이브러리를 사용하는 것입니다.
jemfinch

3
다음은 gevent, twisted 및 asyncio 기반 코드 예제입니다 (1000000 요청에서 테스트 됨)
jfs

4
@ TarnayKálmán은 다른 상태 코드를 반환 할 수 requests.get있고 requests.head(예 : 페이지 요청 대 헤드 요청) 이것이 최선의 조언이 아님
AlexG

답변:


200

트위스트리스 솔루션 :

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)

이것은 꼬인 솔루션보다 약간 빠르며 CPU를 덜 사용합니다.


10
@ 칼미, 왜 큐를 설정 concurrent*2합니까?
Marcel Wilson

8
연결닫는 것을 잊지 마십시오 conn.close(). 너무 많은 http 연결을 열면 어느 시점에서 스크립트가 중단되고 메모리가 소모 될 수 있습니다.
Aamir Adnan

4
@hyh, Queue모듈은 queue파이썬 3에서 이름이 바뀌 었습니다 . 이것은 파이썬 2 코드입니다.
Tarnay Kálmán 2012

3
연결을 유지하여 SAME 서버와 매번 대화하려면 얼마나 빨리 갈 수 있습니까? 스레드 전체에서 또는 스레드 당 하나의 영구 연결로 수행 할 수 있습니까?
mdurant

2
@mptevsion, CPython을 사용하는 경우, 예를 들어 "print status, url"을 "my_global_list.append ((status, url))"로 바꿀 수 있습니다. (대부분의 작업) 목록은 GIL로 인해 CPython (및 일부 다른 파이썬 구현)에서 암시 적으로 스레드 안전하므로 안전합니다.
Tarnay Kálmán

54

토네이도 비동기 네트워킹 라이브러리를 사용하는 솔루션

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
이 코드는 비 차단 네트워크 I / O를 사용하고 있으며 제한이 없습니다. 수만 개의 개방형 연결로 확장 할 수 있습니다. 단일 스레드에서 실행되지만 스레딩 솔루션보다 훨씬 빠릅니다. 비 차단 I / O 체크 아웃 en.wikipedia.org/wiki/Asynchronous_I/O
2013 년

1
전역 i 변수로 여기서 무슨 일이 일어나고 있는지 설명 할 수 있습니까? 어떤 종류의 오류 검사?
LittleBobbyTables

4
``ioloop ''를 언제 종료해야하는지 결정하는 카운터입니다.
Michael Dorner

1
@AndrewScottEvans 그것은 파이썬 2.7과 프록시를 사용한다고 가정했습니다
Dejell

5
@Guy Avraham 행운의 ddos ​​계획에 도움을 받고 있습니다.
Walter

51

2010 년 이후이 게시물이 게시되어 다른 답변을 모두 시도하지는 않았지만 몇 가지를 시도했지만 python3.6을 사용하여 가장 잘 작동하는 것으로 나타났습니다.

AWS에서 실행되는 초당 약 150 개의 고유 도메인을 가져올 수있었습니다.

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
나는 모르기 때문에 묻고 있지만이 선물 물건은 async / await로 대체 될 수 있습니까?
TankorSmash

1
가능하지만 위의 작업이 더 효과적이라는 것을 알았습니다. aiohttp를 사용할 수 있지만 표준 lib의 일부는 아니며 상당히 많이 변경되고 있습니다. 그것은 효과가 있지만 나는 그것이 잘 작동하는 것을 찾지 못했습니다. 나는 그것을 사용할 때 더 높은 오류율을 얻었고 내 인생에서 이론적으로는 동시 선물뿐만 아니라 작동하지 않을 수도 있습니다. 더 잘 작동하는 것 같습니다 : stackoverflow.com/questions/45800857/… 제대로 작동하면 테스트 할 수 있도록 답변을 게시하십시오.
Glen Thompson

1
이것은 nitpick이지만 time1 = time.time()for 루프의 상단과 for 루프 time2 = time.time()바로 뒤에 배치하는 것이 훨씬 깨끗하다고 ​​생각합니다 .
Matt M.

스 니펫을 테스트했는데 어떻게 든 두 번 실행되었습니다. 내가 뭔가 잘못하고 있습니까? 아니면 두 번 달리기를 의미합니까? 후자의 경우 두 번 트리거하는 방법을 이해하도록 도와 줄 수 있습니까?
Ronnie

1
두 번 실행해서는 안됩니다. 왜 그런지 모르겠습니다.
글렌 톰슨

40

스레드는 절대 대답이 아닙니다. 전체 목표가 "가장 빠른 방법"인 경우 허용되지 않는 처리량 제한뿐만 아니라 프로세스 및 커널 병목 현상을 모두 제공합니다.

약간 twisted의 비동기 HTTP클라이언트는 훨씬 나은 결과를 제공합니다.


ironfroggy : 나는 당신의 감정에 기대어 있습니다. 스레드와 대기열 (자동 뮤텍스)을 사용하여 솔루션을 구현하려고 시도했지만 대기열을 100,000 가지로 채우는 데 얼마나 오래 걸립니까? 나는 여전히이 스레드의 모든 사람들이 다른 옵션과 제안을 가지고 놀고 있으며 아마도 Twisted가 좋은 해결책 일 것입니다.
IgorGanapolsky

2
100k 가지로 대기열을 채우는 것을 피할 수 있습니다. 입력에서 한 번에 하나씩 항목을 처리 한 다음 스레드를 시작하여 각 항목에 해당하는 요청을 처리하십시오. (아래에서 설명하는 것처럼 스레드 수가 임계 값 미만인 경우 런처 스레드를 사용하여 HTTP 요청 스레드를 시작하십시오. 스레드가 결과를 dict 맵핑 URL에 응답하여 응답으로 쓰거나 튜플을 목록에 추가하도록하십시오.)
Erik 주둔지

ironfroggy : 또한 파이썬 스레드를 사용하여 어떤 병목 현상이 발생했는지 궁금합니다. 그리고 파이썬 스레드는 어떻게 OS 커널과 상호 작용합니까?
Erik Garrison

epoll 반응기를 설치했는지 확인하십시오. 그렇지 않으면 select / poll을 사용하게되며 매우 느려집니다. 또한 실제로 100,000 개의 연결을 동시에 열려고하면 (프로그램이 그런 식으로 작성되고 URL이 다른 서버에 있다고 가정) OS가 부족하지 않도록 OS를 조정해야합니다 파일 디스크립터, 임시 포트 등을 포함합니다 (한 번에 10,000 개의 미해결 연결이 없는지 확인하는 것이 더 쉽습니다).
Mark Nottingham

erikg : 좋은 아이디어를 추천했습니다. 그러나 200 스레드로 달성 할 수있는 가장 좋은 결과는 약입니다. 6 분 더 짧은 시간에 이것을 달성 할 수있는 방법이 있다고 확신합니다 ... Mark N : Twisted가 제가 결정한 방법이라면, epoll 원자로가 확실히 유용합니다. 그러나 스크립트를 여러 컴퓨터에서 실행하는 경우 각 컴퓨터에 Twisted를 설치해야합니까? 상사가 그 길로 가도록 설득력이 있는지 모르겠습니다 ...
IgorGanapolsky

21

나는 이것이 오래된 질문이라는 것을 알고 있지만 Python 3.7에서는 asyncioand를 사용 하여이 작업을 수행 할 수 있습니다 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))

이에 대한 자세한 내용은 여기 에서 예를 참조 하십시오 .


이것은 C # async / await 및 Kotlin Coroutines 와 비슷 합니까?
IgorGanapolsky

@ IgorGanapolsky, 예, C # async / await와 매우 유사합니다. 나는 Kotlin Coroutines에 익숙하지 않습니다.
Marius Stănescu

@ sandyp, 그것이 작동하는지 확실하지 않지만 시도하려면 aiohttp에 UnixConnector를 사용해야합니다. docs.aiohttp.org/en/stable/client_reference.html#connectors에서 자세히 알아보십시오 .
Marius Stănescu

감사합니다 @ MariusStănescu. 바로 내가 사용한 것입니다.
sandyp

asyncio.gather (* tasks)를 표시하기위한 +1 여기 urls= [fetch(construct_fetch_url(u),idx) for idx, u in enumerate(some_URI_list)] results = await asyncio.gather(*urls)
제가

19

grequests를 사용하십시오 . 그것은 request + Gevent 모듈의 조합입니다.

GRequests를 사용하면 Gevent과 함께 요청을 사용하여 비동기 HTTP 요청을 쉽게 만들 수 있습니다.

사용법은 간단합니다.

import grequests

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

보내지 않은 요청 세트를 작성하십시오.

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

동시에 모두 보내기 :

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

7
gevent는 이제 python 3을 지원합니다
Benjamin Toueg

14
grequests는 일반적인 요청의 일부가 아니며 크게 유지되지 않은 것 같습니다
Thom

8

이 문제를 해결하는 좋은 방법은 먼저 하나의 결과를 얻는 데 필요한 코드를 작성한 다음 스레딩 코드를 통합하여 응용 프로그램을 병렬화하는 것입니다.

완벽한 세계에서 이것은 단순히 나중에 처리하기 위해 사전 또는 목록으로 결과를 출력하는 10 만 개의 스레드를 동시에 시작한다는 것을 의미하지만 실제로이 방식으로 발행 할 수있는 병렬 HTTP 요청 수는 제한되어 있습니다. 로컬에서는 동시에 열 수있는 소켓 수, 파이썬 인터프리터가 허용하는 실행 스레드 수에 제한이 있습니다. 원격으로 모든 요청이 한 서버 또는 여러 서버에 대한 경우 동시 연결 수가 제한 될 수 있습니다. 이러한 제한 사항으로 인해 한 번에 적은 양의 URL 만 폴링하는 방식으로 스크립트를 작성해야 할 수도 있습니다. 다른 포스터에서 언급했듯이 100은 적절한 스레드 풀 크기 일 수 있습니다. 더 많은 것을 성공적으로 배포 할 수 있습니다).

이 디자인 패턴에 따라 위의 문제를 해결할 수 있습니다.

  1. 현재 실행중인 스레드 수 (threading.active_count ()를 통해 또는 스레드 객체를 데이터 구조로 푸시하여 스레드를 추적 할 수 있음)가 최대 동시 요청 수 (예 : 100)가 될 때까지 새 요청 스레드를 시작하는 스레드를 시작합니다. 그런 다음 짧은 시간 종료 동안 휴면 상태가됩니다. 처리 할 URL이 더 이상 없으면이 스레드가 종료되어야합니다. 따라서 스레드는 계속 깨어나고 새 스레드를 시작하며 완료 될 때까지 대기합니다.
  2. 요청 스레드가 결과를 나중에 검색 및 출력 할 수 있도록 일부 데이터 구조에 저장하도록하십시오. 결과를 저장하는 구조 가 CPython list또는 dictCPython 인 경우 잠금없이 스레드에서 고유 항목을 안전하게 추가하거나 삽입 할 수 있지만 파일에 쓰거나보다 복잡한 크로스 스레드 데이터 상호 작용 이 필요한 경우에는 이 상태를 손상으로부터 보호하기위한 상호 배제 잠금 .

스레딩 모듈 을 사용하는 것이 좋습니다 . 이를 사용하여 실행중인 스레드를 시작하고 추적 할 수 있습니다. 파이썬의 스레딩 지원은 거의 없지만 문제에 대한 설명은 귀하의 요구에 충분하다고 제안합니다.

마지막으로, 파이썬으로 작성된 병렬 네트워크 애플리케이션의 간단한 애플리케이션을 보려면 ssh.py를 확인하십시오 . Python 스레딩을 사용하여 많은 SSH 연결을 병렬화하는 작은 라이브러리입니다. 디자인은 요구 사항에 충분히 근접하여 좋은 리소스가 될 수 있습니다.


1
erikg : 큐에 방정식을 넣는 것이 합리적입니까 (상호 제외 잠금의 경우)? 파이썬의 GIL이 수천 개의 스레드로 연주하는 데 적합하지 않다고 생각합니다.
IgorGanapolsky

너무 많은 스레드 생성을 방지하기 위해 상호 배제 잠금이 필요한 이유는 무엇입니까? 나는 그 용어를 오해 한 것 같습니다. 스레드 대기열에서 실행중인 스레드를 추적하여 완료시 제거하고 해당 스레드 한계까지 더 추가 할 수 있습니다. 그러나 문제가되는 것과 같은 간단한 경우에는 현재 Python 프로세스의 활성 스레드 수를보고 임계 값 아래로 떨어질 때까지 기다렸다가 설명 된대로 임계 값까지 더 많은 스레드를 시작할 수 있습니다. 암시 적 잠금으로 간주 할 수는 있지만 명시 적 잠금은 필요하지 않습니다.
Erik Garrison

erikg : 여러 스레드가 상태를 공유하지 않습니까? O'Reilly의 책 "Python for Unix 및 Linux 시스템 관리"의 305 페이지에 "큐를 사용하지 않는 스레딩을 사용하면 많은 사람들이 현실적으로 처리 할 수있는 것보다 훨씬 복잡합니다. 항상 큐를 사용하는 것이 훨씬 더 좋습니다. 큐 모듈은 또한 큐 자체가 이미 뮤텍스에 의해 내부적으로 보호되어 있기 때문에 큐 모듈도 뮤텍스로 데이터를 명시 적으로 보호해야 할 필요성을 완화시키기 때문입니다. " 다시 한 번 이것에 대한 당신의 견해를 환영합니다.
IgorGanapolsky

Igor : 자물쇠를 사용해야한다는 것이 옳습니다. 이것을 반영하기 위해 게시물을 편집했습니다. 즉, 파이썬을 실제로 사용하면 list.append 또는 해시 키 추가와 같이 스레드에서 원자 적으로 수정하는 데이터 구조를 잠글 필요가 없습니다. 그 이유는 list.append와 같은 작업을 어느 정도 원 자성으로 제공하는 GIL 때문이라고 생각합니다. 현재 이것을 테스트하기 위해 테스트를 실행하고 있습니다 (10k 스레드를 사용하여 0-9999의 숫자를 목록에 추가하고 모든 추가 기능이 작동하는지 확인하십시오). 거의 100 번의 반복 후에도 테스트는 실패하지 않았습니다.
Erik Garrison

Igor :이 주제에 대해 또 다른 질문이 있습니다 : stackoverflow.com/questions/2740435/…
Erik Garrison

7

최상의 성능을 얻으려면 스레드 대신 비동기 I / O를 사용하는 것이 좋습니다. 수천 개의 OS 스레드와 관련된 오버 헤드는 사소한 것이 아니며 Python 인터프리터 내의 컨텍스트 전환은 그 위에 훨씬 더 추가됩니다. 스레딩은 확실히 작업을 수행하지만 비동기 경로가 더 나은 전반적인 성능을 제공 할 것으로 생각합니다.

특히, Twisted 라이브러리 ( http://www.twistedmatrix.com ) 의 비동기 웹 클라이언트를 제안합니다 . 그것은 확실히 가파른 학습 곡선을 가지고 있지만 Twisted의 비동기 프로그래밍 스타일을 잘 다루면 사용하기가 쉽습니다.

Twisted의 비동기 웹 클라이언트 API에 대한 HowTo는 다음에서 구할 수 있습니다.

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


Rakis : 현재 비동기 및 비 차단 I / O를 검토하고 있습니다. 구현하기 전에 더 잘 배울 필요가 있습니다. 귀하의 게시물에 대해 한 가지 의견은 (최소한 Linux 배포판에서는) "수천 개의 OS 스레드"를 생성하는 것이 불가능하다는 것입니다. 프로그램이 중단되기 전에 파이썬이 스폰 할 수있는 최대 스레드 수가 있습니다. 그리고 제 경우 (CentOS 5에서) 최대 스레드 수는 303입니다.
IgorGanapolsky

알아두면 좋습니다. 파이썬에서 한 번에 소수 이상의 스폰을 시도하지는 않았지만 폭탄이 터지기 전에 그보다 더 많은 것을 만들 수있을 것으로 기대했을 것입니다.
Rakis

6

해결책 :

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

시험 시간 :

[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

핑 타임 :

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

6
Twisted를 스레드 풀로 사용하면 얻을 수있는 이점이 대부분 무시됩니다. 대신 비동기 HTTP 클라이언트를 사용해야합니다.
Jean-Paul Calderone

1

스레드 풀을 사용하는 것이 좋은 옵션이며 상당히 쉽습니다. 불행히도 파이썬에는 스레드 풀을 매우 쉽게 만드는 표준 라이브러리가 없습니다. 그러나 여기에 시작 해야하는 괜찮은 라이브러리가 있습니다 : http://www.chrisarndt.de/projects/threadpool/

그들의 사이트에서 코드 예제 :

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

도움이 되었기를 바랍니다.


ThreadPool에 q_size를 다음과 같이 지정하는 것이 좋습니다. ThreadPool (poolsize, q_size = 1000) 메모리에 100000 개의 WorkRequest 객체가 없도록합니다. "만약q_size 0> 작업의 크기 요청 큐가 제한되며 스레드 풀 블록 큐가 가득 할 때 그것은 (참조 그것에서 더 많은 작업 요청을 넣어 시도 putRequest또한 긍정적를 사용하지 않는 방법) timeout가치를 putRequest."
Tarnay Kálmán

지금까지 제안 된대로 스레드 풀 솔루션을 구현하려고합니다. 그러나 makeRequests 함수의 매개 변수 목록을 이해하지 못합니다. some_callable, list_of_args, 콜백은 무엇입니까? 아마도 내가 도움이 될 실제 코드 스 니펫을 보았을 것입니다. 그 도서관의 저자가 어떤 예도 게시하지 않은 것에 놀랐습니다.
IgorGanapolsky

some_callable은 모든 작업이 완료된 함수입니다 (http 서버에 연결). list_of_args는 some_callabe에 전달 될 인수입니다. 콜백은 작업자 스레드가 완료 될 때 호출되는 함수입니다. 작업자 객체 (실제로 자신을 신경 쓰지 않아도 됨)와 작업자가 검색 한 결과의 두 가지 인수가 필요합니다.
케빈 Wiskia

1

만들기 epoll개체,
오픈 많은 클라이언트 TCP 소켓을
요청 헤더보다 조금 더 될 자신의 전송 버퍼를 조정
요청 헤더를 보낼 - 그것은 단지 버퍼에 배치, 즉시 수에 소켓을 등록해야 epoll객체,
수행 .pollepollobect,
처음 3 읽기 바이트에서 각각의 소켓에서 .poll,
에 기록 sys.stdout다음 \n(플러시하지 않습니다), 가까운 클라이언트 소켓.

동시에 열린 소켓 수 제한 — 소켓을 만들 때 오류를 처리합니다. 다른 소켓이 닫힌 경우에만 새 소켓을 작성하십시오.
OS 한계를 조정하십시오.
몇 가지 (많지 않은) 프로세스로 분기 해보십시오. 이렇게하면 CPU를 좀 더 효과적으로 사용할 수 있습니다.


@IgorGanapolsky해야합니다. 그렇지 않으면 놀랐습니다. 그러나 확실히 실험이 필요합니다.
George Sovetov

0

귀하의 경우 스레딩은 아마도 응답을 기다리는 데 대부분의 시간을 소비 할 것이므로 아마도 트릭을 수행 할 것입니다. 표준 라이브러리에는 Queue 와 같은 유용한 모듈 이 있습니다.

나는 파일을 병렬로 다운로드하는 것과 비슷한 일을했고 나에게 충분했지만 당신이 이야기하는 규모에 맞지 않았습니다.

작업이 CPU에 더 많이 묶인 경우 더 많은 CPU / 코어 / 스레드를 사용할 수 있는 멀티 프로세싱 모듈을 살펴볼 수 있습니다 (프로세스 당 잠금이므로 서로 차단하지 않는 더 많은 프로세스)


내가 언급하고 싶은 유일한 것은 여러 프로세스를 생성하는 것이 여러 스레드를 생성하는 것보다 비쌀 수 있다는 것입니다. 또한 여러 프로세스와 여러 스레드가있는 100,000 개의 HTTP 요청을 보내는 경우 성능이 크게 향상되지 않습니다.
IgorGanapolsky

0

사용을 고려하십시오 풍차 풍차 아마 많은 스레드를 수행하지 못할지라도.

5 개의 머신에서 수동 롤링 된 Python 스크립트를 사용하여이를 수행 할 수 있습니다. 각 머신은 포트 40000-60000을 사용하여 아웃 바운드 연결하여 100,000 개의 포트 연결을 엽니 다.

또한 OpenSTA 와 같이 훌륭하게 스레드 된 QA 앱으로 샘플 테스트를 수행하는 데 도움이 될 수 있습니다 각 서버가 얼마나 많은 양을 처리 할 수 ​​있는지 알기 위해 될 수 있습니다.

또한 LWP :: ConnCache 클래스와 함께 간단한 Perl을 사용하십시오. 그런 식으로 더 많은 성능 (더 많은 연결)을 얻을 수 있습니다.


0

이 꼬인 비동기 웹 클라이언트는 꽤 빠릅니다.

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

tornado패키지를 사용하는 것이 이것을 달성하는 가장 빠르고 간단한 방법 이라는 것을 알았습니다 .

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

가장 쉬운 방법은 파이썬의 내장 스레딩 라이브러리를 사용하는 것입니다. 그것들은 "실제"/ 커널 스레드가 아닙니다 (직렬화와 같은) 문제가 있지만 충분합니다. 큐 및 스레드 풀이 필요합니다. 하나의 옵션이 여기 있지만 직접 작성하는 것은 쉽지 않습니다. 100,000 개의 통화를 모두 병렬화 할 수는 없지만 동시에 100 개 정도의 통화를 해제 할 수 있습니다.


7
예를 들어 루비와는 달리 파이썬의 스레드는 매우 실제적입니다. 후드 아래에서는 최소한 Unix / Linux 및 Windows에서 기본 OS 스레드로 구현됩니다. 아마도 당신은 GIL을 언급하고 있지만 스레드를 덜 실제적으로 만들지는 않습니다.
Eli Bendersky

2
Eli는 Python의 스레드에 대해서는 맞지만 스레드 풀을 사용하려는 Pestilence의 요점도 정확합니다. 이 경우 마지막으로 할 일은 100K 요청 각각에 대해 별도의 스레드를 동시에 시작하는 것입니다.
Adam Crossland

1
Igor, 주석으로 코드 스 니펫을 현명하게 게시 할 수는 없지만 질문을 편집하여 추가 할 수 있습니다.
Adam Crossland

역병 : 내 솔루션에 권장되는 대기열 및 대기열 당 스레드 수는 몇 개입니까?
IgorGanapolsky

또한 이것은 CPU 바운드가 아닌 I / O 바운드 작업입니다. GIL은 CPU 바운드 작업에 큰 영향을 미칩니다
PirateApp
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.