Python gevent/请求在发出大量head请求时挂起

Python gevent/请求在发出大量head请求时挂起,python,urllib2,python-requests,gevent,grequests,Python,Urllib2,Python Requests,Gevent,Grequests,我需要提出10万个head请求,并且在请求之上使用gevent。我的代码运行了一段时间,但最终挂起。我不确定它为什么挂起,或者挂在请求或gevent中。我在requests和gevent中都使用了timeout参数 请看一下下面我的代码片段,并告诉我应该更改什么 import gevent from gevent import monkey, pool monkey.patch_all() import requests def get_head(url, timeout=3): tr

我需要提出10万个head请求,并且在请求之上使用gevent。我的代码运行了一段时间,但最终挂起。我不确定它为什么挂起,或者挂在请求或gevent中。我在requests和gevent中都使用了timeout参数

请看一下下面我的代码片段,并告诉我应该更改什么

import gevent
from gevent import monkey, pool
monkey.patch_all()
import requests

def get_head(url, timeout=3):
    try:
        return requests.head(url, allow_redirects=True, timeout=timeout)
    except:
        return None

def expand_short_urls(short_urls, chunk_size=100, timeout=60*5):
    chunk_list = lambda l, n: ( l[i:i+n] for i in range(0, len(l), n) )
    p = pool.Pool(chunk_size)
    print 'Expanding %d short_urls' % len(short_urls)
    results = {}
    for i, _short_urls_chunked in enumerate(chunk_list(short_urls, chunk_size)):
        print '\t%d. processing %d urls @ %s' % (i, chunk_size, str(datetime.datetime.now()))
        jobs = [p.spawn(get_head, _short_url) for _short_url in _short_urls_chunked]
        gevent.joinall(jobs, timeout=timeout)
        results.update({_short_url:job.get().url for _short_url, job in zip(_short_urls_chunked, jobs) if job.get() is not None and job.get().status_code==200})
    return results 

我尝试过grequests,但它被放弃了,我也经历过github拉取请求,但它们也都有问题

我不确定这是否能解决您的问题,但您没有正确使用pool.pool()

试试这个:

def expand_short_urls(short_urls, chunk_size=100):
    # Pool() automatically limits your process to chunk_size greenlets running concurrently
    # thus you don't need to do all that chunking business you were doing in your for loop
    p = pool.Pool(chunk_size)
    print 'Expanding %d short_urls' % len(short_urls)

    # spawn() (both gevent.spawn() and Pool.spawn()) returns a gevent.Greenlet object
    # NOT the value your function, get_head, will return
    threads = [p.spawn(get_head, short_url) for short_url in short_urls]
    p.join()

    # to access the returned value of your function, access the Greenlet.value property
    results = {short_url: thread.value.url for short_url, thread in zip(short_urls, threads) 
如果thread.value不是None并且thread.value.status_code==200}
返回结果

您观察到的RAM使用情况主要来自存储100000个响应对象时积累的所有数据,以及所有潜在的开销。我复制了您的应用程序案例,并针对排名前Alexa的15000个URL发出了HEAD请求。这其实并不重要

  • 无论我是使用一个gevent池(即每个连接一个greenlet)还是一组固定的greenlet,都请求多个URL
  • 我设置的池大小有多大
最后,RAM的使用量随着时间的推移而增长,达到了相当大的数量。然而,我注意到从
请求
更改为
urlib2
已经导致RAM使用量减少了大约两倍。就是我换了

result = requests.head(url)

其他一些建议:不要使用两种超时机制。Gevent的超时方法非常可靠,您可以这样轻松地使用它:

def gethead(url):
    result = None
    try:
        with Timeout(5, False):
            result = requests.head(url)
    except Exception as e:
        result = e
    return result
可能看起来有点棘手,但要么返回
None
(精确到5秒后,并指示超时),要么返回表示通信错误的任何异常对象,要么返回响应。很好

虽然这可能不是问题的一部分,但在这种情况下,我建议让员工保持活力,让他们分别处理多个项目!产卵的开销确实很小。不过,这将是一个非常简单的解决方案,有一组长寿命的greenlet:

def qworker(qin, qout):
    while True:
        try:
            qout.put(gethead(qin.get(block=False)))
        except Empty:
            break

qin = Queue()
qout = Queue()

for url in urls:
    qin.put(url)

workers = [spawn(qworker, qin, qout) for i in xrange(POOLSIZE)]
joinall(workers)
returnvalues = [qout.get() for _ in xrange(len(urls))]
此外,您确实需要认识到,这是一个您正在解决的大规模问题,会产生非标准问题。当我复制您的场景时,超时时间为20秒,需要请求100个工人和15000个URL,我很容易得到大量套接字:

# netstat -tpn | wc -l
10074
也就是说,操作系统需要管理10000多个套接字,其中大多数处于时间等待状态。我还观察到“打开的文件太多”错误,并通过sysctl调整了限制。当您请求10万个URL时,您可能也会达到这样的限制,您需要制定措施来防止系统饥饿

还请注意您使用请求的方式,它会自动跟踪从HTTP到HTTPS的重定向,并自动验证证书,所有这些都会消耗RAM

在我的测量中,当我将请求的URL的数量除以程序的运行时时,我几乎从未通过100次响应/秒,这是与世界各地的外国服务器进行高延迟连接的结果。我想你也受到了这样的限制。将体系结构的其余部分调整到这个极限,您将可能能够生成一个从Internet到磁盘(或数据库)的数据流,而中间的RAM使用量不会太大

我要回答你的两个主要问题,具体来说:

我认为gevent/你使用它的方式不是你的问题。我认为你只是低估了任务的复杂性。它伴随着严重的问题,并将您的系统推向极限

  • 您的RAM使用问题:如果可以的话,从使用
    urllib2
    开始。然后,如果事情积累得太多,你需要克服积累。尝试产生一种稳定的状态:您可能希望开始将数据写到磁盘,并且通常会遇到对象可能被垃圾收集的情况

  • 您的代码“最终挂起”:这可能是由于您的RAM问题。如果不是,那么就不要产生那么多的greenlet,而是按照指示重用它们。此外,还要进一步降低并发性,监视打开的套接字的数量,必要时增加系统限制,并尝试找出软件挂起的确切位置


  • gevent
    是一项硬性要求吗?不是。我在考虑搬到龙卷风去。gevent也在吞噬我的ramIt看起来你只是在用
    gevent
    来拥有一批员工。对吗?那么,另一个排序池是否足够,或者您是否需要有关
    gevent
    tornado
    的特定内容?异步运行的工作池可以异步执行并发任务。如果您想坚持使用轻量级线程,我建议您使用标准的asyncio:)顺便说一句,我现在使用futures,效率更高。
    # netstat -tpn | wc -l
    10074