多个Python进程运行缓慢
我有一个Python脚本,它会向多个网站发送HTTP和urllib请求。
我们需要处理大量的网站,所以希望尽快完成这些请求。因为HTTP请求比较慢(比如如果网站不存在可能会超时),我同时运行多个脚本,从数据库中的域名列表获取数据。
问题是,经过一段时间(几个小时到24小时),这些脚本开始变得慢下来,使用命令ps -al
查看时,发现它们都在“休眠”。
我们的服务器非常强大(8个核心,72GB内存,6TB的Raid 6等,连接速度80MB 2:1),从来没有达到极限,也就是说,使用Free -m
查看时显示
-/+ buffers/cache: 61157 11337
Swap: 4510 195 4315
而使用top
命令时,显示大约80-90%的空闲。
使用sar -d
命令显示平均利用率为5.3%。
更有趣的是,使用iptraf
开始时的速度在50-60MB/s,经过大约4小时后降到8-10MB/s。
我目前在每台服务器上运行大约500个脚本(总共2台服务器),它们都出现了同样的问题。
使用ps -al
命令显示大多数Python脚本都在休眠,我不明白为什么,比如:
0 S 0 28668 2987 0 80 0 - 71003 sk_wai pts/2 00:00:03 python
0 S 0 28669 2987 0 80 0 - 71619 inet_s pts/2 00:00:31 python
0 S 0 28670 2987 0 80 0 - 70947 sk_wai pts/2 00:00:07 python
0 S 0 28671 2987 0 80 0 - 71609 poll_s pts/2 00:00:29 python
0 S 0 28672 2987 0 80 0 - 71944 poll_s pts/2 00:00:31 python
0 S 0 28673 2987 0 80 0 - 71606 poll_s pts/2 00:00:26 python
0 S 0 28674 2987 0 80 0 - 71425 poll_s pts/2 00:00:20 python
0 S 0 28675 2987 0 80 0 - 70964 sk_wai pts/2 00:00:01 python
0 S 0 28676 2987 0 80 0 - 71205 inet_s pts/2 00:00:19 python
0 S 0 28677 2987 0 80 0 - 71610 inet_s pts/2 00:00:21 python
0 S 0 28678 2987 0 80 0 - 71491 inet_s pts/2 00:00:22 python
脚本中没有任何休眠状态,所以我不明白为什么ps -al
显示大多数脚本都在休眠,为什么它们会越来越慢,随着时间的推移请求的IP数量减少,而CPU、内存、磁盘访问和带宽都充足。
如果有人能帮忙,我将非常感激。
编辑:
代码很庞大,因为我在其中使用了异常处理来捕获关于域名的诊断信息,也就是我无法连接的原因。如果需要,我会把代码发到某个地方,但通过HTTPLib和URLLib的基本调用都是直接来自Python的示例。
更多信息:
使用命令
quota -u mysql
和
quota -u root
都没有返回任何结果。
使用nlimit -n
命令返回1024,我已经修改了limit.conf文件,允许mysql有16000个软连接和硬连接,目前能够运行超过2000个脚本,但问题依然存在。
一些进展
好的,我已经更改了用户的所有限制,确保所有的socket都关闭(之前没有关闭),虽然情况有所改善,但仍然有变慢的问题,虽然没有那么严重。
有趣的是,我还注意到了一些内存泄漏——脚本运行的时间越长,使用的内存越多,但我不确定是什么导致的。我将输出数据存储在一个字符串中,然后在每次迭代后打印到终端,最后我也会清空这个字符串,但不断增加的内存是否是因为终端存储了所有输出?
编辑:看起来不是——我运行了30个脚本,没有输出到终端,内存泄漏依然存在。我没有使用任何复杂的东西(只是字符串、HTTPlib和URLLib),不知道Python的mysql连接器是否有问题……
4 个回答
可能是你的系统某些资源不够用。我的猜测是,你是否感觉到系统能处理的网络连接数量有限?如果是这样的话,如果你能更快地关闭这些连接,可能会让性能有所提升。
补充一下:根据你愿意投入的精力,你可以重新设计你的应用程序,让一个进程处理多个请求。一个进程内部可以重复使用同一个连接,也可以使用很多不同的资源。Twisted这个框架非常适合这种编程方式。
解决了!非常感谢Chown的巨大帮助!
慢的原因是我没有设置socket的超时时间,因此过了一段时间,程序一直在尝试读取不存在的数据,导致卡住。加上一个简单的
timeout = 5
socket.setdefaulttimeout(timeout)
就解决了这个问题(真丢人,不过我还在学习python,给自己辩解一下)
内存泄漏的问题是因为我用的urllib和python的版本。经过大量的搜索,发现这是一个与嵌套的urlopen有关的问题,网上有很多相关的帖子,只要你学会如何向谷歌提问就能找到。
感谢大家的帮助。
编辑:
还有一个对内存泄漏问题有帮助的办法(虽然没有完全解决)是手动进行垃圾回收:
import gc
gc.collect
希望这对其他人也有帮助。
检查一下 ulimit
和 quota
,这两个是关于你这个机器和运行脚本的用户的限制。文件 /etc/security/limits.conf
里可能也有一些资源限制,你可能需要修改一下。
用 ulimit -n
可以查看允许打开的最大文件描述符数量。
- 是不是因为打开的连接太多,超过了这个限制?
- 脚本在用完每个连接后有没有关闭它们?
你还可以用 ls -l /proc/[PID]/fd/
来查看文件描述符,其中 [PID]
是某个脚本的进程ID。
需要看看一些代码才能搞清楚到底发生了什么……
编辑(引入评论和更多故障排除的想法):
能不能把你 打开 和 关闭 连接的代码给我看看?
当只运行几个脚本时,它们会在一段时间后变得闲置吗?还是说只有当同时运行几百个时才会这样?
有没有一个主进程来启动所有这些脚本?
如果你在用 s = urllib2.urlopen(someURL)
,记得在用完后要 s.close()
。Python 有时候会帮你自动关闭(比如你用 x = urllib2.urlopen(someURL).read()
),但如果你把返回值赋给一个变量(比如 .urlopen()
),就得自己来关闭。仔细检查一下你打开和关闭 urllib 的调用(或者说所有的输入输出代码,以防万一)。如果每个脚本设计成一次只打开一个连接,但你的 /proc/PID/fd
显示每个脚本进程有多个活动的连接,那肯定是代码上有问题需要修复。
ulimit -n
显示 1024
是 mysql 用户可以打开的连接的 限制,你可以用 ulimit -S -n [LIMIT_#]
来修改这个限制,但最好先看看这篇文章:
使用 'ulimit -n' 改变 process.max-file-descriptor 可能会导致 MySQL 改变 table_open_cache 的值.
修改后可能需要注销再登录,或者把它加到 /etc/bashrc
里(如果你修改了 bashrc
,别忘了执行 source /etc/bashrc
,这样就不用注销再登录了)。
磁盘空间也是我发现过的一个问题(真是费了不少劲才知道),它可能会导致一些奇怪的问题。我遇到过进程看起来在运行(不是僵尸进程),但因为它们打开了一个日志文件的句柄,而那个分区的磁盘空间用完了,所以没有按预期工作。
用 netstat -anpTee | grep -i mysql
也可以查看这些连接是否已经连接、建立、等待关闭、等待超时等等。
watch -n 0.1 'netstat -anpTee | grep -i mysql'
可以实时查看连接的打开、关闭、状态变化等,以一个不错的表格输出(如果你设置了 --color=always
,可能需要先 export GREP_OPTIONS=
)。
lsof -u mysql
或者 lsof -U
也能显示打开的文件描述符(输出会比较详细)。
import urllib2
import socket
socket.settimeout(15)
# or settimeout(0) for non-blocking:
#In non-blocking mode (blocking is the default), if a recv() call
# doesn’t find any data, or if a send() call can’t
# immediately dispose of the data,
# a error exception is raised.
#......
try:
s = urllib2.urlopen(some_url)
# do stuff with s like s.read(), s.headers, etc..
except (HTTPError, etcError):
# myLogger.exception("Error opening: %s!", some_url)
finally:
try:
s.close()
# del s - although, I don't know if deleting s will help things any.
except:
pass
一些手册页和参考链接: