聊聊Django应用的部署和性能的那些事儿

随着工作的深入,我越来越发现Python Web开发中有很多坑,也一直在羡慕AspNetCore和Go等的可执行文件部署和高性能,以及Spring生态的丰富,不过因为工作用了Django,生活还是要继续的嘛,这Django好歹也是有很大份额的Web框架,也没那么不堪,至少开发速度上就吊打一众框架了~

在之前的文章里我介绍过使用Docker部署Django应用的方法,不过那种部署方式只适合上线调试的场景,直接使用Django内置的Web服务器提供服务,单线程,性能很低,真正上线服务,还是要uwsgi配合nginx部署。

理清思路

以前我写过文章介绍了Django应用使用uwsgi+nginx在Linux服务器上直接的部署方法,现在我们为了方便管理,使用Docker来部署Django应用。

我看到网上很多方案都是把nginx也一起用了docker,感觉有些不妥,我们服务器上可能有很多个项目,一个项目一个nginx,多占用的资源不说,就配置而言,也要麻烦不少。

一个装在系统里的nginx是完全足够的,也方便配置,就我们团队而言,公司的网安做得比较严格,对服务器开放的端口和申请的域名有限制,没办法一个应用开一个端口,只能用nginx监听一个端口并且配置反向代理来实现多个项目的服务,所以一个nginx就很方便了。

我们的思路是django和uwsgi放在docker里,老办法,使用docker-compose编排相关依赖的数据库、缓存、监控服务,然后使用nginx做反向代理和提供静态文件服务,为了静态文件和代码热更新,还要做volume映射。

接下来一步步介绍构建一个方便的uwsgi+django镜像的方法。

Dockerfile编写

首先是我们的镜像文件,相比起之前的开发镜像,多了一个uwsgi模块,其他的不变。

FROM python:3.7

# 设置 python 环境变量
ENV PYTHONUNBUFFERED 1

# 创建 code 文件夹并将其设置为工作目录
RUN mkdir /code
WORKDIR /code

# 更新 pip
RUN pip install -i //mirrors.aliyun.com/pypi/simple pip -U
# 设置国内源
RUN pip config set global.index-url //mirrors.aliyun.com/pypi/simple

# 将 requirements.txt 复制到容器的 code 目录
ADD requirements.txt /code/

# 安装库
RUN pip install -r requirements.txt

# 将当前目录复制到容器的 code 目录
ADD . /code

# 安装uwsgi
RUN pip install uwsgi

uwsgi的ini配置

uwsgi的配置我选择ini的形式,文件内容如下:

[uwsgi]
socket = :8000

# the base directory (full path)
chdir = /code

# Django s wsgi file
module = config.wsgi

# 启用主进程,当进程挂掉会再spawn一个(以下三个指标,实现“并发”)
master = true
# 进程个数
processes = 4
# 每个进程中的线程个数/workers
threads = 2
# 一个工作进程最大请求数
max-requests = 5000

vacuum = true

# pid文件,用于脚本启动、停止该进程
pidfile = /code/uwsgi.pid

# 当文件改变时,优雅的重启uWSGI
touch-reload = /code/readme.md

注意一点,在之前的配置中,我们是用daemonize = log/uwsgi.log来配置日志文件路径实现uwsgi输出日志的,但是如果docker这样做的话,会导致docker-compose启动这个容器的时候直接执行完成自动退出,所以权宜之计只能不输出日志文件,输出到控制台,这样保持容器不被关闭(以后有更好的方法我会更新文章进行记录)

还有我用了touch-reload来监控文件修改自动重启uwsgi,这样就可以实现代码的热更新了(严格来说不算真的热更新哈哈哈。但够用了)每次更新代码的同时修改一下readme文件,可以记录一下更新内容,又自动重启了服务,多方便~

不过这样的热更新会带来短暂的 bad request ,所以尽量选择流量少的时段进行服务更新~

目前的系统还比较简单,所以我的方案是在测试服务器测试没问题就会同步代码到线上服务器~ 由于前端是小程序且用户量还不大,所以用户基本无感知~

Docker-Compose

这个没啥好说啦,就改了一下command而已~

version: "3"
services:
  redis:
    image: redis
    expose:
      - 6379
  web:
    restart: always # 除正常工作外,容器会在任何时候重启,比如遭遇 bug、进程崩溃、docker 重启等情况。
    build: .
    environment:
      - ENVIRONMENT=docker
    command: uwsgi uwsgi.ini
    volumes:
      - .:/code
    ports:
      - "19001:8000"
    depends_on:
      - redis

经过以上的步骤,一个简单的docker-compose up命令就把我们的服务跑起来了,再在nginx里配置一下反向代理和静态文件就美滋滋了,关于nginx的配置的,我打算下一篇文章再讲。

最后就是关于性能了,经过以上的部署,经过测试这个系统在目前没有任何优化的情况下可以承受最高1800 QPS,可能大佬会觉得太低了,嗯Python这性能确实…但按照目前我们的产品来说完全足够(超出),不行就让公司堆配置,反正是云服务随便扩容(公司服务器和钱都管够哈哈哈)以后按需优化…溜了哈哈哈