我需要提出100k头请求,并且我在请求之上使用gevent。我的代码运行了一段时间,但最终挂起。我不确定它为什么会挂起,或者它是否挂在请求或gevent中。我在请求和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
我已经尝试了问候,但它已经被放弃了,而且我已经完成了github pull请求,但它们都有问题。
答案 0 :(得分:8)
您正在观察的RAM使用情况主要源于存储100.000响应对象时堆积的所有数据以及所有底层开销。我已经复制了你的应用案例,并从顶级Alexa排名中解除了对15000个URL的HEAD请求。这并不重要
最后,RAM使用量随着时间的推移而增长,达到相当大的数量。但是,我注意到从requests
更改为urllib2
已导致RAM使用量减少约2倍。也就是说,我替换了
result = requests.head(url)
与
request = urllib2.Request(url)
request.get_method = lambda : 'HEAD'
result = urllib2.urlopen(request)
其他一些建议:不要使用两种超时机制。 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秒之后,并指示超时),任何表示通信错误或响应的异常对象。效果很棒!
虽然这可能不是问题的一部分,但在这种情况下,我建议让工作人员活着并让他们分别处理多个项目!实际上,产生greenlets的开销很小。尽管如此,这将是一个非常简单的解决方案,其中包含一组长寿命的小片:
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个套接字要管理,其中大多数都处于TIME_WAIT状态。我还观察到“打开文件太多”错误,并通过sysctl调整了限制。当您请求100.000个URL时,您可能也会达到这样的限制,并且您需要采取措施来防止系统挨饿。
还要注意您使用请求的方式,它会自动跟踪从HTTP到HTTPS的重定向,并自动验证证书,所有这些都肯定会花费RAM。
在我的测量中,当我将所请求的URL的数量除以程序的运行时间时,我几乎从未通过100个响应/秒,这是全世界与外部服务器的高延迟连接的结果。我猜你也受这种限制的影响。将架构的其余部分调整到此限制,您可能能够生成从Internet到磁盘(或数据库)的数据流,而中间的RAM使用量不是很大。
我应该解决你的两个主要问题,特别是:
我认为gevent /你使用它的方式是不你的问题。我认为你只是低估了你的任务的复杂性。它伴随着令人讨厌的问题,并将您的系统推向极限。
您的RAM使用问题:如果可以,请先使用urllib2
开始。然后,如果事情积累仍然太高,你需要反对积累。尝试生成稳定状态:您可能希望开始将数据写入磁盘,并且通常可以解决对象可能被垃圾收集的情况。
您的代码“最终会挂起”:可能这是您的RAM问题。如果不是,那么不要产生这么多greenlet,但重用如图所示。此外,进一步降低并发性,监控打开的套接字数量,必要时增加系统限制,并尝试找出完全软件挂起的位置。
答案 1 :(得分:1)
我不确定这是否可以解决您的问题,但您没有正确使用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} 返回结果