多个Python进程会减慢速度

4
我有一个Python脚本,会向多个域名发出HTTP和urllib请求。
我们需要处理大量的域名,因此需要尽快完成。由于HTTP请求很慢(即如果没有网站在该域上,则可能超时),因此我同时运行多个脚本,并从数据库中的域列表中提供输入。
问题在于,在一段时间内(几个小时到24小时),所有脚本都开始变慢,而ps -al显示它们正在睡眠。
服务器非常强大(8核,72GB RAM,6TB Raid 6等等,80MB 2:1连接),且永远不会达到最大值,即Free -m显示。
-/+ buffers/cache:      61157      11337
Swap:         4510        195       4315

顶部显示空闲率在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个脚本,但仍然存在问题。
一些进展:
好吧,我已经更改了用户的所有限制,确保关闭了所有套接字(它们没有),虽然情况有所改善,但仍然存在减速问题,尽管不是很严重。
有趣的是,我还注意到了一些内存泄漏 - 脚本运行的时间越长,它们使用的内存就越多,但我不确定是什么原因导致了这种情况。 我将输出数据存储在一个字符串中,然后在每次迭代之后将其打印到终端上。我确实在最后清除了该字符串,但是终端是否存储了所有输出,从而导致内存不断增加?
编辑:不是这个原因-运行了30个脚本而没有输出到终端,但仍然存在泄漏问题。 我没有使用任何复杂的东西(只是字符串、HTTPLib和URLLib),想知道Python MySQL连接器是否存在任何问题...?

1
如果您提供一些代码可能会更有帮助。您是如何准确地进行请求的? - Muhammad Alkarouri
你确定你面临的问题与你的上行网络连接变差无关吗? - 6502
这不应该发生——连接非常稳定,双向速度为80MB 2:1。如果我启动大约500个脚本,连接将保持在大约50MB/s左右一个小时左右,然后在几个小时内降至10MB/s。如果我再启动大约100个脚本,它会再次增加到40-50MB/s,然后在类似的时间段内变慢。——没有任何脚本停止——它们似乎只是像上面的ps -al输出一样进入了睡眠状态。 - dan360
lsof 命令也是一个不错的尝试。如果有 1024 个打开的文件,则已达到 ulimit,并且您可以预期进程处于睡眠状态。您可以尝试提高 ulimit,看看性能是否能够更长时间地保持高水平。 - extraneon
2
如果使用一些异步框架,如twisted、gevent等,您可以使用较少(约10个)的进程来进行并发请求。这里有一个gevent示例,以及一个twisted示例 - jfs
4个回答

7

请检查运行脚本的计算机和用户的ulimitquota。您可能需要修改/etc/security/limits.conf中的资源限制。

ulimit -n将显示允许的最大打开文件描述符数。

  • 所有打开的套接字是否已超过此限制?
  • 脚本在完成后是否关闭每个套接字?

您还可以使用ls -l /proc/[PID]/fd/来检查fd,其中[PID]是一个脚本的进程ID。

需要查看一些代码才能知道实际情况。


编辑导入评论和更多故障排除思路):

您能展示一下您的连接打开和关闭的代码吗?当只有几个脚本进程正在运行时,它们是否也会在一段时间后变得空闲?或者仅在同时运行数百个时才会发生这种情况?
是否有一个单独的父进程启动了所有这些脚本?

如果您使用的是s = urllib2.urlopen(someURL),请确保在完成后使用s.close()关闭它。Python通常会自动关闭一些东西(例如,如果您使用x = urllib2.urlopen(someURL).read()),但是如果告诉您这样做,它将留给(例如将变量分配给.urlopen()的返回值)。仔细检查打开和关闭urllib调用(或为安全起见,所有 I/O 代码)。如果每个脚本仅设计为同时具有1个打开的套接字,而您的/proc/PID/fd显示每个脚本进程有多个活动/打开的套接字,则肯定存在要修复的代码问题。

ulimit -n显示1024,表示mysql用户可以拥有的打开socket/fd's上限,您可以使用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 mysqllsof -U 还将向您显示已打开的FD(输出非常详细)。
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

一些man页面和参考链接:


我也在谷歌上搜索了Fork Bombs,但是我看不出1000个进程应该有任何问题,特别是大多数时间脚本都在请求数据,并且使用80MB(突发)连接,我认为这应该没问题。我会对配额进行更多的调查 - 谢谢。 - dan360
quota -u mysql 和 quota -u root 都没有返回任何内容。 - dan360
lrwx------ 1 root root 64 Oct 1 14:30 0 -> /dev/pts/2 lrwx------ 1 root root 64 Oct 1 14:30 1 -> /dev/pts/2 lrwx------ 1 root root 64 Oct 1 01:38 2 -> /dev/pts/2 lrwx------ 1 root root 64 Oct 1 14:30 3 -> socket:[275069545] lrwx------ 1 root root 64 Oct 1 14:30 4 -> socket:[313790164] lrwx------ 1 root root 64 Oct 1 14:30 6 -> socket:[313706399] - dan360
lrwx------ 1 root root 64 Oct 1 14:30 0 -> /dev/pts/2 lrwx------ 1 root root 64 Oct 1 14:30 1 -> /dev/pts/2 lrwx------ 1 root root 64 Oct 1 01:38 2 -> /dev/pts/2 lrwx------ 1 root root 64 Oct 1 14:30 3 -> socket:[275069614] lrwx------ 1 root root 64 Oct 1 14:30 4 -> socket:[308695530] lrwx------ 1 root root 64 Oct 1 14:30 5 -> socket:[308708863] - dan360
不错的观点 @extraneon 我已经添加了一个代码示例,展示如何通过 try/finally 块关闭套接字。 - chown
显示剩余9条评论

2

问题已解决!非常感谢 Chown 的大力帮助!

网站速度变慢是因为我没有设置套接字超时时间,因此随着时间的推移,机器人在尝试读取不存在的数据时会挂起。只需添加一个简单的

timeout = 5
socket.setdefaulttimeout(timeout)

我解决了它(真惭愧 - 但是我还在学习Python)。

内存泄漏是由于urllib和我使用的Python版本引起的。经过大量搜索,似乎这是嵌套urlopen的问题 - 当您找出如何向Google提出正确的问题时,有很多在线帖子讨论此问题。

感谢大家的帮助。

编辑:

手动垃圾回收也有助于解决内存泄漏问题(虽然并没有完全解决):

import gc
gc.collect

希望这能帮助其他人。

很高兴听到你解决了这个问题!丹,我很高兴能帮上忙! - chown

1

这可能是你的某些系统资源不足。猜想:你是否感觉到了系统可以处理的套接字池的限制?如果是,如果你能更快/更早地关闭套接字,你可能会看到性能有所提高。

编辑:根据你想要付出的努力程度,你可以重构你的应用程序,使一个进程执行多个请求。同一个进程内可以重复使用一个套接字,也可以使用许多不同的资源。Twisted非常适合这种类型的编程。


1
另一个需要考虑的系统资源是短暂端口/proc/sys/net/ipv4/ip_local_port_range(在Linux上)。与/proc/sys/net/ipv4/tcp_fin_timeout一起,它们限制了并发连接数。
来自Python WSGI服务器基准测试

这基本上使服务器能够打开大量并发连接。

echo “10152 65535″ > /proc/sys/net/ipv4/ip_local_port_range
sysctl -w fs.file-max=128000
sysctl -w net.ipv4.tcp_keepalive_time=300
sysctl -w net.core.somaxconn=250000
sysctl -w net.ipv4.tcp_max_syn_backlog=2500
sysctl -w net.core.netdev_max_backlog=2500
ulimit -n 10240

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接