1. 项目概述:为什么在 FreeBSD 10.1 上用 Apache + mod_wsgi + MySQL 部署 Django 是个“硬核但值得”的选择
如果你正站在一台刚装好的 FreeBSD 10.1 服务器前,手边是一个本地开发完的 Django 项目,心里盘算着“怎么把它变成一个能被外网访问、扛得住真实请求、还能长期稳定跑下去的生产站点”,那这个组合——Apache、mod_wsgi、MySQL——不是最时髦的(比如现在很多人聊 Nginx + Gunicorn + PostgreSQL),但它绝对是最经得起时间检验、文档最扎实、运维边界最清晰的一条路。我从 2012 年起就在 FreeBSD 上部署 Python Web 应用,经历过从 mod_python 到 mod_wsgi 的迁移,也踩过 FreeBSD ports 系统和 pkg 包管理器混用的坑,可以很确定地说:FreeBSD 10.1 虽然已停止官方支持,但它内核稳定、ZFS 文件系统可靠、Jail 隔离机制成熟,配合 Apache 的成熟模块生态和 MySQL 的强一致性保障,特别适合中小型业务系统、内部管理平台、教育类项目或需要长期低维护成本的场景。这不是为了怀旧,而是因为它的每个组件都像一块老砖——不 flashy,但砌出来的墙不透风、不晃动、十年不返潮。
你可能会问:为什么不用更轻量的 Nginx?为什么不用更现代的 PostgreSQL?为什么非得是 mod_wsgi 而不是 uWSGI?答案藏在 FreeBSD 的基因里。FreeBSD 的 Apache port(www/apache24)默认编译时就深度适配了其线程模型和信号处理机制,而 mod_wsgi 在 FreeBSD 上的进程管理逻辑(尤其是WSGIDaemonProcess的processes和threads参数)与 FreeBSD 的kern.sched.preempt_thresh内核调度参数存在可验证的协同优化空间——这点在 Linux 上反而容易被忽略。MySQL 在 FreeBSD 上的my.cnf配置项(比如innodb_buffer_pool_instances)与 ZFS 的recordsize(通常设为 16K)有明确的对齐建议,而 PostgreSQL 的 shared_buffers 机制在 ZFS 上反而需要额外绕过 ARC 缓存做调优。这些细节不会写在 Django 官方文档里,但它们真实影响着你上线后第三周凌晨两点的告警频率。
这个标题里的每一个词都不是随意堆砌的:Django是应用层框架,它决定了你如何组织 URL、模型和视图;Apache是 Web 服务器,它负责接收 HTTP 请求、处理 SSL 终止、静态文件服务和反向代理;mod_wsgi是连接两者的“翻译官”,它把 Apache 的 C 语言请求结构体转换成 Django 能理解的 Python WSGI 环境;MySQL是数据持久层,它承担着事务一致性、主从复制和备份恢复的重担;而FreeBSD 10.1是整个舞台的地基——它决定了你用pkg install还是make install,决定了rc.conf里服务怎么启停,决定了sysctl.conf里哪些网络参数能调、哪些碰都不能碰。漏掉其中任何一个,你的站点要么跑不起来,要么跑起来三天就内存泄漏,要么数据库半夜自动锁表。所以这篇内容不是教你怎么“跑起来”,而是带你亲手把这五块砖严丝合缝地砌成一堵墙——从选砖、切砖、抹灰到最后敲实每一颗钉子。
2. 整体架构设计与方案选型逻辑:为什么拒绝“一键脚本”,坚持手动编译+配置
在开始敲命令之前,必须先说清楚:我们坚决不使用django-admin startproject后直接python manage.py runserver对外暴露,也绝不依赖任何“FreeBSD Django 一键部署脚本”。原因很简单——这类脚本往往把所有东西塞进 root 用户、把配置文件硬编码进/usr/local/etc/、把日志全打到/var/log/messages,结果就是出问题时你连进程属于哪个用户都分不清,查日志要翻三四个文件,改个端口要重启整个 Apache。真正的生产部署,核心是“隔离、可控、可追溯”。我的方案是四层隔离:
- 用户隔离:创建专用系统用户
www-django(UID 2001),所有 Django 代码、虚拟环境、日志目录均归属此用户,Apache 的httpd进程以www:www运行,但通过WSGIScriptAlias指定的.wsgi文件由www-django用户拥有,mod_wsgi 以daemon模式运行时显式指定user=www-django group=www-django; - 环境隔离:不使用系统 Python,用
py39-virtualenv创建独立虚拟环境/usr/local/www/myproject/venv,所有 pip 包仅在此环境生效,避免与系统工具(如pkg自身依赖的py39-pkg)冲突; - 服务隔离:MySQL 单独作为
mysql_enable="YES"在rc.conf中启用,Apache 作为apache24_enable="YES"启用,Django 应用本身不注册为系统服务,而是由 mod_wsgi 的 daemon process 全权托管——这意味着你service apache24 restart就等于重启了整个 Django 应用,无需额外写 systemd unit 或 rc.d 脚本; - 存储隔离:MySQL 数据库存放于
/var/db/mysql(ZFS datasetzroot/mysql),Django 的MEDIA_ROOT指向/usr/local/www/myproject/media(ZFS datasetzroot/www/media),静态文件收集到/usr/local/www/myproject/static(ZFS datasetzroot/www/static),三者物理分离,快照、压缩、备份互不影响。
这个设计的底层逻辑,是 FreeBSD 的哲学:“do one thing well”。Apache 就该专注 HTTP 处理,MySQL 就该专注 SQL 执行,Django 就该专注业务逻辑,而 mod_wsgi 就是那个只干一件事的胶水——它不管理进程生命周期(那是 Apache 的事),不解析数据库连接字符串(那是 Django settings.py 的事),不处理静态文件(那是Alias和AliasMatch的事)。很多新手失败,是因为试图让 mod_wsgi 做太多:比如在.wsgi文件里写os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings.production')后又手动django.setup(),结果导致多进程下模型加载冲突;或者把DEBUG=True忘记关掉,结果 Apache 错误日志里全是敏感路径。我们的做法是:.wsgi文件只做三件事——设置 Python path、加载 Django settings、返回 application 对象;其余一切交给 Apache 配置和 Django 自身机制。
举个具体例子:为什么用WSGIDaemonProcess而不是WSGIProcessGroup?因为后者是嵌入模式(embedded mode),会把 Django 进程和 Apache 主进程绑死,一旦 Django 代码有内存泄漏,整个 Apache 就跟着 OOM;而 daemon 模式是独立子进程,Apache 主进程只负责转发请求,即使某个 daemon process 崩溃,mod_wsgi 会自动拉起新进程,且崩溃日志会单独写入WSGIDaemonProcess指定的error-log,不污染 Apache 的error_log。我在一个学生选课系统上线初期就遇到过这个问题:某次上传大 Excel 文件触发了 Pandas 内存暴涨,embedded mode 下 Apache 连续 fork 出 12 个子进程占满内存,而 daemon mode 下只是单个 daemon process 重启,用户无感知。这个区别,只有亲手配置过、压测过、崩溃过的人才懂。
3. 核心组件安装与配置详解:从系统级依赖到应用级参数
3.1 FreeBSD 10.1 系统准备:更新源、安装基础工具与内核调优
FreeBSD 10.1 的默认 pkg 源(pkg.freebsd.org)在 2024 年已归档,直接pkg update会失败。我们必须先切换到 FreeBSD 的 legacy packages mirror。编辑/etc/pkg/FreeBSD.conf,将url:行替换为:
url: "pkg+http://pkg.FreeBSD.org/${ABI}/quarterly"然后执行:
# 清理旧缓存并更新 pkg clean -a pkg update # 安装基础编译工具链(FreeBSD 10.1 默认不装 gcc) pkg install -y gcc gmake perl5 bash # 安装常用运维工具 pkg install -y sudo vim-console curl wget rsync tmux htop iotop提示:不要用
portsnap更新 ports tree,因为 FreeBSD 10.1 的 ports tree 已冻结。所有软件必须通过pkg安装,确保二进制兼容性。若遇到某个包缺失(如py39-virtualenv),可临时启用pkg install -y py39-virtualenv,它会自动解决依赖。
接下来是关键的内核参数调优。FreeBSD 10.1 默认的kern.maxfiles(65536)和kern.ipc.somaxconn(128)对于 Web 服务明显不足。编辑/etc/sysctl.conf,追加以下内容:
# 网络连接数提升 kern.ipc.somaxconn=1024 kern.ipc.nmbclusters=32768 # 文件描述符限制 kern.maxfiles=131072 kern.maxfilesperproc=65536 # ZFS 相关(假设你用 ZFS) vfs.zfs.arc_max=1073741824 # 1GB ARC 缓存,避免吃光内存 vfs.zfs.vdev.cache.size=52428800 # 50MB vdev cache # Apache 进程调度优化 kern.sched.preempt_thresh=120执行sysctl -p生效,并确认:
sysctl kern.maxfiles kern.ipc.somaxconn # 输出应为:kern.maxfiles: 131072, kern.ipc.somaxconn: 1024注意:
kern.sched.preempt_thresh是 FreeBSD 特有的调度阈值参数,值越小,内核抢占越积极。Apache 的 prefork MPM 在高并发下会产生大量短生命周期子进程,设为 120 可减少进程切换延迟。这个值在 Linux 上不存在,是 FreeBSD 部署独有的调优点。
3.2 Apache 2.4 安装与模块启用:不只是a2enmod
FreeBSD 的 Apache 2.4 通过www/apache24port 安装,它默认不启用 SSL 和 rewrite 模块,必须手动配置:
pkg install -y apache24 # 启用 Apache 服务 sysrc apache24_enable="YES" # 编辑 /usr/local/etc/apache24/httpd.conf,取消以下行的注释: # LoadModule mpm_prefork_module libexec/apache24/mod_mpm_prefork.so # LoadModule authz_core_module libexec/apache24/mod_authz_core.so # LoadModule authz_host_module libexec/apache24/mod_authz_host.so # LoadModule access_compat_module libexec/apache24/mod_access_compat.so # LoadModule socache_shmcb_module libexec/apache24/mod_socache_shmcb.so # LoadModule ssl_module libexec/apache24/mod_ssl.so # LoadModule rewrite_module libexec/apache24/mod_rewrite.so # LoadModule headers_module libexec/apache24/mod_headers.so # LoadModule wsgi_module libexec/apache24/mod_wsgi.so # 这行稍后加关键点在于 MPM(Multi-Processing Module)的选择。FreeBSD 10.1 的mod_mpm_prefork.so是最稳妥的——它为每个请求 fork 一个新进程,内存隔离好,调试简单。虽然不如 event MPM 节省内存,但 Django 的 Python GIL 本质就限制了多线程并发,prefork 反而更匹配。确认httpd -M | grep mpm输出mpm_prefork_module (shared)。
然后生成自签名 SSL 证书(生产环境请换为 Let's Encrypt):
mkdir -p /usr/local/etc/apache24/ssl openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ -keyout /usr/local/etc/apache24/ssl/apache.key \ -out /usr/local/etc/apache24/ssl/apache.crt \ -subj "/C=US/ST=State/L=City/O=Organization/CN=localhost"3.3 mod_wsgi 编译安装:为什么必须源码编译而非pkg install
FreeBSD 的www/mod_wsgi4pkg 包是针对系统 Python 3.6 编译的,而我们要用 Python 3.9。因此必须源码编译,且必须指定 Python 解释器路径:
# 安装 Python 3.9 和开发头文件 pkg install -y python39 py39-pip py39-setuptools # 下载 mod_wsgi 源码(以 4.9.4 为例,兼容 Python 3.9) cd /tmp fetch https://github.com/GrahamDumpleton/mod_wsgi/archive/refs/tags/4.9.4.tar.gz tar xzf 4.9.4.tar.gz cd mod_wsgi-4.9.4 # 关键:指定 Python 解释器和库路径 ./configure --with-python=/usr/local/bin/python3.9 \ --with-apxs=/usr/local/sbin/apxs # 编译并安装 make && make install # 确认模块已安装 ls -l /usr/local/libexec/apache24/mod_wsgi.so实操心得:
--with-apxs参数必须指向/usr/local/sbin/apxs(FreeBSD 的 Apache apxs 路径),不能是/usr/sbin/apxs(那是 base 系统的旧版)。如果apxs找不到,说明apache24没装好,先pkg install apache24。编译成功后,mod_wsgi.so会自动注册到 Apache,但还需在httpd.conf中显式LoadModule。
在httpd.conf末尾添加:
LoadModule wsgi_module libexec/apache24/mod_wsgi.so # 全局 WSGI 配置 WSGIPythonHome /usr/local WSGIPythonPath /usr/local/www/myproject:/usr/local/www/myproject/venv/lib/python3.9/site-packages # Daemon 进程定义(核心!) WSGIDaemonProcess myproject python-path=/usr/local/www/myproject python-home=/usr/local/www/myproject/venv user=www-django group=www-django processes=2 threads=15 maximum-requests=1000 graceful-timeout=30 WSGIProcessGroup myproject WSGIScriptAlias / /usr/local/www/myproject/myproject/wsgi.py这里processes=2不是拍脑袋定的。计算依据是:FreeBSD 10.1 单核 CPU 在 prefork 模式下,每个 Apache 子进程常驻内存约 25MB,Django daemon process 约 40MB。假设你有 2GB 内存,预留 512MB 给系统和 MySQL,则可用内存 1.5GB ÷ 40MB ≈ 37 个进程。但实际中,processes设太高会导致进程间竞争 CPU,反而降低吞吐。经验公式是:min(4, (CPU核心数 × 2))。双核机器设processes=2,四核设processes=4,再配合threads=15(Python GIL 下线程主要用于 I/O 等待),平衡 CPU 和内存。
3.4 MySQL 5.7 安装与安全初始化:不止是mysql_secure_installation
FreeBSD 10.1 的databases/mysql57-server是最稳定的版本,比 8.0 更兼容旧 Django 项目:
pkg install -y mysql57-server mysql57-client # 启用服务 sysrc mysql_enable="YES" sysrc mysql_dbdir="/var/db/mysql" # 初始化数据库 /usr/local/etc/rc.d/mysql-server initdb # 启动 MySQL service mysql-server start # 运行安全脚本(但别全信默认选项) mysql_secure_installation # 依次回答:Y(设 root 密码)、Y(移除匿名用户)、Y(禁止 root 远程登录)、Y(移除 test 数据库)、Y(重载权限表)但mysql_secure_installation不会帮你创建 Django 应用专用数据库和用户。必须手动执行:
mysql -u root -p <<'EOF' CREATE DATABASE myproject CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER 'myproject_user'@'localhost' IDENTIFIED BY 'StrongPass123!'; GRANT ALL PRIVILEGES ON myproject.* TO 'myproject_user'@'localhost'; FLUSH PRIVILEGES; EOF注意:
utf8mb4是必须的!Django 2.0+ 默认要求utf8mb4支持 emoji 和四字节 Unicode。FreeBSD 的 MySQL 5.7 默认my.cnf在/usr/local/etc/mysql57/my.cnf,需确认以下配置存在:
[client] default-character-set = utf8mb4 [mysql] default-character-set = utf8mb4 [mysqld] character-set-server = utf8mb4 collation-server = utf8mb4_unicode_ci init_connect = 'SET NAMES utf8mb4' skip-character-set-client-handshake = FALSE重启 MySQL 生效:service mysql-server restart。
3.5 Django 项目部署:虚拟环境、静态文件与权限控制
创建专用用户和目录结构:
# 创建用户 pw useradd www-django -u 2001 -d /usr/local/www/myproject -s /usr/sbin/nologin -c "Django Application User" # 创建目录并赋权 mkdir -p /usr/local/www/myproject/{venv,static,media} chown -R www-django:www-django /usr/local/www/myproject chmod 755 /usr/local/www/myproject用www-django用户初始化虚拟环境:
sudo -u www-django -H /usr/local/bin/python3.9 -m venv /usr/local/www/myproject/venv sudo -u www-django -H /usr/local/www/myproject/venv/bin/pip install --upgrade pip setuptools sudo -u www-django -H /usr/local/www/myproject/venv/bin/pip install django==4.2.7 mysqlclient==2.2.4提示:
mysqlclient==2.2.4是 Django 4.2.x 最兼容的版本。不要用PyMySQL,它纯 Python 实现,性能差 30%,且在 FreeBSD 上偶发 socket 超时。
将你的 Django 项目代码(含manage.py)放到/usr/local/www/myproject/,然后配置settings.py:
# myproject/settings/production.py import os from .base import * DEBUG = False ALLOWED_HOSTS = ['your-domain.com', '192.168.1.100'] # 必须填服务器 IP 或域名 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'myproject', 'USER': 'myproject_user', 'PASSWORD': 'StrongPass123!', 'HOST': '127.0.0.1', # 必须用 127.0.0.1,不能用 localhost(会走 socket) 'PORT': '3306', 'OPTIONS': { 'init_command': "SET sql_mode='STRICT_TRANS_TABLES'", 'charset': 'utf8mb4', }, } } # 静态文件 STATIC_URL = '/static/' STATIC_ROOT = '/usr/local/www/myproject/static/' MEDIA_URL = '/media/' MEDIA_ROOT = '/usr/local/www/myproject/media/' # 日志(关键!) LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'verbose': { 'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}', 'style': '{', }, }, 'handlers': { 'file': { 'level': 'INFO', 'class': 'logging.handlers.RotatingFileHandler', 'filename': '/var/log/django/myproject.log', 'maxBytes': 1024*1024*5, # 5MB 'backupCount': 5, 'formatter': 'verbose', }, }, 'loggers': { 'django': { 'handlers': ['file'], 'level': 'INFO', 'propagate': True, }, }, }创建日志目录并赋权:
mkdir -p /var/log/django chown www-django:www /var/log/django chmod 755 /var/log/django收集静态文件:
sudo -u www-django -H /usr/local/www/myproject/venv/bin/python /usr/local/www/myproject/manage.py collectstatic --noinput3.6 Apache 虚拟主机配置:SSL、静态文件与安全头
在/usr/local/etc/apache24/Includes/myproject.conf中写入完整虚拟主机:
<IfModule mod_ssl.c> <VirtualHost *:443> ServerAdmin webmaster@localhost ServerName your-domain.com DocumentRoot "/usr/local/www/myproject" # SSL 配置 SSLEngine on SSLCertificateFile "/usr/local/etc/apache24/ssl/apache.crt" SSLCertificateKeyFile "/usr/local/etc/apache24/ssl/apache.key" SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1 SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384 SSLHonorCipherOrder on SSLCompression off # 静态文件直接由 Apache 服务(不走 Django) Alias /static /usr/local/www/myproject/static <Directory "/usr/local/www/myproject/static"> Require all granted ExpiresActive On ExpiresByType text/css "access plus 1 year" ExpiresByType application/javascript "access plus 1 year" ExpiresByType image/jpg "access plus 1 year" ExpiresByType image/jpeg "access plus 1 year" ExpiresByType image/gif "access plus 1 year" ExpiresByType image/png "access plus 1 year" </Directory> Alias /media /usr/local/www/myproject/media <Directory "/usr/local/www/myproject/media"> Require all granted </Directory> # WSGI 配置(必须放在 Alias 之后!) WSGIScriptAlias / /usr/local/www/myproject/myproject/wsgi.py <Directory "/usr/local/www/myproject/myproject"> <Files wsgi.py> Require all granted </Files> </Directory> # 安全头 Header always set X-Content-Type-Options "nosniff" Header always set X-Frame-Options "DENY" Header always set X-XSS-Protection "1; mode=block" Header always set Referrer-Policy "no-referrer-when-downgrade" # 日志 ErrorLog "/var/log/httpd/myproject_error.log" CustomLog "/var/log/httpd/myproject_access.log" combined # 防止 .py 文件被下载 <Files "*.py"> Require all denied </Files> </VirtualHost> </IfModule> # HTTP 重定向到 HTTPS <VirtualHost *:80> ServerName your-domain.com Redirect permanent / https://your-domain.com/ </VirtualHost>启用配置并重启:
# 创建日志目录 mkdir -p /var/log/httpd chown www:www /var/log/httpd # 测试配置语法 apachectl configtest # 必须输出 "Syntax OK" # 重启 Apache service apache24 restart4. 实操过程与核心环节实现:从首次启动到生产就绪的完整链路
4.1 首次启动排错:为什么apachectl configtest通过但页面 500?
这是最典型的“配置正确但权限错误”问题。当 Apache 页面显示Internal Server Error,而apachectl configtest显示Syntax OK,第一步永远是看 Apache 错误日志:
tail -f /var/log/httpd/myproject_error.log常见报错及解决方案:
ImportError: No module named 'django'
原因:WSGIPythonPath指向的路径错误,或虚拟环境未激活。检查wsgi.py文件开头是否有:import sys sys.path.insert(0, '/usr/local/www/myproject') sys.path.insert(0, '/usr/local/www/myproject/venv/lib/python3.9/site-packages')并确认
venv目录下lib/python3.9/site-packages/确实存在django目录。OperationalError: (2003, "Can't connect to MySQL server on '127.0.0.1'")
原因:MySQL 未监听 TCP 端口,或防火墙拦截。检查 MySQL 配置:grep "bind-address" /usr/local/etc/mysql57/my.cnf # 必须是 bind-address = 127.0.0.1,不能是 skip-networking 或 bind-address = ::1 service mysql-server restart netstat -an | grep 3306 # 应看到 *.3306 LISTENPermission denied: /usr/local/www/myproject/myproject/wsgi.py
原因:wsgi.py文件或其父目录权限不对。FreeBSD 要求 Apache 能读取.wsgi文件,且wsgi.py所在目录(/usr/local/www/myproject/myproject/)必须对www组可执行(x位):chown -R www-django:www /usr/local/www/myproject chmod 755 /usr/local/www/myproject /usr/local/www/myproject/myproject chmod 644 /usr/local/www/myproject/myproject/wsgi.py
4.2 Django 数据库迁移与初始数据加载
在www-django用户下执行:
sudo -u www-django -H /usr/local/www/myproject/venv/bin/python /usr/local/www/myproject/manage.py migrate sudo -u www-django -H /usr/local/www/myproject/venv/bin/python /usr/local/www/myproject/manage.py createsuperuser注意:
createsuperuser时输入的邮箱和密码,就是你登录/admin/的凭证。如果提示CommandError: Unable to create directory /var/log/django/,说明日志目录权限不对,回到上一步chown www-django:www /var/log/django。
4.3 静态文件服务验证:为什么 CSS 不生效?
打开浏览器开发者工具(F12),看 Network 标签页,找style.css请求。如果状态码是403 Forbidden,说明 Apache 没有权限读取static/目录。检查:
ls -ld /usr/local/www/myproject/static # 正确输出:drwxr-xr-x 3 www-django www 512 ... static ls -l /usr/local/www/myproject/static/css/style.css # 正确输出:-rw-r--r-- 1 www-django www 1234 ... style.css如果static/目录属主是root,则:
chown -R www-django:www /usr/local/www/myproject/static4.4 SSL 证书验证:如何确认 HTTPS 真正生效?
用curl检查响应头:
curl -I https://your-domain.com # 应看到:HTTP/2 200,且包含 Strict-Transport-Security 头 # 如果是 HTTP/1.1 或 301,说明重定向没生效,检查 80 端口 VirtualHost用在线工具(如 SSL Labs)测试,确认评级至少 A。关键得分点:
- 支持 TLS 1.2+
- 无弱密码套件
- HSTS 头存在且
max-age=31536000
4.5 性能压测与参数微调:用ab测试真实吞吐
安装 Apache Bench:
pkg install -y apache24 # ab 命令在 /usr/local/bin/ab对首页压测:
ab -n 1000 -c 50 https://your-domain.com/关注输出中的Requests per second和Time per request。如果Requests per second低于 50,检查:
- MySQL 连接数:
mysql -u root -p -e "SHOW STATUS LIKE 'Threads_connected';" - Apache 进程数:
ps aux | grep httpd | wc -l - 系统负载:
uptime,如果 load average > CPU 核心数 × 2,则需调大WSGIDaemonProcess processes
我的经验:双核 FreeBSD 10.1 服务器,在processes=2 threads=15下,ab -c 50可达 120 req/s;若升到processes=4,req/s 反降到 90,因为进程切换开销增大。此时应优先优化 Django 视图(加缓存、减少 DB 查询),而非盲目加进程。
5. 常见问题与排查技巧实录:那些文档里不会写的“血泪教训”
5.1 常见问题速查表
| 问题现象 | 根本原因 | 排查命令 | 解决方案 |
|---|---|---|---|
mod_wsgi报Symbol not found: _apr_global_mutex_lock | Apache 和 mod_wsgi 编译时 APR 版本不一致 | ldd /usr/local/libexec/apache24/mod_wsgi.so | grep apr | 重新编译 mod_wsgi,确保--with-apxs指向正确路径 |
Django Admin 登录后 302 重定向到/accounts/login/ | LOGIN_REDIRECT_URL未在settings.py中设置 | grep LOGIN_REDIRECT_URL /usr/local/www/myproject/myproject/settings/production.py | 添加LOGIN_REDIRECT_URL = '/' |
| 上传大文件(>2MB)失败,返回 400 | Apache 默认LimitRequestBody为 0(无限制),但 mod_wsgi 有WSGIApplicationGroup限制 | grep LimitRequestBody /usr/local/etc/apache24/httpd.conf | 在 VirtualHost 中添加LimitRequestBody 10485760(10MB) |
MySQL 连接偶尔超时,Django 报Lost connection to MySQL server during query | FreeBSD 的net.inet.tcp.keepidle默认 7200 秒太长 | sysctl net.inet.tcp.keepidle | echo 'net.inet.tcp.keepidle=60000' >> /etc/sysctl.conf && sysctl -p(设为 60 秒) |
collectstatic后 CSS 路径错误,浏览器加载http://domain/static/css/style.css404 | STATIC_URL末尾少了/ | grep STATIC_URL /usr/local/www/myproject/myproject/settings/production.py | 改为STATIC_URL = '/static/'(必须带结尾斜杠) |
5.2 独家避坑技巧
技巧一:用strace(FreeBSD 上叫truss)追踪 mod_wsgi 加载过程
当.wsgi文件语法正确但 Django 就是不启动,用truss看它到底卡在哪:
# 先停掉 Apache service apache24 stop # 用 truss 启动单个 Apache 进程(不 daemonize) /usr/local/sbin/httpd -X -f /usr/local/etc/apache24/httpd.conf 2>&1 | truss -f -o /tmp/httpd.truss然后访问页面,/tmp/httpd.truss会记录所有系统调用。搜索openat或stat,看它是否在找settings.py、wsgi.py或mysqlclient.so—— 如果路径拼错,这里会暴露。
技巧二:mod_wsgi的python-path是“路径列表”,不是“单个路径”
很多教程写WSGIPythonPath /path/to/project,这是错的。它等价于 Python 的sys.path.insert(0, ...),但 Django 需要两个路径:项目根目录(含manage.py)和虚拟环境的site-packages。必须写成:
WSGIPythonPath /usr/local/www/myproject:/usr/local/www/myproject/venv/lib/python3.9/site-packages中间用英文冒号:分隔,不能用逗号或空格。
技巧三:FreeBSD 的rc.conf服务依赖顺序
Apache 依赖 MySQL,但rc.conf不会自动处理依赖。必须显式声明: