首页 / 资讯中心 / 文章详情

Linux部署Django大型商城:从环境规划到Gunicorn与Nginx完整实战

Linux部署Django大型商城:从环境规划到Gunicorn与Nginx完整实战 ★ FEATURED ARTICLE
简介这是一套Linux部署环境下Django大型前后端商城项目的完整源码包适合具备Python与Web开发基础的工程师用于学习Django框架在电商领域的实际落地方式项目未集成支付宝与短信接口专注呈现商城核心业务逻辑与生产部署架构便于集中精力理解主体链路。压缩包共204个文件体积仅1.69MB其中99个Python源码文件承载模型、视图与业务处理逻辑30个HTML模板配合18个JavaScript脚本和3个CSS样式表构成前端页面另有SQL、配置文件与多张图片素材可直接对照学习前后端交互方式。目前已有230人学习下载。资源以manage.py、shopping_mall主应用和requirements依赖清单构成标准Django工程覆盖Linux环境配置、MySQL数据库接入、商品与订单模型设计、URL与视图映射、模板渲染、用户认证、Redis缓存、Celery异步任务以及Gunicorn与Nginx部署等完整链路目录结构清晰适合按模块逐步研读帮助开发者快速搭建并理解一个可用商城系统的整体架构。1. linux部署django大型前后端商城卡在部署链路而不是业务代码在linux部署环境下把django大型前后端商城完整跑起来真正难的不是商品模块或购物车逻辑而是整个部署链路Python环境、MySQL连接、Gunicorn进程、Nginx反向代理、静态资源和媒体文件分流任何一环都能让服务在上线前一小时突然翻车。我见过不少项目本地runserver一切正常push到服务器后图片404、接口502、Django重启起不来最后只能对着日志排查到后半夜。这篇文章会把一套我反复用过的部署方案完整拆开讲清从环境规划和账号开始一路到settings改造、Gunicorn托管、Nginx静态资源伺服最后是高频坑的排查方法。适合有Django开发基础、正准备把商城项目放到linux服务器的开发者和中小团队。2. linux部署前的环境规划账号、Python虚拟环境与中间件选型2.1 先定运行账号和目录结构为什么不要用root跑商城很多人在linux服务器上做第一版部署时习惯直接在root下pip install、python manage.py runserver最后商城确实能打开但隐患很大Django进程一旦被利用攻击者拿到的是root shell后来想换目录、改权限也会被root创建的只读文件卡住。常见做法是先建一个专用系统账号再把代码、虚拟环境、日志、上传文件分目录放。sudo useradd --system --home /srv/shop_demo --shell /usr/sbin/nologin shopuser sudo mkdir -p /srv/shop_demo/media /srv/shop_demo/static sudo mkdir -p /srv/shop_demo/logs /srv/shop_demo/run sudo chown -R shopuser:shopuser /srv/shop_demo这里--system表示系统账号不会出现在普通登录列表中--shell /usr/sbin/nologin让这个账号无法交互登录即使Gunicorn被攻破shell权限也受限。目录拆分后media放用户上传的商品图、头像static放collectstatic后的Django静态资源logs给Gunicorn和Nginx日志run放pid文件。日常部署我一般用sudo -u shopuser bash -c 命令切换身份执行而不是直接su因为nologin账号su进去会被拒。下一步需要把git仓库代码放进/srv/shop_demo注意代码目录所有者必须是shopuser。如果代码是用root clone的之后Gunicorn写日志、写入media都会报权限错误。遇到这类问题先回头看目录所有权这是我被问过最多的问题之一。2.2 用python3 venv隔离商城依赖创建命令与依赖全流程Django商城项目依赖很杂Django、DRF、mysqlclient、django-redis、django-cors-headers、Celery也可能有。如果直接装在系统Python里很容易和系统包冲突升级一个库搞崩另一个服务。linux部署下我基本只用python3 -m venv做隔离不用虚拟环境管理工具额外折腾。sudo apt-get update sudo apt-get install -y python3-venv python3-dev default-libmysqlclient-dev build-essential pkg-config sudo -u shopuser bash -c cd /srv/shop_demo python3 -m venv venv source venv/bin/activate pip install --upgrade pip pip install -r requirements.txtpython3-dev提供Python头文件default-libmysqlclient-dev是mysqlclient编译时需要的客户端开发库。如果缺这两个pip install mysqlclient会在编译阶段报mysql_config not found。build-essential和pkg-config同样是为了本地编译Python扩展。项目依赖下的requirements.txt里我一般会锁版本不锁版本号的话三个月后同一份requirements可能装出一套完全不同的依赖组合。安装完成以后验证一下/srv/shop_demo/venv/bin/python -c import django, mysqlclient能正常输出就说明环境可用。注意这里每次操作都要用绝对路径/srv/shop_demo/venv/bin/python不要在systemd或Nginx里依赖source activate那些环境默认shell不加载venv后面的坑基本都从这里来。2.3 中间件选型MySQL、Redis与异步队列到底装什么大型商城不等于每个组件都要上。我见到的失败案例是照着大厂架构图把MySQL、Redis、RabbitMQ、Elasticsearch全部装一遍结果单机资源全被中间件吃掉Django反而跑不动。选型要看现阶段真实规模。下面是我在初期商城项目里常用的判断表中间件商城里的角色初期要不要装部署注意点MySQL商品、订单、用户、库存持久化必须设utf8mb4调大innodb_buffer_pool_sizeRedis商品缓存、购物车、分布式锁、Celery broker必须开appendonly设maxmemory防止占满内存RabbitMQ订单超时、短信、支付回调等可靠异步消息不急着装先用Redis做broker消息量大再拆Elasticsearch商品搜索、筛选、分词看搜索复杂度MySQL里的LIKE会拖垮商品表但数据量小时不用上一键安装和启动命令很简单sudo apt-get install -y mysql-server redis-server然后systemctl enable --now mysql redis-server。真正要花时间的是配置MySQL要新建商城专用库和账号不能用root连DjangoRedis默认监听127.0.0.1就够不要暴露到公网。异步任务方面我最常用的做法是先让Celery使用Redis作为broker少维护一个RabbitMQ。等订单量起来、出现消息丢失或积压问题时再替换成RabbitMQ业务代码改动不大。3. Django生产配置改造settings拆分与静态媒体目录的四个关键调整3.1 把settings.py拆成包base、dev、prod各自管什么商城项目里一个settings.py写到三四百行是常态生产配置和开发配置混在一起上线时临时关DEBUG、改ALLOWED_HOSTS容易漏项。常见做法是把settings改成包结构按环境拆开shop_demo/settings/ ├── __init__.py ├── base.py # 公共配置 ├── dev.py # 开发环境 └── prod.py # 生产环境__init__.py保持空文件即可关键是在启动命令里指定模块。manage.py里默认走dev.py生产用Gunicorn时通过环境变量DJANGO_SETTINGS_MODULEshop_demo.settings.prod切换。prod.py从base导入全部公共配置只覆盖生产差异# prod.py import os from .base import * DEBUG False ALLOWED_HOSTS os.environ.get(DJANGO_ALLOWED_HOSTS, 127.0.0.1).split(,) SECRET_KEY os.environ[DJANGO_SECRET_KEY] DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: os.environ[DB_NAME], USER: os.environ[DB_USER], PASSWORD: os.environ[DB_PASSWORD], HOST: os.environ.get(DB_HOST, 127.0.0.1), PORT: os.environ.get(DB_PORT, 3306), CONN_MAX_AGE: 60, OPTIONS: {charset: utf8mb4}, } } CACHES { default: { BACKEND: django_redis.cache.RedisCache, LOCATION: os.environ.get(REDIS_URL, redis://127.0.0.1:6379/1), OPTIONS: {CLIENT_CLASS: django_redis.client.DefaultClient}, } } MEDIA_URL /media/ MEDIA_ROOT /srv/shop_demo/media STATIC_URL /static/ STATIC_ROOT /srv/shop_demo/staticSECRET_KEY不能有默认值启动时没设置就直接报错这是故意的防止有人把生产密钥写进代码库。ALLOWED_HOSTS从环境变量读取避免每次改配置都要改文件。CONN_MAX_AGE60让Django复用MySQL连接减少握手次数对商城这种请求量上来的场景很重要。utf8mb4保证商品标题里的emoji和生僻字不会变成问号这个坑在商城评论场景特别常见。3.2 生产环境数据库、缓存和静态媒体路径配置配置写完后第一次部署要按顺序执行迁移和静态文件收集。collectstatic对Django admin特别关键很多商城的后台管理系统是Django自带admin如果没收集静态资源admin页面会没有CSS长着一张“裸奔”的脸。sudo -u shopuser bash -c cd /srv/shop_demo source venv/bin/activate python manage.py migrate --noinput python manage.py collectstatic --noinput python manage.py check --deploy --settingsshop_demo.settings.prod--noinput避免命令中途询问确认适合部署脚本里执行。migrate是建表和数据迁移。check --deploy会按生产安全标准检查配置常见提示包括SECRET_KEY是否明文、DEBUG是否关闭、是否启用安全中间件等。它不会直接帮你改但会把问题列出来一条条对照处理即可。要注意collectstatic收集的是Django各类app里的静态资源对前后端分离商城来说前端Vue或React的构建产物不应该塞进STATIC_ROOT它应该由Nginx直接伺服独立目录。否则前端构建一次就要往Django静态目录拷一次职责混乱。媒体文件是用户上传的商品图根本不会出现在collectstatic结果里所以MEDIA_ROOT要指向一个独立目录后续迁移对象存储时整个目录搬走即可。3.3 跨域与Cookie前后端分离商城必须处理的CORS前后端分离商城最常见的部署形态有两种。第一种是同一域名下用路径区分例如https://shop.example.com/放前端页面/api/走Django接口这种同域部署不需要CORS。第二种是前端域名和API域名不同比如https://shop.example.com对https://api.example.com这种必须处理跨域。我建议小团队优先选同域部署省掉CORS相关的所有问题但如果你有独立客户端域名或未来要开放API跨域配置也要会。# prod.py 追加 INSTALLED_APPS [corsheaders] MIDDLEWARE [corsheaders.middleware.CorsMiddleware] MIDDLEWARE CORS_ALLOWED_ORIGINS [https://shop.example.com] CORS_ALLOW_CREDENTIALS True CSRF_TRUSTED_ORIGINS [https://shop.example.com]corsheaders要放在中间件最前面因为跨域请求的预检OPTIONS要第一时间拿到响应头。CORS_ALLOW_CREDENTIALSTrue表示允许带Cookie前提是前端用withCredentials: true发起请求。CSRF_TRUSTED_ORIGINS是Django 4以后单独要求的配置不加的话即使CORS通过POST/PUT/DELETE也容易被CSRF校验拦下。如果用JWT且把token存在前端localStorageAuthorization头不依赖Cookie可以不开Credentials但商城登录态用HttpOnly Cookie更安全所以这两项我一般同时配。4. 用Gunicorn和systemd把Django商城进程管起来worker数量与重启机制4.1 为什么runserver不能上生产Gunicorn的进程模型python manage.py runserver是开发服务器自带reload、单进程、性能很低而且它默认绑定127.0.0.1真要暴露公网也不安全。生产商城的Django应用需要一个WSGI服务器来承担并发请求Gunicorn是比较通用的方案。它的模型是master管理多个worker进程worker接收HTTP请求并调用Django的WSGI应用。选择Gunicorn而不是uWSGI的考量是配置简单、日志干净对Django生态支持好。uWSGI性能上限更高但配置项复杂大型团队愿意调优可以选中小商城项目用Gunicorn的人更多。注意Gunicorn是同步worker模型如果接口里有长时间阻塞操作worker会被占住后面的请求排队。所以商城里的导出、批量通知这类操作一定要丢给Celery异步执行否则再多的worker也不够耗。4.2 worker数量、并发类型和内存预算的计算方式网上最常看到的公式是2 * CPU核数 1它来自CPU密集型场景但商城API大部分时间在等MySQL、Redis返回属于IO密集型。如果只用sync worker一个worker同时只能处理一个请求MySQL查询一慢整个接口就排队。Gunicorn提供gthread类型让每个worker内开多线程更适合IO密集的Django商城。cd /srv/shop_demo DJANGO_SETTINGS_MODULEshop_demo.settings.prod \ /srv/shop_demo/venv/bin/gunicorn shop_demo.wsgi:application \ --bind 127.0.0.1:8000 \ --workers 4 \ --threads 4 \ --worker-class gthread \ --timeout 60 \ --graceful-timeout 30 \ --max-requests 2000 \ --max-requests-jitter 200 \ --access-logfile /srv/shop_demo/logs/gunicorn-access.log \ --error-logfile /srv/shop_demo/logs/gunicorn-error.log \ --pid /srv/shop_demo/run/gunicorn.pid--worker-class gthread决定用线程并发--threads 4表示每个worker内4个线程。worker数量不是越大越好一个Django worker大概占用150MB到250MB内存4个worker加线程开销差不多1GB。我常用的初始配置如下云主机配置建议workeersthreads大致并发能力适用阶段2核4G24同时8个请求刚上线、流量不大4核8G44同时16个请求常态业务8核16G64同时24个请求活动期前置--max-requests 2000和--max-requests-jitter 200的意思是worker每处理约2000个请求后自己重启jitter随机偏移防止多个worker同时到期同时重启造成服务空窗。这是对抗Python内存泄漏的后悔药商城系统跑几天后内存缓慢上涨很常见除非你有时间排查每个依赖否则先用这个机制兜底。4.3 systemd服务文件开机自启、崩溃拉起和优雅重启Gunicorn不能靠nohup后台挂了就不管服务器重启、进程崩溃、worker泄漏都需要一个守护者。systemd是linux发行版自带的进程管理配置好服务文件后可以让Gunicorn随机器启动、崩溃自动拉起、日志进journald。; /etc/systemd/system/shop-gunicorn.service [Unit] Descriptionshop_demo gunicorn Afternetwork-online.target mysql.service redis-server.service Wantsnetwork-online.target [Service] Typenotify Usershopuser Groupshopuser WorkingDirectory/srv/shop_demo EnvironmentFile/etc/shop_demo/shop.env ExecStart/srv/shop_demo/venv/bin/gunicorn shop_demo.wsgi:application \ --bind 127.0.0.1:8000 \ --worker-class gthread \ --workers 4 \ --threads 4 \ --timeout 60 \ --max-requests 2000 \ --max-requests-jitter 200 ExecReload/bin/kill -s HUP $MAINPID Restartalways RestartSec3 KillModemixed [Install] WantedBymulti-user.targetAfter里面带上mysql和redis服务名保证数据库和缓存在Gunicorn前先启动。Typenotify需要Gunicorn支持master进程真正ready后systemd才认为服务启动成功比固定sleep几秒更可靠。EnvironmentFile指向/etc/shop_demo/shop.env里面用KEYvalue格式存放所有敏感配置这个文件权限要设为600。ExecReload发送HUP信号给masterworker优雅处理完手头请求后再退出适合改配置后热加载。启动命令如下sudo mkdir -p /etc/shop_demo sudo touch /etc/shop_demo/shop.env sudo chmod 600 /etc/shop_demo/shop.env sudo systemctl daemon-reload sudo systemctl enable --now shop-gunicorn systemctl status shop-gunicorn如果启动失败先看journalctl -u shop-gunicorn -e不要反复restart。我见过最隐蔽的问题是EnvironmentFile权限不对systemd直接拒绝服务启动还有Typenotify配合旧版Gunicorn会出现启动超时这时要么去掉notify改成simple要么升级Gunicorn版本。5. 常见问题排查Nginx反向代理与前后端联调的5个坑5.1 一套同时伺服前端静态页与Django API的Nginx配置Nginx是整套部署的入口。它的核心任务是把/api/和/admin/请求转给Gunicorn把/static/和/media/直接用文件系统响应然后剩下的路径全部交给前端页面。一个常见错误是直接用Django伺服前端dist这既慢又不安全。下面是我常用的最简配置server { listen 80; server_name shop.example.com; client_max_body_size 20m; root /srv/shop_demo/frontend/dist; index index.html; gzip on; gzip_types text/css application/javascript application/json image/svgxml; location /api/ { 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 60s; } location /admin/ { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; } location /media/ { alias /srv/shop_demo/media/; expires 30d; add_header Cache-Control public, immutable; } location /static/ { alias /srv/shop_demo/static/; expires 30d; } location / { try_files $uri $uri/ /index.html; } }proxy_pass后不带斜杠所以/api/order/传到Django时URI不变如果写成http://127.0.0.1:8000/带斜杠会把/api前缀剥掉后端URL就必须去掉api前缀。商城API通常有统一前缀所以我保留原样。try_files $uri $uri/ /index.html是给前端history路由用的页面刷新时Nginx找不到对应路径就回退到首页由前端路由接管。这个配置下要注意location匹配规则/api、/media、/static这些前缀优先级高于/不会误落到前端回退逻辑。5.2 现象502Gunicorn没起来还是地址不对现象打开商城首页正常点接口请求后返回502Nginx错误日志里出现connect() failed (111: Connection refused) while connecting to upstream。原因Nginx把请求转发到127.0.0.1:8000但那里没有进程在监听。常见是Gunicorn服务没启动、启动后崩溃、或者bind地址写成了0.0.0.0:9000。还有一个隐藏原因systemd服务启动成功但worker异常退出master还没来得及拉起。解决先手动探测后端是否活着。执行curl -v http://127.0.0.1:8000/api/health/如果Connection refused说明Gunicorn没在跑。接着systemctl status shop-gunicorn看进程状态journalctl -u shop-gunicorn -e看崩溃日志。确认Gunicorn正常运行后再检查Nginx里的proxy_pass地址、端口和后端bind保持一致。我自己踩过最蠢的一次是改了Gunicorn端口但没改Nginx排查了半小时才想起来。5.3 现象504超时是表象慢在接口还是链路上现象下单接口偶尔请求很久后返回504 Gateway TimeoutNginx错误日志提示upstream timed out。原因Gunicorn的--timeout默认30秒Nginx的proxy_read_timeout默认60秒。商城某个接口调外部支付网关或MySQL慢查询耗时超过这两个阈值Nginx直接断开连接。这时候调大超时只是掩耳盗铃真正要解决的是接口为什么慢。解决先用日志定位。Gunicorn的access log里每行有响应时间找到耗时超过1秒的接口再看对应SQL和外部调用。如果是MySQL查询慢加索引、减少N1查询如果是支付回调这类耗时操作改成Celery异步处理接口立刻返回“处理中”。超时参数本身建议设成Gunicorn--timeout 60Nginxproxy_read_timeout 120s这是兜底不是优化。5.4 媒体404、admin裸奔和跨域403三个容易被忽略的配置点现象1商品图片上传成功但页面访问/media/xxx.jpg返回404。原因是Nginx的alias路径拼接错了。location /media/对应alias /srv/shop_demo/media/;如果alias少了末尾斜杠请求/media/goods/a.jpg时会拼成srv/shop_demo/mediagoods/a.jpg。同样Django里MEDIA_ROOT和Nginx alias的根目录必须指向同一个实际路径。现象2Django admin页面可以登录但CSS和JS全部丢失。原因是生产环境没有执行collectstatic或者Nginx的/static/指向了错误的目录。执行过一次collectstatic后还要确认/srv/shop_demo/static/admin/目录存在。如果存在但样式还是丢了大概率是Nginx的location匹配被前端回退规则覆盖注意/static/的location要写在location /之前Nginx取最长前缀匹配。现象3前端和API域名不同POST请求返回403浏览器控制台提示CSRF验证失败。原因是只配了CORS没配CSRF_TRUSTED_ORIGINS。Django对带Cookie的跨域请求要求信任来源域名所以要在prod.py里同时设置CORS_ALLOWED_ORIGINS和CSRF_TRUSTED_ORIGINS而且必须写完整协议和域名不能带路径。这是一个改一行就解决的配置但它最容易让人怀疑人生因为CORS响应头明明已经通过了。6. 上线后的快速验证与性能调优技巧6.1 用curl和Nginx日志找出隐藏慢接口上线后第一件事不是打开浏览器点页面而是对关键接口做一次带耗时统计的请求。下面这条命令会输出HTTP状态码和总耗时curl -s -o /dev/null -w code:%{http_code} total:%{time_total}s\n http://127.0.0.1:8000/api/health/把地址换成商品列表、商品详情、加入购物车、提交订单这几个商城核心接口逐个跑一遍。耗时超过500ms的接口要警惕。接着在Nginx配置里加一条自定义日志格式把$request_time打进access loglog_format timed $remote_addr [$time_local] $request $status $request_time; access_log /srv/shop_demo/logs/nginx-access.log timed;重新加载Nginx后运行一会儿用awk $NF 1 {print}筛出耗时大于1秒的请求按URL聚合就能知道商城目前最慢的接口是谁。不要靠浏览器F12感觉线上真实流量下的耗时才准确。6.2 Gunicorn平滑重载与真实流量压测更新代码后需要重启Gunicorn。直接systemctl restart会中断所有worker高峰时线上会出现几十个请求失败。正确做法是sudo systemctl reload shop-gunicorn这条命令通过ExecReload向master发送HUP信号worker处理完当前请求才退出新代码在下一批请求加载。整理发布流程时把这一步固定下来能避免很多“升级导致订单失败”的血泪经验。压测时不要一上来就压Gunicorn要先压完整Nginx链路。ab -n 2000 -c 50 https://shop.example.com/api/goods/可以粗略看吞吐和P95耗时。压测期间盯两个指标worker内存是否持续上涨、MySQL连接数是否打满。如果连接数飙高检查CONN_MAX_AGE是否生效如果内存只涨不降把--max-requests数值调小让worker更快轮换。6.3 MySQL连接和Redis缓存的两个调优点商城部署稳定后性能瓶颈通常会从代码转移到MySQL连接管理和缓存命中率。Django的CONN_MAX_AGE60能复用连接但MySQL默认wait_timeout可能是28800秒Django的连接在空闲后会被MySQL主动断开复用到一个已断开的连接时会出现MySQL server has gone away。常见做法是把映射关系调整成Connection寿命小于MySQL空闲阈值或者在启动健康检查请求检测连接可用性。另一个取舍是Redis缓存过期策略商品详情缓存15分钟、首页轮播缓存10分钟、库存变化实时失效。击穿和雪崩可以通过主动过期打散、热点数据永不过期加定时更新来缓解。我印象最深的一次排障是某个模拟商城项目上线前突然出现周期性的502查了Gunicorn日志后发现worker每隔几百个请求就重启一次重启窗口里的请求全部失败。原因就是--max-requests设置成500且没有jitter所有worker在同一时间点集体轮换。调成2000加jitter后问题再没出现过。从那以后我养成了一个习惯每次部署上线至少观察一小时Gunicorn日志重点看不正常重启和5xx状态码确认稳定后才算真正收工。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站