【相关的基础知识】
《【架构】了解常见的软件架构》
《【架构】分布式系统及相关技术栈初了解》 (*重要)
我们在部署 flask、django 等 python web 框架(站点)时,通常有两种选择方式,nginx+django+uwsgi或者django+nginx+gunicorn。(在linux下通常都使用nginx,因为速度快,还经常做代理服务器,功能强大。)
网上最多的教程就是 nginx+gunicorn/uwsgi 的部署方式,那为什么要这么部署呢?
这里必须要知道的一个概念,WSGI,web service gateway interface(网络服务网关接口)。它不是 web server,也不是 web application,它是架在 server 和 application 之间的一种协议和规范。
此协议是Python语言的专利,它定义了一组在Web服务宿主程序和HTTP响应代理程序之间通信的普遍适用的接口。
- 它的产生是因为Python程序员注意到,对于Web框架和Web宿主服务器程序间,有严重的耦合性,比如说,某些框架是针对Apache的mod_python设计的。于是,WSGI就定义了一套非常低级别的接口。
- 常见的Python Web框架都实现了这个协议:如 CherryPy, Django, web.py, web2py, TurboGears, Tornado, Pylons, BlueBream, Google App Engine[dubious – discuss], Trac, Flask, Pyramid,等等.
- 关于网关协议CGI/FCGI/SCGI/WSGI对比可参考:https://landybird.github.io/python/2017/09/19/Django%E9%83%A8%E7%BD%B2/
gunicorn 和 uWSGI 就是实现了 WSGI 协议的 web server。
而 flask、django 等 web 框架也是用的 wsgi 协议,所以需要在 web framework 之前加上 gunicorn 或者 uwsgi。话说回来,python web 框架都自带 wsgi 服务器,为什么还要这俩呢?一句话,性能太差,只能用于开发环节。
*wsgiref模块(django 框架自带)
django 框架自带的wsgiref模块是用于测试和学习的简单的WSGI服务器模块。
- 这个模块监听8000端口,把Http请求,根据WSGI协议,转换application函数中的environ参数,然后调用application函数。
- wsgiref会把application函数提供的响应头设置转换为HTTP协议的响应头,把application的返回(return)作为响应体,根据HTTP协议,生成响应,返回给浏览器。
django自带wsgi server vs 部署uwsgi+nginx后的性能对比可参考:https://www.shuzhiduo.com/A/GBJrnnQEJ0/
Nginx是俄罗斯人Igor Sysoev编写的轻量级Web服务器 ,它不仅是一个高性能的HTTP和反向代理服务器,同时也是一个IMAP/POP3/SMTP 代理服务器。Nginx以事件驱动的方式编写,所以有非常好的性能,同时也是一个非常高效的反向代理、负载平衡服务器。
对于大多数使用者来说,Nginx只是一个静态文件服务器或者http请求转发器,它可以把静态文件的请求直接返回静态文件资源,把动态文件的请求转发给后台的处理程序,例如php-fpm、apache、tomcat、jetty等,这些后台服务,即使没有nginx的情况下也是可以直接访问的(有些时候这些服务器是放在防火墙的面,不是直接对外暴露,通过nginx做了转换)。
*扩展之perfork模型
perfork是一种服务端编程模型,Nginx、Gunicorn,、uWSGI都是这种模型的实现。
- 简单的说,perfok就是master进程启动注册一堆信号处理函数,创建listen socket fd,fork出多个worker子进程,子进程执行accept循环处理请求(这里简化模型,当然也可以用select、epoll多路复用)
- master进程只负责监控worker进程状态,通过pipeline通信来控制worker进程
perfork模型使用master进程来监控worker进程状态,避免了我们使用supervisor来监控进程,还支持多种信号来控制worker的数量,使得CPU能充分得到利用,多个worker进程监听同一端口,可以配置reuse_port参数在worker进程间负载均衡。
*扩展之gunicorn 的管理机制
在管理 worker 上,gunicorn 使用了 pre-fork 模式,即一个 master 进程管理多个 worker 进程,所有请求和响应都由 worker 执行,master 就是一个 loop,监听 worker不同进程信号并且作出响应。
- 比如接受到 TTIN 提升 worker 数量,TTOU 降低运行 Worker 数量。如果 worker 挂了,发出 CHLD, 则重启失败的 worker, 同步的 Worker 一次处理一个请求。
结合起来的效果是什么呢, 做个简单比喻:
看起来不错,但存在以下问题:
- gunicorn 如果要实现复杂功能,其配置比较复杂
- gunicorn 有些功能是无法实现的,比如 访问控制、限速、限制连接数等
- gunicorn 不支持 https,当然高版本支持,但是不如 nginx
- gunicorn 不支持 http1.1
- gunicorn 无法扛住巨大的并发量
只做适合的事,没有绝对的谁只能做什么事。
在使用Django的时候可能会考虑项目到底如何组织?只有一个app的时候不要紧,有两个、三个甚至多个app的时候,模板(templates)要放在那里,静态文件(static files)放在哪里?这里仅是一个参考:
以一个myproject项目为例,这个项目由两个app组成:
为什么要这么做?这个在后面生产境部署的时候优势就会显现出来。
前提:Django 已安装,若想查看安装的是哪个版本,通过在命令提示行输入命令python -m django --version
相关指令:
$ django-admin.py startproject myproject # 1)会在当前目录下创建一个 myproject 目录。
$ cd myproject
$ python manage.py startapp myapp1 # 2)创建app
$ python manage.py startapp myapp2
$ touch requirements.txt # 新建requirement.txt
$ mkdir myapp1/{
static,templates} # 新建两个空文件夹
$ mkdir myapp2/{
static,templates}
1)创建目录后:
├── myproject
│ ├── manage.py # manage.py: 一个让你用各种方式管理 Django 项目的命令行工具。
│ └── myproject
│ ├── __init__.py
│ ├── asgi.py
│ ├── settings.py # settings.py:Django 项目的配置文件。
│ ├── urls.py # urls.py:Django 项目的 URL 声明,就像你网站的“目录”。
│ └── wsgi.py # wsgi.py:作为你的项目的运行在 WSGI 兼容的Web服务器上的入口。
2)创建app后:
├── manage.py
├── myapp1
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ │ └── __init__.py
│ ├── models.py
│ ├── tests.py
│ └── views.py
└── myproject
├── __init__.py
├── __pycache__
│ ├── __init__.cpython-37.pyc
│ └── settings.cpython-37.pyc
├── asgi.py
├── settings.py
├── urls.py
└── wsgi.py
3)最终:
├── manage.py
├── myapp1
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ │ └── __init__.py
│ ├── models.py
│ ├── static
│ ├── templates
│ ├── tests.py
│ └── views.py
├── myapp2
│ ├── __init__.py
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ │ └── __init__.py
│ ├── models.py
│ ├── static
│ ├── templates
│ ├── tests.py
│ └── views.py
├── myproject
│ ├── __init__.py
│ ├── __pycache__
│ │ ├── __init__.cpython-37.pyc
│ │ └── settings.cpython-37.pyc
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
└── requirements.txt
如果使用了大量第三方Django插件,如何方便管理?有没有办法一次pip全部安装?(试想想在生产环境的每一个服务器上手动安装一百个依赖包)这就是requirements.txt的作用。requirements.xt是一个txt文本,格式为依赖包名称==依赖包版本,一行一个。
# myproject/requirements.txt
Django==3.1.1
PyMySQL==0.10.1
requests==2.21.0
pytz==2020.1
安装所有依赖包只需要指令pip install -r requirements.txt
以上说的方法很简单,一个项目一个settings.py,本地使用时没有问题。到了生产环境,问题来了。Debug不能再设为True(这个选项是为了调试方便,如果在生产环境打开的话就有种"裸奔“的感觉,因为任何人都可以看到你的环境变量,文件目录等等),静态文件,模板文件的路径不能设为相对路径,等等。
那么怎么处理开发环境和生产环境设置的不同呢?
原理跟Ruby on Rails很像,那就是不同环境用不同的配置文件。
dev.py对应开发环境(development),prod.py对应生产环境(production),而test.py对应测试环境。(根据需要还可以加入staging.py)
$ rm myproject/settings.py # 删除原来的settings.py
$ mkdir myproject/settings
$ touch myproject/settings/{
__init.py,dev.py,prod.py,test.py}
然后在调用manage.py的时候指定–settings。
python manage.py migrate --settings=myproject.settings.prod
settings的具体内容可以参考:https://www.cnblogs.com/fmgao-technology/p/10118781.html
同理,对于requirements.txt也可以模块化: dev.txt,prod.txt,test.txt,再加上一个common.txt用来放共享的依赖包。
$ rm requirements.txt # 删除原来的requirements.txt
$ mkdir requirements
$ touch requirememts\{
common.txt,dev.txt,prod.txt,test.txt}
详细的可参考:
- https://sfdye.com/post/2014-03-09-django-best-practice-and-deployment-with-nginx-gunicorn-and-supervisor/
(较旧以前的版本了)- https://cloud.tencent.com/developer/article/1704281
【部分内容参考自】