django项目在uwsgi+nginx上部署遇到的坑
本文来自网易云社区
作者:王超
问题背景
django框架提供了一个开发调试使用的WSGIServer, 使用这个服务器可以很方便的开发web应用。但是 正式环境下却不建议使用这个服务器, 其性能、安全性都堪忧。一个推荐的做法是使用uwsgi+Nginx来部署django应用。如何使用uwsgi部署不在本文的讨论范围里。
在大多数情况, WSGIServer下的能正常工作的代码, 在uwsgi中也能正常运行。 但是也有很多坑点, 导致uwsgi下的结果与WSGIServer的结果完全不同。 这里就来聊聊这些坑点。
坑点集锦
代码加载顺序
在使用WSGIServer开发时, django应用是通过python manage.py 0.0.0.0:80的命令来启动的, 这个命令对应的python代码就是
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)
而通过uwsgi部署django时, django应用是通过uwsgi -http 8080 --wsgi-file wsgi来启动的, 这个命令其实就是去加载wsgi.py中的代码
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()
应用的启动方式不同, 导致应用中各个模块的加载顺序也完全不同。
为了研究具体的加载顺序, 我们在ViewBase中加入了以下元类, 这个元类会在所有ViewBase的子类被创建时, 打印出此时的调用堆栈与进程ID(为什么要打印进程id, 后文后解释)
import tracebackclass MetaCls(type):
def __new__(cls, name, bases, attrs):
pid = os.getpid()
print( '%d proc load module: %s' % (pid, attrs["__module__"]) )
print( "".join(traceback.format_stack()) ) return super(MetaCls, cls).__new__(cls, name, bases, attrs)class ViewBase(object):
__metaclass__ = MetaCls
.......
首先使用python manage.py runserver启动应用, 发现打印出来的信息如下:
2017-04-24 16:22:23,095 __new__[line:231] thread:MainThread: 9428 proc load module: app.BsLogic.Admin.views
File "manage.py", line 26, in <module>
execute_from_command_line(sys.argv) File "D:\project\qaweb\django\core\management\__init__.py", line 367, in execute_from_command_line
utility.execute()
...... #这里省略django内部调用
File "D:\project\qaweb\django\core\checks\urls.py", line 14, in check_url_config # 从这里开始要加载urls了
return check_resolver(resolver)
...... #这里省略django内部调用
File "D:\project\qaweb\qaweb\urls.py", line 30, in <module>
urlpart = import_string(str_module)
...... #省略
File "D:\project\qaweb\app\BsLogic\Admin\__init__.py", line 3, in <module> # 这里开始就是我们写的代码了
import urls File "D:\project\qaweb\app\BsLogic\Admin\urls.py", line 4, in <module>
import views File "D:\project\qaweb\app\BsLogic\Admin\views.py", line 16, in <module>
class HotFix(ViewBase):
File "D:\project\qaweb\app\BsLogic\Common.py", line 232, in __new__
print( "".join(traceback.format_stack()) )
为了便于分析, 这里省略了django内部的调用。 可以发现, 程序的入口就是execute_from_command_line, 然后经过一系列的内部调用, 再开始加载urls, 因为urls会映射到我们写的views, 所以我们写的代码也会跟着加载, 简言之, 使用manage.py启动时, 我们写的所有相关代码(除了那些完全独立的代码), 都会在应用启动时全部加载。
然后, 我们使用uwsgi的方式启动应用, 发现竟然没有打印信息, 难道我们写的代码根本没有被加载。 为了弄清楚原因, 只能看django源码。 果然, 发现通过get_wsgi_application()启动应用时, 仅仅加载了中间件的代码
# wsgi.py application = get_wsgi_application()# django/core/wsgi.py line 14return WSGIHandler()# django/core/handlers/wsgi.py line 153self.load_middleware()# django/core/handlers/base.pyload_middleware(self)
为了验证想法, 我们在中间件代码中加入打印堆栈的语句, 然后重启服务, 这样打印出来的堆栈是:
File "/home/wc/wangchao/mqaDjango/qaweb/wsgi_django.py", line 13, in <module>
application = get_wsgi_application()
File "./django/core/wsgi.py", line 14, in get_wsgi_application return WSGIHandler()
File "./django/core/handlers/wsgi.py", line 153, in __init__
self.load_middleware()
File "./django/core/handlers/base.py", line 80, in load_middleware
middleware = import_string(middleware_path)
File "./django/utils/module_loading.py", line 20, in import_string module = import_module(module_path)
File "/usr/lib/python2.7/importlib/__init__.py", line 37, in import_module
__import__(name)
File "./app/BsLogic/MiddleWare/__init__.py", line 3, in <module> import AuthMiddleWare
File "./app/BsLogic/MiddleWare/AuthMiddleWare.py", line 15, in <module>
from ..Common import createLogger, getIp
File "./app/BsLogic/Common.py", line 234, in <module> print traceback.format_stack()
结果与我们的猜想一致。 那么, 我们写的views代码, 究竟去哪了呢? 先按捺住这个疑问, 我们通过web访问我们的站点, 同时留意我们打印的堆栈信息。 我们会发现, 出现了我们想要的加载views代码的堆栈:
File "./django/core/handlers/wsgi.py", line 170, in __call__ # 入口
response = self.get_response(request)
...... #省略django的内部调用
File "./django/urls/resolvers.py", line 313, in url_patterns # 这里开始要加载urls了
patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
...... # 省略加载urls时, django的内部调用
File "./app/BsLogic/scm/urls.py", line 5, in <module> # 这里就是我们写的代码了
import views File "./app/BsLogic/scm/views.py", line 30, in <module>
class BinPackage(ViewBase):
File "./app/BsLogic/Common.py", line 232, in __new__
print traceback.format_stack()
也就是说, 我们写的代码, 并不会在应用启动时就会加载, 而是在接收到第一个request之后, 才开始加载urls, 然后再加载我们的views代码。 如果在views的代码中定义了全局变量, 然后在其他地方使用了这变量, 就很有可能出现NameError: name 'xxx' is not defined的bug, 这是因为, 定义全局变量的语句还没有执行(坑啊~)
结论:
使用execute_from_command_line方式启动django应用时, 会先加载urls, 从而会加载我们写的业务代码(views中的代码); 然后再加载中间件代码. 在应用启动完成时, 所有相关代码都已经被加载入内存。
使用get_wsgi_application方式启动django应用时, 会先加载中间件代码, 这与1中的是完全相反的。 此时, 我们的业务代码仍然没有被加载, 直到第一个请求过来。 如果我们在代码中, 使用了未加载的代码中的全局变量, 就会出现莫名其妙的bug
多进程
uwsgi是一个优秀的web server, 但是出于性能和安全性的考虑, 往往会在uwsgi上面再包一层Nginx。而Nginx是一个异步多进程的服务器, 所以在使用中往往会fork多个nginx的worker进程, 来提高处理request的效率。worker进程数一般是cpu核心数。
通过uwsgi来启动django服务时, 在monitor.log中可以看到worker进程的信息
Python main interpreter initialized at 0xb52bc0your server socket listen backlog is limited to 100 connections
your mercy for graceful operations on workers is 60 seconds
your request buffer size is 4096 bytes
mapped 364080 bytes (355 KB) for 4 cores *** Operational MODE: preforking ***
File "/home/wc/wangchao/mqaDjango/qaweb/wsgi_django.py", line 13, in <module>
application = get_wsgi_application()
File "./django/core/wsgi.py", line 14, in get_wsgi_application return WSGIHandler()
File "./django/core/handlers/wsgi.py", line 153, in __init__
self.load_middleware()
File "./django/core/handlers/base.py", line 80, in load_middleware
middleware = import_string(middleware_path)
File "./django/utils/module_loading.py", line 20, in import_string
module = import_module(module_path)\n
File "/usr/lib/python2.7/importlib/__init__.py", line 37, in import_module
__import__(name)
File "./app/BsLogic/MiddleWare/__init__.py", line 3, in <module>
import AuthMiddleWare\n
File "./app/BsLogic/MiddleWare/AuthMiddleWare.py", line 15, in <module> from ..Common import createLogger, getIp
File "./app/BsLogic/Common.py", line 236, in <module>
print traceback.format_stack()10860 proc load module: app.BsLogic.Common WSGI app 0 (mountpoint='') ready in 0 seconds on interpreter 0xb52bc0 pid: 10860 (default app)
*** uWSGI is running in multiple interpreter mode ***
gracefully (RE)spawned uWSGI master process (pid: 10860)spawned uWSGI worker 1 (pid: 11398, cores: 1)spawned uWSGI worker 2 (pid: 11399, cores: 1)spawned uWSGI worker 3 (pid: 11400, cores: 1)spawned uWSGI worker 4 (pid: 11401, cores: 1)
上面的信息, 我们可以看到master进程和worker进程的pid。 还有一点值得注意的是, 上面的调用堆栈, 就是加载中间件代码的堆栈, 中间件在master进程中加载完成后, 才开始fork子进程, 所以,切勿在中间件中写block的代码, 万一deadlock, 整个服务就挂了。 其实这也是符合Nginx的设计理念的, Nginx的master进程负责处理request信息, 包括处理处理起始行、提取头部、负载等, 然后把请求随机下发到worker进程。同样的, django的中间件也是处理request的, 包括加载session等等。 所以应该把中间件代码放在master进程。
根据前文分析, 我们的业务代码会在第一个request到来之后加载, 但是到底是加载到哪个进程呢(这里可是有1个master和4个worker), 这也是为什么我们在打印堆栈的时候要带上pid的原因。 为了弄清楚问题, 我们多次访问我们的web应用, 看打印出来的日志:
GET /merge11399 proc load module: app.BsLogic.Merge.viewsGET /merge11400 proc load module: app.BsLogic.Merge.viewsGET /11401 proc load module: app.BsLogic.Package.viewsGET /None11398 proc load module: app.BsLogic.Merge.views
分析日志发现, 所有的worker进程都会加载我们的业务代码。 如果某个worker进程, 没有加载过业务代码, 那么当有一个request被下发给它时, 就会去加载。
由于每个worker进程都会加载一次我们的views代码, 那么就会产生一个问题。如果我们在全局的位置, 做了一些特殊的操作, 比如说开了一个线程, 或者定义一把全局锁, 那么, 在Nginx多进程下, 就会发生, 每个进程都开了一个线程, 或者每个进程都有自己的锁。 之前就遇到过一个bug, 全局位置开了线程去轮询某个资源, 然后写入数据库, 部署到Nginx后, 发现每个item都被写了4次......
结论:
除了加载顺序不一样之外, 业务代码加载次数也不一样, 我们的代码会在nginx所有子进程中都加载一次
由于进程间不共享内存, 所以在web应用中, 切勿使用全局变量, 在worker A中的修改不会同步到worker B, 必然会出bug
不要试图在master进程中开启线程, 实测无用(奇怪的是, 在master中开的线程, 会被托管到celery中......)
结语
养成好的编码习惯, web应用中不要使用全局变量, 在需要全局变量的情况下, 多考虑是否能用数据库替代。对于"我自己电脑上是好的"这种bug, 要淡定对待, 线上环境确实一堆坑
网易云免费体验馆,0成本体验20+款云产品!
更多网易研发、产品、运营经验分享请访问网易云社区。
django项目在uwsgi+nginx上部署遇到的坑的更多相关文章
- Django项目在Linux服务器上部署和躺过的坑
引言 在各方的推荐下,领导让我在测试环境部署之前开发的测试数据预报平台.那么问题来了,既然要在服务器上部署, 就需要准备: 1.linux服务器配置 2.linux安装python环境搭建与配置 3. ...
- Linux 集群概念 , wsgi , Nginx负载均衡实验 , 部署CRM(Django+uwsgi+nginx), 部署学城项目(vue+uwsgi+nginx)
Linux 集群概念 , wsgi , Nginx负载均衡实验 , 部署CRM(Django+uwsgi+nginx), 部署学城项目(vue+uwsgi+nginx) 一丶集群和Nginx反向代理 ...
- nginx+uwsgi部署Django项目到Ubuntu服务器全过程,以及那些坑!!!
前言:自己在windows上用PyCharm编写的Django项目,编写完后在windows上运行一点问题都没有,但是部署到服务器上时却Bug百出.百度,CSDN,sf,各种搜索寻求解决方案在历时3天 ...
- Django部署,Django+uWSGI+nginx+Centos部署
说明:系统是在windows上开发的,使用django1.11.4+python3.6.3开发,需要部署在centos6.4服务器上. 第一步:在Centos6.4上安装Python3.6.2 安装请 ...
- 在nginx上部署vue项目(history模式);
在nginx上部署vue项目(history模式): vue-router 默认是hash模式,使用url的hash来模拟一个完整的url,当url改变的时候,页面不会重新加载.但是如果我们不想has ...
- 在nginx上部署vue项目(history模式)--demo实列;
在很早之前,我写了一篇 关于 在nginx上部署vue项目(history模式) 但是讲的都是理论,所以今天做个demo来实战下.有必要让大家更好的理解,我发现搜索这类似的问题还是挺多的,因此在写一篇 ...
- 在Nginx上部署ThinkPHP,解决Pathinfo问题
在Nginx上部署ThinkPHP,解决Pathinfo问题 事实上.要解决nginx不支持pathinfo的问题.有两个解决思路,一是不使用pathinfo模式,二是改动nginx的配置文件,使它支 ...
- nginx上部署python web
nginx上部署python web http://uwsgi-docs.readthedocs.io/en/latest/tutorials/Django_and_nginx.html
- Nginx上部署HTTPS
Nginx上部署HTTPS依赖OpenSSL库和包含文件,即须先安装好libssl-dev,且ln -s /usr/lib/x86_64-linux-gnu/libssl.so /usr/lib/, ...
随机推荐
- JVM(二):垃圾回收
三个问题: 那些内存需要回收? -- 对象是否存活判断 什么时候回收? --垃圾回收触发条件 如何回收? --垃圾回收算法 垃圾回收应用 -- 理解GC日志.使用垃圾回收命令和工具 1. 判断 ...
- Android 类似360悬浮窗口实现源码
当我们在手机上安装360安全卫士时,手机屏幕上时刻都会出现一个小浮动窗口,点击该浮动窗口可跳转到安全卫士的操作界面,而且该浮动窗口不受其他activity的覆盖影响仍然可见(多米音乐也有相关的和主界面 ...
- JAVA方法定义和调用
类的方法代表的是实例的某种行为或功能 定义类的方法 访问修饰 类型 方法名(参数列表){ //方法体 } 1.把方法当作一个模块,是个“黑匣子”,完成某个特定的功能,并返回处理结果 2.方法分类“ 返 ...
- C#添加删除防火墙例外(程序、端口)
一. 添加 COM 引用 在引用里,选择 COM 页, 找到 NetFwTypeLib , 确定即可 二. 添加允许通过防火墙的例外程序 using System; using System.Coll ...
- HDU汉诺塔系列
这几天刷了杭电的汉诺塔一套,来写写题解. HDU1207 汉诺塔II HDU1995 汉诺塔V HDU1996 汉诺塔VI HDU1997 汉诺塔VII HDU2064 汉诺塔III HDU2077 ...
- linux 命令——53 route(转)
Linux系统的route 命令用于显示和操作IP路由表(show / manipulate the IP routing table).要实现两个不同的子网之间的通信,需 要一台连接两个网络的路由器 ...
- IBM中国
https://www.ibm.com/developerworks/cn/linux/l-memory/
- 【51nod1743】雪之国度(最小生成树+倍增)
点此看题面 大致题意: 给你一张无向连通图,其中每条边的边权为这条边连接的两点的权值之差.每次询问两点之间是否存在两条不重复的路径,若存在则输出这两条路径上最大值的最小值. 大致思路 这题显然就是要让 ...
- 剑指offer:按之字形顺序打印二叉树(Python)
题目描述 请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推. 解题思路 先给定一个二叉树的样式: 前段时间 ...
- C#的接口基础教程之二 定义接口
定义接口 从技术上讲,接口是一组包含了函数型方法的数据结构.通过这组数据结构,客户代码可以调用组件对象的功能. 定义接口的一般形式为: [attributes] [modifiers] interfa ...