【问题标题】:Multiple Python Processes slow多个 Python 进程缓慢
【发布时间】:2011-11-28 23:23:49
【问题描述】:

我有一个 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

热门节目在 80-90% 之间闲置

sar -d 显示平均 5.3% util

更有趣的是,iptraf 开始时的速度约为 50-60MB/s,大约 4 小时后达到 8-10MB/s。

我目前在每台服务器(2 台服务器)上运行大约 500 个版本的脚本,它们都显示相同的问题。

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 显示它们中的大多数都处于睡眠状态,以及为什么当 CPU、内存、磁盘访问时,随着时间的推移,它们会变得越来越慢,发出更少的 IP 请求和带宽都可用。

如果有人能帮忙,我将不胜感激。

编辑:

代码量很大,因为我通过它使用异常来捕获有关域的诊断信息,即我无法连接的原因。如果需要,将在某处发布代码,但通过 HTTPLib 和 URLLib 的基本调用直接来自 python 示例。

更多信息:

两者

配额 -u mysql 配额 -u 根

空手而归

nlimit -n 返回 1024 更改 limit.conf 以允许 mysql 允许 16000 个软连接和硬连接,并且到目前为止能够运行超过 2000 个脚本,但问题仍然存在。

一些进展

好的,所以我已经更改了用户的所有限制,确保所有套接字都已关闭(它们没有关闭),尽管情况有所好转,但我的速度仍然慢了下来,尽管没有那么糟糕。

有趣的是,我还注意到一些内存泄漏 - 脚本运行的时间越长,使用的内存就越多,但我不确定是什么原因造成的。 我将输出数据存储在一个字符串中,然后在每次迭代后将其打印到终端,我也会在最后清除字符串,但是不断增加的内存是否可以归结为存储所有输出的终端?

编辑:不似乎没有 - 运行了 30 个脚本而没有输出到终端并且仍然存在相同的泄漏。 我没有使用任何聪明的东西(只是字符串、HTTPlib 和 URLLib)——想知道 python mysql 连接器是否有任何问题......?

【问题讨论】:

  • 如果您提供一些代码可能会有所帮助。您如何准确地执行请求?
  • 您确定您面临的问题与您的上游互联网连接变差无关吗?
  • 不应该这样 - 连接非常稳固,双向 80MB 2:1 - 如果我开始说 500 个脚本,连接将保持在 50MB/s 左右一个小时左右,然后在几个小时内降低到 10MB/s。如果我然后开始另一个说 100,它将增加再次使用 40-50MB,然后在类似的时间段内变慢。 - 所有脚本都没有停止 - 它们似乎只是按照上面的 ps -al 输出进入睡眠状态。
  • lsof 也是一个不错的尝试命令。如果有 1024 个打开的文件,那么您达到了 ulimit 并且您希望进程处于休眠状态。您可以尝试提高 ulimit 并查看性能是否可以保持更长时间。
  • 如果你使用一些异步,你可以使用更少(~10)个进程来发出并发请求。框架如twisted、gevent。这里是gevent exampletwisted example

标签: python performance http unix task


【解决方案1】:

选中 ulimitquota 的复选框和运行脚本的用户。 /etc/security/limits.conf 还可能包含您可能想要修改的资源限制。

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

  • 是否所有打开的套接字都超过了这个值?
  • 脚本完成后是否会关闭每个套接字?

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

需要查看一些代码才能知道到底发生了什么......


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

您能否在代码中显示您的打开关闭连接?
当仅运行几个脚本进程正在运行时,它们是否也开始空闲过了一会儿?还是只有同时运行数百个以上才会发生这种情况?
是否有一个父进程可以启动所有这些脚本?

如果您使用s = urllib2.urlopen(someURL),请确保在完成后使用s.close()。 Python 可以经常为你关闭一些东西(比如如果你在做 x = urllib2.urlopen(someURL).read()),但如果你被告知(比如分配一个变量),它会将它留给 .urlopen()的返回值)。仔细检查您打开和关闭 urllib 调用(或 all I/O 代码以确保安全)。如果每个脚本设计为一次只有 1 个打开的套接字,并且您的 /proc/PID/fd 显示每个脚本进程有多个活动/打开的套接字,那么肯定有一个 代码 问题需要修复。

ulimit -n 显示 1024 给出了 mysql 用户可以拥有的打开 socket/fd'slimit,您可以更改这是ulimit -S -n [LIMIT_#],但请先查看这篇文章:
Changing process.max-file-descriptor using 'ulimit -n' can cause MySQL to change table_open_cache value

之后您可能需要注销并重新登录。和/或将其添加到 /etc/bashrc(如果您更改 bashrc 并且不想注销/登录,请不要忘记添加到 source /etc/bashrc)。

磁盘空间是我发现的另一件事(困难的方式)会导致非常奇怪的问题。我已经让进程表现得好像它们正在运行(不是僵尸),但没有做预期的事情,因为它们在剩余磁盘空间为零的分区上打开了日志文件的句柄。

netstat -anpTee | grep -i mysql 还将显示这些套接字是否已连接/已建立/等待关闭/等待超时/等。

watch -n 0.1 'netstat -anpTee | grep -i mysql' 在一个漂亮的表格输出中实时查看套接字打开/关闭/更改状态/等(如果您可能需要先export GREP_OPTIONS=将其设置为 --color=always)。

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

一些手册页和参考链接:

【讨论】:

  • 抱歉应该提到 - 已经通过这个并允许通过 PAM 的 mysql 用户 16384(软和硬)连接 - 在我这样做之前,我只能按预期启动大约 1020 个脚本 - 现在我可以打开 000 年代。但是 ulimit -n 仍然显示 1024 - 不确定这是否有区别?
  • 也在谷歌上搜索 Fork Bombs - 但看不到 1000 个进程应该有任何问题,尤其是在大多数情况下脚本花费时间请求数据并且使用 80MB(突发)连接时会想到这很好 - 将对配额进行更多调查 - 谢谢。
  • quota -u mysql 和quota -u root 都一无所获。
  • 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 根根 64 Oct 1 01:38 2 -> /dev/pts/2 lrwx------ 1 根根 64 Oct 1 14:30 3 -> 套接字:[275069545] lrwx------ 1 根根 64 Oct 1 14:30 4 -> 套接字:[313790164] lrwx------ 1 根根 64 Oct 1 14:30 6 ->套接字:[313706399]
  • 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 根根 64 Oct 1 01:38 2 -> /dev/pts/2 lrwx------ 1 根根 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 ->套接字:[308708863]
【解决方案2】:

这可能是您缺乏的一些系统资源。一个猜测:你能感觉到你的系统可以处理的套接字池的限制吗?如果是的话,如果您可以更快/更快地关闭套接字,您可能会看到性能提升。

编辑:取决于您要付出的努力,您可以重组您的应用程序,以便一个进程执行多个请求。一个套接字可以在同一进程中重用,也可以在许多不同的资源中重用。 Twisted 非常适合这种类型的编程。

【讨论】:

    【解决方案3】:

    另一个需要考虑的系统资源是临时端口/proc/sys/net/ipv4/ip_local_port_range(在 Linux 上)。与/proc/sys/net/ipv4/tcp_fin_timeout 一起限制并发连接数。

    来自Benchmark of Python WSGI Servers

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

    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
    

    【讨论】:

      【解决方案4】:

      解决了! - 在 Chown 的大力帮助下 - 非常感谢!

      速度变慢是因为我没有设置套接字超时,因此在一段时间内挂起的机器人试图读取不存在的数据。添加一个简单的

      timeout = 5
      socket.setdefaulttimeout(timeout)
      

      解决了(我感到羞耻 - 但在我的辩护中,我仍在学习 python)

      内存泄漏归结为 urllib 和我正在使用的 python 版本。经过大量的谷歌搜索后,它似乎是嵌套 urlopens 的问题 - 当您弄清楚如何向 Google 提出正确的问题时,有很多关于它的在线帖子。

      感谢大家的帮助。

      编辑:

      也有助于解决内存泄漏问题(尽管没有完全解决)的方法是进行手动垃圾收集:

      import gc
      gc.collect
      

      希望对其他人有所帮助。

      【讨论】:

      • 很高兴听到您解决了这个问题!很高兴我能帮到丹!
      猜你喜欢
      • 2016-03-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-01-26
      • 2013-06-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多