Django从零开始(十):项目部署与性能优化

一、部署概述

将Django项目从开发环境迁移到生产环境,需要考虑性能、稳定性、安全性三个方面。本章将介绍从服务器搭建到线上运维的完整流程。

生产环境架构

# 常见的Django生产部署架构

# 方案1:Nginx + Gunicorn + Django(最经典)
用户 → Nginx(80/443) → Gunicorn(8000) → Django

# 方案2:Nginx + uWSGI + Django
用户 → Nginx(80/443) → uWSGI(8000) → Django

# 方案3:Docker + Nginx + Gunicorn
用户 → Nginx容器 → Gunicorn容器 → Django

# 完整架构(含缓存和数据库)
用户 → Nginx → Gunicorn → Django → PostgreSQL
                     ↕            ↕
                   Redis        Celery → RabbitMQ

二、Gunicorn部署

2.1 安装与配置

# 安装Gunicorn
pip install gunicorn

# 基本启动
gunicorn mysite.wsgi:application

# 生产环境推荐参数
gunicorn mysite.wsgi:application \
    --bind 0.0.0.0:8000 \
    --workers 4 \
    --threads 2 \
    --timeout 120 \
    --access-logfile - \
    --error-logfile - \
    --log-level info \
    --name mysite

2.2 Gunicorn配置文件

# gunicorn.conf.py

import multiprocessing
import os

# 服务器绑定
bind = '127.0.0.1:8000'

# Worker数量(推荐 2 * CPU核心数 + 1)
workers = multiprocessing.cpu_count() * 2 + 1

# 每个Worker的线程数
threads = 2

# Worker超时时间(秒)
timeout = 120

# 最大并发请求数(之后重启worker,防止内存泄漏)
max_requests = 5000
max_requests_jitter = 500

# 日志
accesslog = '/var/log/gunicorn/access.log'
errorlog = '/var/log/gunicorn/error.log'
loglevel = 'info'

# 进程命名
proc_name = 'mysite'

# Worker临时目录
worker_tmp_dir = '/dev/shm'  # 使用内存文件系统提升性能

# 预加载应用(减少内存占用,但更新需重启)
preload_app = True

# 优雅重启
graceful_timeout = 30
keepalive = 5

2.3 Systemd服务配置

# /etc/systemd/system/mysite.service

[Unit]
Description=MySite Django Application
After=network.target postgresql.service redis.service

[Service]
Type=notify
User=www-data
Group=www-data
WorkingDirectory=/var/www/mysite
ExecStart=/var/www/mysite/venv/bin/gunicorn \
    --config /var/www/mysite/gunicorn.conf.py \
    mysite.wsgi:application
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=30
PrivateTmp=true
Environment="DJANGO_SETTINGS_MODULE=mysite.settings"

[Install]
WantedBy=multi-user.target

# 管理命令
sudo systemctl start mysite
sudo systemctl enable mysite    # 开机自启
sudo systemctl status mysite
sudo systemctl reload mysite    # 优雅重载
sudo systemctl restart mysite

三、Nginx配置

# /etc/nginx/sites-available/mysite

# HTTP → HTTPS重定向
server {
    listen 80;
    server_name yourdomain.com www.yourdomain.com;
    return 301 https://$server_name$request_uri;
}

# HTTPS配置
server {
    listen 443 ssl http2;
    server_name yourdomain.com www.yourdomain.com;

    # SSL证书
    ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;

    # SSL优化
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;

    # 安全头
    add_header X-Frame-Options DENY;
    add_header X-Content-Type-Options nosniff;
    add_header X-XSS-Protection "1; mode=block";
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    # 日志
    access_log /var/log/nginx/mysite_access.log;
    error_log /var/log/nginx/mysite_error.log;

    # 静态文件(Nginx直接处理,性能远超Django)
    location /static/ {
        alias /var/www/mysite/staticfiles/;
        expires 30d;
        add_header Cache-Control "public, immutable";
        gzip on;
        gzip_types text/css application/javascript application/json image/svg+xml;
    }

    # 媒体文件
    location /media/ {
        alias /var/www/mysite/media/;
        expires 7d;
    }

    # favicon
    location = /favicon.ico {
        alias /var/www/mysite/staticfiles/favicon.ico;
        access_log off;
        log_not_found off;
    }

    # 代理到Gunicorn
    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # 超时设置
        proxy_connect_timeout 30s;
        proxy_read_timeout 120s;
        proxy_send_timeout 120s;

        # 缓冲设置
        proxy_buffering on;
        proxy_buffer_size 4k;
        proxy_buffers 8 4k;
    }

    # 文件上传大小限制
    client_max_body_size 10M;
}

四、Docker部署

4.1 Dockerfile

# Dockerfile

# 多阶段构建
FROM python:3.12-slim AS builder

WORKDIR /app

# 安装系统依赖
RUN apt-get update && apt-get install -y --no-install-recommends \
    build-essential \
    libpq-dev \
    && rm -rf /var/lib/apt/lists/*

# 安装Python依赖
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 生产阶段
FROM python:3.12-slim

WORKDIR /app

# 安装运行时依赖
RUN apt-get update && apt-get install -y --no-install-recommends \
    libpq5 \
    curl \
    && rm -rf /var/lib/apt/lists/*

# 从builder复制Python包
COPY --from=builder /usr/local/lib/python3.12/site-packages/ /usr/local/lib/python3.12/site-packages/
COPY --from=builder /usr/local/bin/ /usr/local/bin/

# 复制项目代码
COPY . .

# 收集静态文件
RUN python manage.py collectstatic --noinput

# 创建非root用户
RUN useradd -m -r appuser && chown -R appuser:appuser /app
USER appuser

# 健康检查
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
    CMD curl -f http://localhost:8000/health/ || exit 1

EXPOSE 8000

CMD ["gunicorn", "mysite.wsgi:application", "--bind", "0.0.0.0:8000", "--workers", "4"]

4.2 Docker Compose

# docker-compose.yml

version: '3.8'

services:
  # Django应用
  web:
    build: .
    container_name: mysite-web
    restart: always
    env_file: .env
    volumes:
      - ./media:/app/media
      - staticfiles:/app/staticfiles
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    networks:
      - mysite-network

  # Nginx
  nginx:
    image: nginx:1.25-alpine
    container_name: mysite-nginx
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d
      - ./nginx/ssl:/etc/nginx/ssl
      - staticfiles:/var/www/staticfiles:ro
      - ./media:/var/www/media:ro
    depends_on:
      - web
    networks:
      - mysite-network

  # PostgreSQL
  db:
    image: postgres:16-alpine
    container_name: mysite-db
    restart: always
    environment:
      POSTGRES_DB: mysite
      POSTGRES_USER: mysite
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U mysite"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - mysite-network

  # Redis
  redis:
    image: redis:7-alpine
    container_name: mysite-redis
    restart: always
    command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD}
    volumes:
      - redis_data:/data
    networks:
      - mysite-network

  # Celery Worker
  celery:
    build: .
    container_name: mysite-celery
    restart: always
    command: celery -A mysite worker -l info --concurrency=4
    env_file: .env
    depends_on:
      - redis
      - db
    networks:
      - mysite-network

  # Celery Beat(定时任务)
  celery-beat:
    build: .
    container_name: mysite-celery-beat
    restart: always
    command: celery -A mysite beat -l info
    env_file: .env
    depends_on:
      - redis
    networks:
      - mysite-network

volumes:
  postgres_data:
  redis_data:
  staticfiles:

networks:
  mysite-network:
    driver: bridge

五、Celery异步任务

# 安装
pip install celery redis

# mysite/celery.py
import os
from celery import Celery

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
app = Celery('mysite')
app.config_from_object('django.conf:settings', namespace='CELERY')
app.autodiscover_tasks()

# settings.py
CELERY_BROKER_URL = 'redis://localhost:6379/0'
CELERY_RESULT_BACKEND = 'redis://localhost:6379/0'
CELERY_ACCEPT_CONTENT = ['json']
CELERY_TASK_SERIALIZER = 'json'
CELERY_RESULT_SERIALIZER = 'json'
CELERY_TIMEZONE = 'Asia/Shanghai'

# blog/tasks.py
from celery import shared_task
from django.core.mail import send_mail

@shared_task
def send_welcome_email(user_id):
    """发送欢迎邮件"""
    from django.contrib.auth.models import User
    user = User.objects.get(id=user_id)
    send_mail(
        '欢迎注册',
        f'你好 {user.username},欢迎加入我们!',
        'noreply@example.com',
        [user.email],
    )

@shared_task
def generate_report():
    """生成每日报告"""
    # 生成统计报告
    pass

# 在视图中调用
from .tasks import send_welcome_email

def register(request):
    # ...
    send_welcome_email.delay(user.id)  # 异步执行

六、性能优化

6.1 数据库优化

# 1. 使用select_related减少查询(ForeignKey/OneToOne)
# 不好:N+1查询问题
articles = Article.objects.all()
for article in articles:
    print(article.category.name)  # 每次循环都查询一次数据库

# 好:使用select_related(JOIN查询)
articles = Article.objects.select_related('category', 'author').all()
for article in articles:
    print(article.category.name)  # 不再额外查询

# 2. 使用prefetch_related减少查询(ManyToMany/反向ForeignKey)
articles = Article.objects.prefetch_related('tags').all()

# 3. 组合使用
articles = Article.objects.select_related(
    'category', 'author'
).prefetch_related(
    'tags', 'comments'
).filter(status='published')

# 4. 只查询需要的字段
articles = Article.objects.only('title', 'slug', 'created_at')

# 5. 使用defer排除大字段
articles = Article.objects.defer('content')  # 不查询content字段

# 6. 批量操作
Article.objects.bulk_create([
    Article(title='文章1', slug='article-1', author=user),
    Article(title='文章2', slug='article-2', author=user),
    Article(title='文章3', slug='article-3', author=user),
])

Article.objects.bulk_update(articles, ['title', 'status'])

# 7. 添加数据库索引
class Meta:
    indexes = [
        models.Index(fields=['-created_at']),
        models.Index(fields=['status', 'is_top']),
    ]

6.2 查询性能分析

# 查看查询SQL
print(Article.objects.filter(status='published').query)

# 查看查询次数(检测N+1问题)
from django.db import connection
queries = connection.queries
print(f"Total queries: {len(queries)}")
for q in queries:
    print(q['sql'], q['time'])

# 使用assertNumQueries测试查询次数
from django.test.utils import override_settings

def test_query_count(self):
    with self.assertNumQueries(2):  # 期望只有2次查询
        response = self.client.get('/api/articles/')

6.3 静态文件优化

# settings.py
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'

# 收集静态文件
python manage.py collectstatic

# 使用WhiteNoise直接从Django提供静态文件(适合小项目)
pip install whitenoise

# settings.py
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'whitenoise.middleware.WhiteNoiseMiddleware',  # 紧跟Security之后
    ...
]

STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'

七、环境变量管理

# 安装python-dotenv
pip install python-dotenv

# .env文件(不提交到Git)
DEBUG=False
SECRET_KEY=your-secret-key-here
DATABASE_URL=postgres://user:pass@localhost:5432/mysite
REDIS_URL=redis://localhost:6379/0
ALLOWED_HOSTS=yourdomain.com,www.yourdomain.com
EMAIL_HOST=smtp.gmail.com
EMAIL_PORT=587

# settings.py
from dotenv import load_dotenv
import os

load_dotenv()

SECRET_KEY = os.environ.get('SECRET_KEY')
DEBUG = os.environ.get('DEBUG', 'False') == 'True'
ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', '').split(',')

# 使用dj-database-url解析数据库URL
pip install dj-database-url

import dj_database_url
DATABASES = {
    'default': dj_database_url.parse(
        os.environ.get('DATABASE_URL'),
        engine='django.db.backends.postgresql',
    )
}

八、CI/CD自动化部署

# .github/workflows/deploy.yml

name: Deploy

on:
  push:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'
      - run: pip install -r requirements.txt
      - run: python manage.py test

  deploy:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Deploy to server
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            cd /var/www/mysite
            git pull origin main
            pip install -r requirements.txt
            python manage.py migrate
            python manage.py collectstatic --noinput
            sudo systemctl restart mysite

九、监控与运维

# 健康检查端点
from django.http import JsonResponse
from django.db import connection

def health_check(request):
    """健康检查API"""
    checks = {'database': False, 'cache': False}

    try:
        connection.ensure_connection()
        checks['database'] = True
    except Exception:
        pass

    from django.core.cache import cache
    try:
        cache.set('health', 'ok', 1)
        checks['cache'] = cache.get('health') == 'ok'
    except Exception:
        pass

    is_healthy = all(checks.values())
    return JsonResponse(checks, status=200 if is_healthy else 503)

十、系列总结

恭喜你完成了Django从零开始的全部学习!让我们回顾一下10章的核心内容:

章节 主题 核心内容
1 环境搭建 虚拟环境、项目创建、settings配置
2 模型与ORM 字段类型、关系字段、CRUD、高级查询
3 视图与路由 FBV/CBV、URL配置、请求响应对象
4 模板系统 模板语法、继承、自定义标签过滤器
5 表单处理 Form/ModelForm、验证器、文件上传
6 用户认证 注册登录、权限系统、社交登录
7 REST API DRF序列化器、视图集、JWT认证
8 中间件/信号/缓存 请求管道、事件处理、Redis缓存
9 测试与安全 单元测试、API测试、安全防护
10 部署优化 Gunicorn/Nginx、Docker、性能优化

掌握这些知识后,你已经具备了独立开发完整Django项目的能力。接下来建议:

  • 动手做一个完整的项目(博客系统、电商网站等)
  • 阅读Django官方文档深入理解
  • 学习前端框架(Vue.js/React)配合Django后端
  • 关注Django新版本的更新内容

祝你Django开发之旅一切顺利!🚀

发表回复

后才能评论