【部署】了解nginx+gunicorn+django架构


【相关的基础知识】
《【架构】了解常见的软件架构》
《【架构】分布式系统及相关技术栈初了解》 (*重要)


1)为什么是nginx+gunicorn+django架构?

我们在部署 flask、django 等 python web 框架(站点)时,通常有两种选择方式,nginx+django+uwsgi或者django+nginx+gunicorn。(在linux下通常都使用nginx,因为速度快,还经常做代理服务器,功能强大。)

网上最多的教程就是 nginx+gunicorn/uwsgi 的部署方式,那为什么要这么部署呢?

1、首先了解一下相关概念:

  • WSGI:

    这里必须要知道的一个概念,WSGI,web service gateway interface(网络服务网关接口)。它不是 web server,也不是 web application,它是架在 server 和 application 之间的一种协议和规范

    在这里插入图片描述

    • WSGI 的目的就是解耦 web server 和 web application,它包括两个部分,server 和 application,server 用来接收 web 客户端的请求,application 用来接收 server 传来的请求,然后传给 web server。
    • 关于WSGI的详细说明可参考:https://zhuanlan.zhihu.com/p/95942024

此协议是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:

    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:

    Nginx是俄罗斯人Igor Sysoev编写的轻量级Web服务器 ,它不仅是一个高性能的HTTP和反向代理服务器,同时也是一个IMAP/POP3/SMTP 代理服务器。Nginx以事件驱动的方式编写,所以有非常好的性能,同时也是一个非常高效的反向代理、负载平衡服务器。

    • 在性能上:Nginx占用很少的系统资源,能支持更多的并发连接(能支持高达50,000个并发连接数),达到更高的访问效率
    • 在功能上:Nginx是优秀的代理服务器和负载均衡服务器
    • 在安装配置上:Nginx安装简单、配置灵活
    • 特点:占有内存少,并发能力强,事实上nginx的并发能力在同类型的网页服务器中表现较好
    • Nginx支持热部署,启动速度特别快,还可以在不间断服务的情况下对软件版本或配置进行升级,即使运行数月也无需重新启动。

    对于大多数使用者来说,Nginx只是一个静态文件服务器或者http请求转发器,它可以把静态文件的请求直接返回静态文件资源,把动态文件的请求转发给后台的处理程序,例如php-fpm、apache、tomcat、jetty等,这些后台服务,即使没有nginx的情况下也是可以直接访问的(有些时候这些服务器是放在防火墙的面,不是直接对外暴露,通过nginx做了转换)。


2、uWSGI VS. Gunicorn

  • 首先两种都是实现WSGI协议的Web服务器,并且都是基于Perfork模型。
  • 其次Uwsgi是通过C语言编写的,Gnnicorn是通过Python语言编写的,相对于Uwsgi,Gunicorn相对于简单,启动也十分方便
  • gunicorn 多进程,充分利用服务器资源,可以支持一些并发量不大的web

*扩展之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 一次处理一个请求。

3、多层部署的原理:

结合起来的效果是什么呢, 做个简单比喻:

  • flask webServer + flask app:弱鸡版的server,单进程(单 worker),该进程挂掉,web 服务挂掉,无法管理
    • server 是 web framework 自带的框架,很容易挂掉,单 worker 工作对多核 cpu 服务器来说是一种浪费
    • 无法对 worker 进行管理,挂了你不知道,你知道了也只能重启
    • 而 gunicorn 是可以对 worker 进行管理的

  • gunicorn + flask app:多进程(多 worker)server,失败自动重启该 worker,简单管理
    • server 加上了 gunicorn,gunicorn 相当于是开启了多个进程,它具有以下优点:
    1. 可以调节 worker 的数量,在请求较多时,自动新增 worker,请求较少时,自动减少 worker
    2. 帮我们管理 worker,worker 挂了自动重启
    3. 支持多种配置
    4. 各种框架都适用,且部署方法相同

看起来不错,但存在以下问题:

  • gunicorn 如果要实现复杂功能,其配置比较复杂
  • gunicorn 有些功能是无法实现的,比如 访问控制、限速、限制连接数等
  • gunicorn 不支持 https,当然高版本支持,但是不如 nginx
  • gunicorn 不支持 http1.1
  • gunicorn 无法扛住巨大的并发量
  • nginx + gunicorn + flask app:反向代理,负载均衡,是不是更牛了
    • server 加上 nginx,只为更加高效更加健壮的 web 服务,nginx 的作用:
    1. 负载均衡:有效的调度 request,而 gunicorn 虽然是多进程,但是没不能主动地对 request 进行调度。
    2. 动静分离:经过配置后,nginx 可以直接处理静态请求,而无需经过 python web 服务器,这一点 gunicorn 没有。
    3. 缓存 request 和 response:web 请求包含各种浏览器和各种网络,故 http 请求的发起是一个比较慢的过程,而 gunicorn 需要等待整个请求结束,才处理该请求,并且等 web server 接收完这个请求后,才继续下一个。nginx 可以缓存客户端的请求,收完整个请求后,转发给 gunicorn,等 gunicorn 返回 response 后,再转发给客户端。这是 nginx 擅长,而 gunicorn 不擅长。

  • 多nginx + 多gunicorn + 多web app:大型多实例 web server,一般还会给 gunicorn 挂上 supervisor
    • server 可以通过 nginx 实现多后端、跨语言后端等高可用的负载均衡 web 服务器

4、总结:

只做适合的事,没有绝对的谁只能做什么事。

  • 为什么选择Nginx?

    • 首先Nginx功能强大,具备负载均衡、拦截静态请求、高并发的功能,而这架构主要是通过Nginx处理静态请求,uWSGI/Gunicorn处理动态请求
    • 一般我们会在Gunicorn/uWSGI前面再加一层Nginx, 这样做的原因有一下几点:
    1. 做负载均衡
    2. 静态文件服务器
    3. 更安全
    4. 扛并发
    • 用Nginx转发Gunicorn服务,重点是解决“慢客户端行为”给服务器带来的性能降低问题,另外,在互联网上部署HTTP服务时,还要考虑到“快客户端响应”、SSL处理和高并发等问题,而这些问题在Nginx上一并能搞定,所以在Gunicorn服务之上加一层Nginx反向代理,是个一举多得的部署方案。
      • Gunicorn 是一个pre-forking的软件,这类软件对低延迟的通信,如负载均衡或服务间的互相通信,是非常有效的。但pre-forking系统的不足是,每个通信都会独占一个进程,当向服务器发出的请求多于服务器可用的进程时,由于服务器端没有更多进程响应新的请求,其响应效率会降低
      • 把Nginx挡在pre-forking服务前面处理请求是一种很好的选择。Nginx能够异步、高并发的响应客户端request(慢客户端请求对Nginx影响不大),Nginx一旦接收到的请求后立刻转给Gunicorn服务处理,处理结果再由Nginx以response的形式发回给客户端。这样,整个服务端和客户端的通信,就由原来仅通过Gunicorn的同步通信,变成了基于Nginx和Gunicorn的异步通信,通信效率和并发能力得到大大提升
      • 关于为什么需要Nginx转发Gunicorn服务的详细解释可参考:https://blog.igevin.info/posts/how-to-deploy-flask-apps/

  • 为什么选择gunicorn?

    • gunicorn 服务器与各种 Web 框架兼容,只需非常简单的执行,轻量级的资源消耗,以及相当迅速。
      • 它的特点是与 Django 结合紧密,部署特别方便
      • 缺点也很多,不支持 HTTP 1.1,并发访问性能不高,与 uWSGI,Gevent 等有一定的性能差距,仅可以支持一些并发量不大的web。

在这里插入图片描述


2)django的部署

2.1 项目结构

在使用Django的时候可能会考虑项目到底如何组织?只有一个app的时候不要紧,有两个、三个甚至多个app的时候,模板(templates)要放在那里,静态文件(static files)放在哪里?这里仅是一个参考:

以一个myproject项目为例,这个项目由两个app组成:

  • 每个app有自己的static目录
  • 每个app有自己的templates目录
  • 根目录下与项目同名的文件夹(比如这里的myproject)为项目设置,包含settings.py
  • 根目录下有requirements.txt

为什么要这么做?这个在后面生产境部署的时候优势就会显现出来。


1、创建myproject项目的步骤:

  • 前提: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

2、用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


3、如何做到更好(更模块化)

以上说的方法很简单,一个项目一个settings.py,本地使用时没有问题。到了生产环境,问题来了。Debug不能再设为True(这个选项是为了调试方便,如果在生产环境打开的话就有种"裸奔“的感觉,因为任何人都可以看到你的环境变量,文件目录等等),静态文件,模板文件的路径不能设为相对路径,等等。

那么怎么处理开发环境和生产环境设置的不同呢?

1)不同环境的配置文件

原理跟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。

  • 注意:如果在生产环境使用了不同的settings.py,在数据库迁移或其他操作的时候需要指明它的位置。以migrate为例:python manage.py migrate --settings=myproject.settings.prod

settings的具体内容可以参考:https://www.cnblogs.com/fmgao-technology/p/10118781.html

2)不同环境的依赖库

同理,对于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}

2.2 Django + Nginx + Gunicorn 部署

1、手动部署

  1. 登录服务器,安装基础依赖包。
  2. 安装及配置Database。
  3. 项目设置 settings.py。
  4. 收集静态文件。
  5. Nginx设置。
  6. Gunicorn配置。
  7. DNS设置。

详细的可参考:

  • https://sfdye.com/post/2014-03-09-django-best-practice-and-deployment-with-nginx-gunicorn-and-supervisor/
    (较旧以前的版本了)
  • https://cloud.tencent.com/developer/article/1704281

2、自动部署

  1. 开发上传git
  2. 运维 :通过jenkins 软件 >> 自动化部署(git下载,上传到上线服务器)


【部分内容参考自】

  • nginx+gunicorn/uwsgi+python web 的前世今生:https://www.cnblogs.com/yanshw/p/11596308.html
  • uWSGI和Gunicorn服务器搭建部署:https://www.linshukai.cn/%E6%9C%89%E5%85%B3gunicorn%E3%80%81uwsgi%E6%AF%94%E8%BE%83%E5%88%86%E6%9E%90/
  • Django最佳实践与部署:Nginx + Gunicorn + Supervisor(Ubuntu和CentOS):https://sfdye.com/post/2014-03-09-django-best-practice-and-deployment-with-nginx-gunicorn-and-supervisor/
  • 编写你的第一个 Django 应用,第 1 部分:https://docs.djangoproject.com/zh-hans/2.1/intro/tutorial01/
  • 1
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值