四核处理器上 django 项目的 apache+mod_wsgi 配置

四核处理器上 django 项目的 apache+mod_wsgi 配置

我已经尝试了很长时间,使用 nginx+apache2+mod_wsgi+memcached(+postgresql) 的“典型”django 设置(阅读文档以及一些关于 SO 和 SF 的问题,见评论)

由于我仍然对这种行为不满意(肯定是由于我的一些严重配置错误造成的),我想知道在以下假设下良好的配置是什么样的:

  • 四核 Xeon 2.8GHz
  • 8GB内存
  • 几个 django 项目(有什么特别与此相关的吗?)

这些是我当前会议的摘录:

编辑:我添加了更多内容以使其完整,但根据 Graham 的建议,我将跟进 wsgi 邮件列表

阿帕奇 2 (>阿帕奇2 -v)

Server version: Apache/2.2.12 (Ubuntu)
Server built:   Nov 18 2010 21:16:51
Server's Module Magic Number: 20051115:23
Server loaded:  APR 1.3.8, APR-Util 1.3.9
Compiled using: APR 1.3.8, APR-Util 1.3.9
Architecture:   64-bit
Server MPM:     Worker
  threaded:     yes (fixed thread count)
    forked:     yes (variable process count)
Server compiled with....
 -D APACHE_MPM_DIR="server/mpm/worker"
 -D APR_HAS_SENDFILE
 -D APR_HAS_MMAP
 -D APR_HAVE_IPV6 (IPv4-mapped addresses enabled)
 -D APR_USE_SYSVSEM_SERIALIZE
 -D APR_USE_PTHREAD_SERIALIZE
 -D SINGLE_LISTEN_UNSERIALIZED_ACCEPT
 -D APR_HAS_OTHER_CHILD
 -D AP_HAVE_RELIABLE_PIPED_LOGS
 -D DYNAMIC_MODULE_LIMIT=128
 -D HTTPD_ROOT=""
 -D SUEXEC_BIN="/usr/lib/apache2/suexec"
 -D DEFAULT_PIDLOG="/var/run/apache2.pid"
 -D DEFAULT_SCOREBOARD="logs/apache_runtime_status"
 -D DEFAULT_ERRORLOG="logs/error_log"
 -D AP_TYPES_CONFIG_FILE="/etc/apache2/mime.types"
 -D SERVER_CONFIG_FILE="/etc/apache2/apache2.conf"

apache2 配置

PidFile ${APACHE_PID_FILE}
Timeout 60
KeepAlive Off

ServerSignature Off
ServerTokens Prod
#MaxKeepAliveRequests 100
#KeepAliveTimeout 15
# worker MPM
<IfModule mpm_worker_module>
    StartServers          2
    ServerLimit           4
    MinSpareThreads       2
    MaxSpareThreads       4
    ThreadLimit          32
    ThreadsPerChild      16
    MaxClients          64#128
    MaxRequestsPerChild   10000
</IfModule>

...

SetEnv VHOST null 
#WSGIPythonOptimize 2

<VirtualHost *:8082>
    ServerName subdomain.domain.com
    ServerAlias www.domain.com
    SetEnv VHOST subdomain.domain
    AddDefaultCharset UTF-8
    ServerSignature Off

    LogFormat "%{X-Real-IP}i %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" custom
    ErrorLog  /home/project1/var/logs/apache_error.log
    CustomLog /home/project1/var/logs/apache_access.log custom

    AllowEncodedSlashes On

    WSGIDaemonProcess subdomain.domain user=www-data group=www-data threads=25
    WSGIScriptAlias / /home/project1/project/wsgi.py
    WSGIProcessGroup %{ENV:VHOST}
</VirtualHost>

wsgi.py

目前使用从源代码构建的 3.3 版本

import os
import sys

# setting all the right paths....


_realpath = os.path.realpath(os.path.dirname(__file__))
_public_html = os.path.normpath(os.path.join(_realpath, '../'))    

sys.path.append(_realpath)
sys.path.append(os.path.normpath(os.path.join(_realpath, 'apps')))
sys.path.append(os.path.normpath(_public_html))
sys.path.append(os.path.normpath(os.path.join(_public_html, 'libs')))
sys.path.append(os.path.normpath(os.path.join(_public_html, 'django')))


os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'

import django.core.handlers.wsgi

_application = django.core.handlers.wsgi.WSGIHandler()

def application(environ, start_response):
    """
    Launches django passing over some environment (domain name) settings
    """

    application_group = environ['mod_wsgi.application_group']
    """
    wsgi application group is required. It's also used to generate the
    HOST.DOMAIN.TLD:PORT parameters to pass over
    """
    assert application_group
    fields = application_group.replace('|', '').split(':')
    server_name = fields[0]
    os.environ['WSGI_APPLICATION_GROUP'] = application_group
    os.environ['WSGI_SERVER_NAME'] = server_name
    if len(fields) > 1 :
        os.environ['WSGI_PORT'] = fields[1]
    splitted = server_name.rsplit('.', 2)    
    assert splitted >= 2
    splited.reverse()
    if len(splitted) > 0 :
        os.environ['WSGI_TLD'] = splitted[0]
    if len(splitted) > 1 :
        os.environ['WSGI_DOMAIN'] = splitted[1]
    if len(splitted) > 2 :
        os.environ['WSGI_HOST'] = splitted[2]
    return _application(environ, start_response)`

文件夹结构

以防万一(实际上略有缩短)

/home/www-data/projectN/var/logs
                       /project (contains manage.py, wsgi.py, settings.py)
                       /project/apps (all the project ups are here)
                       /django
                       /libs

如果我忽略了一些明显的问题,请提前原谅我。

我的主要问题是关于 apache2 wsgi 设置。这些设置可以吗?对于一个 django 项目,四核处理器,25 个线程是否合适?对于不同虚拟主机上的多个 django 项目,这是否合适?我应该指定“进程”吗?我还应该添加其他指令吗?wsgi.py 文件中是否存在什么非常糟糕的内容?

我一直在阅读潜在问题使用标准 wsgi.py 文件,我应该切换到那个吗?

或者.. 这个会议应该运行良好,我应该在其他地方寻找问题?

那么,我说的“不满意”是什么意思呢:嗯,我经常得到相当高的 CPU WAIT;但更糟糕的是,apache2 经常会卡住。它只是不再响应,必须重新启动。我已经设置了一个监视器来处理这个问题,但它不是一个真正的解决方案。我一直在想这是否是重负载下数据库访问(postgresql)的问题,但即使是这样,为什么 apache2 进程会卡住?

除了这两个问题之外,整体性能都很棒。我甚至尝试了 New Relic,并获得了非常好的平均结果。

编辑由于我暂时转移到 nginx+gunicorn 环境,所以我自己无法提供答案。

还跟进谷歌群组针对我个人的情况和问题!听起来 Graham 当然真的很忙(mod_wsgi 是一个免费的附带项目!)但转向 Read The Docs 听起来很棒,解决那个积压问题将非常棒。这和新的 Apache 2.4 可能会让我重新考虑最佳组合(目前是 nginx+gunicorn,然后我可能会放弃 nginx 而选择 varnish+apache+mod_wsgi 设置)

答案1

在 Apache 中启用 mod_headers,然后添加到您的 VirtualHost:

RequestHeader add X-Queue-Start "%t"

然后,New Relic 将在主概览图中向您显示排队时间。

这是 Apache 子工作进程首次接受请求和 mod_wsgi 守护进程开始处理请求之间的时间。这可以作为请求积压的一个指标,而积压又可以指示由于线程死锁或线程等待外部资源而导致守护进程中的线程匮乏。

不幸的是,New Relic 依靠请求完成来报告该请求的数据。因此,如果请求卡住,您将不知道。如果所有线程都卡住,则守护进程将停止处理更多请求。

问题是,如果 Apache 子工作进程中的进程/线程数少于 100,即守护进程监听器积压,那么所有这些线程也可能会卡住,而您无法从 Apache 错误日志中知道,因为它们只是在那里等待守护进程接受连接,而这永远不会发生。只有 HTTP 浏览器客户端会知道,因为当 Apache 子工作套接字积压填满时,它将被拒绝连接。

在 mod_wsgi 4.0 中,我将添加配置守护进程监听器积压的功能,以便减少积压,因此您可能会收到某种错误。mod_wsgi 4.0 中已有新选项,用于查找阻塞线程并自动重新启动守护进程,还可以转储阻塞线程当时在代码中的位置的堆栈跟踪。

要实现这一点,您需要使用 mod_wsgi repo 中的 mod_wsgi 4.0 dev 代码。然后,您可以将 WSGIDaemonProcess 上的受阻超时设置为 60 秒,当所有线程都卡住时,它将重新启动并恢复,并转储堆栈跟踪。我仍在调整这一点,还有其他与此相关的配置选项,为什么不在这里描述。

mod_wsgi 4.0 代码还具有其他一些功能,可以与 New Relic 中的自定义图表一起使用,以跟踪不断增长的阻塞线程数量。我对它不满意,需要进行一些更改,但它很稳定。

无论如何,请跳转到 mod_wsgi 邮件列表进行进一步讨论。

相关内容