在 Flask 应用中使用 gevent

普通的 flask 应用

通常在用 python 开发 Flask web 应用时,使用 Flask 自带的调试模式能够给开发带来极大便利。Flask 自带的调试模式可以让我们在程序改动时自动重新加载我们的应用程序,而且 jinja2 的模板也会随着改动自动刷新。一般用法是:

  1. # app.py
  2. from flask import Flask
  3. app = Flask( __name__ )
  4. @app.route( '/')
  5. def hello():
  6. return 'Hello World'
  7. if __name__ == '__main__':
  8. app.run( debug = True )

运行上面这个例子,就可以在本地的 5000 端口运行由 flask 提供的服务器程序。如果我们对这个文件进行修改,那么 flask 的底层框架 werkzuge 检测到文件变动后就会自动重新加载我们的应用程序。

然而 Flask 是单线程运行,如果在某个页面中执行了一些耗时的工作,那么程序就会在这里等待,无法响应其他的请求。也就是说,如果一个路由响应函数中有阻塞代码,那么其他用户无法访问这个 web 服务器,而且自己也打不开其他页面了。

在一个路由中添加阻塞代码,如下所示:

  1. # app.py
  2. from time import sleep
  3. @app.route('/testsleep')
  4. def test_sleep():
  5. sleep( 10 )
  6. return 'Hi, You wait for about 10 seconds, right?'

当打开 /testsleep 页面时,会发现浏览器一直在加载过程中,再去打开 / 页面,发现这个页面也是在加载中。只有等到 /testsleep 页面加载完了,才会去加载 / 页面。

在 flask 中使用 gevent

为了解决一个页面耗时导致所有页面都无法访问的问题。考虑使用 gevent 非阻塞的运行服务器程序。在引入 gevent 前,可以在程序最开始执行的位置引入猴子补丁 gevent.monkey,这能修改 python 默认的 IO 行为,让标准库变成 协作式(cooperative)的 API。注意引入 gevent 后,不能再用原来的方式启动我们的 web 应用了:

  1. # app.py
  2. from gevent import monkey
  3. monkey.patch_all() # 打上猴子补丁
  4. from flask import flask
  5. ...
  6. if __name__ == '__main__':
  7. from gevent import pywsgi
  8. app.debug = True
  9. server = pywsgi.WSGIServer( ('127.0.0.1', 5000 ), app )
  10. server.serve_forever()

这个时候再去打开 /testsleep 页面,还是要等待一些时间才会加载完页面,但是这个时候已经访问 / 页面将会立即加载完毕。

启用调试模式和自动刷新模板

如果在某个页面中的代码有问题,会出现运行时错误,那么访问这个页面只能看到 Internal Server Error 的提示,没有了之前的调试窗口和错误信息。而且在上面的代码中,我已经将 app 的 debug 标志设为了真,然而并没有什么用。为了启用调试模式,方便在开发时看到错误信息,我们需要用到 werkzuge 提供的 DebuggedApplication

  1. # app.py
  2. if __name__ == '__main__':
  3. from werkzeug.debug import DebuggedApplication
  4. dapp = DebuggedApplication( app, evalex= True)
  5. server = pywsgi.WSGIServer( ( '127.0.0.1', 5000 ), dapp )
  6. server.serve_forever()

重新打开首页,可以看到熟悉的错误信息。

如果你使用了模板,那么你可能已经注意到了,使用 gevent 后修改模板再次访问可能也不会看到页面上有相应的改动。那么你需要在修改 app 的配置,以便模板能够自动刷新,以下两种方式是等效的:

  1. app.jinja_env.auto_reload = True
  2. app.config['TEMPLATES_AUTO_RELOAD'] = True

尝试自动重新加载

使用了 gevent 后,原有的一些功能都需要通过一定的配置之后才可以正常访问。但是有一个功能我们仍然没有解决,那就是修改代码后 web 应用不会自动重新加载了。stackoverflowgist 提到的一种解决方法是使用 werkzeug 提供的 run_with_reloader,可以写出这样的代码:

  1. # app.py
  2. ...
  3. if __name__ == '__main__':
  4. ...
  5. from werkzeug.serving import run_with_reloader
  6. server = pywsgi.WSGIServer( ( '127.0.0.1', 5000 ), dapp )
  7. run_with_reloader( server ).serve_forever()

然而如果你这样做了就会发现一点用都没有,甚至连 web 应用都不能正常启动了。

按照这个思路来的还有这段代码提供的 示例,但这个示例是将 run_with_reloader 作为装饰器来使用,以下是该示例的代码:

  1. import gevent.wsgi
  2. import werkzeug.serving
  3. @werkzeug.serving.run_with_reloader
  4. def runServer():
  5. app.debug = True
  6. ws = gevent.wsgi.WSGIServer(('', 5000), app)
  7. ws.serve_forever()

然而这也没有什么作用。看一下 flask 的源代码可以发现,run_with_reloader 已经不是装饰器了。而且开发者提醒我们不要使用下面的这个函数,这个 api 很明显已经被废弃了,flask 源代码如下:

  1. def run_with_reloader(*args, **kwargs):
  2. # People keep using undocumented APIs. Do not use this function
  3. # please, we do not guarantee that it continues working.
  4. from werkzeug._reloader import run_with_reloader
  5. return run_with_reloader(*args, **kwargs)

如果使用 gevent 作为 WSGI 的网关服务器,似乎就没法使用自动加载应用的功能了。

实现自动重新加载

没有其他可以借鉴的方法了,好在之前在查看廖雪峰的 Python 教程时,给出了一个自动重新加载应用的示例,主要原理是利用 watchdog 提供的文件监听功能,在创建、修改文件时会触发相应的处理器,这样就可以实现自动重新加载功能。代码可以去廖雪峰的教程中查看。

之后的应用启动时就不能直接使用 python app.py 了。如果将自动加载的代码保存在同级的 monitor.py 文件中,我们需要使用 python monitor.py app.py 启动应用。最终就可以自动热加载我们的 web 应用了。

关于文件改动事件,之前我也写过一个类似的 JS 程序,原理类似,都是当文件改动时自动执行重新构建应用的命令。

相应的说明代码在 github 上可以查看。

References

  1. gevent monkey
  2. hot reload gevent wsgiserver
  3. gist
  4. code snippet
  5. 廖雪峰 Python 教程

在 Flask 应用中使用 gevent的更多相关文章

  1. Flask 框架中 上下文基础理念,包括cookie,session存储方法,requset属性,current_app模块和g模块

    Flask中上下文,分为请求上下文和应用上下文.既状态留存 ,就是把变量存在某一个地方可以调用 请求上下文:实际就是request和session用法理念,既都是可以存储东西. 应用上下文:既变量共享 ...

  2. Flask项目中整合各组件

    一.介绍 主要介绍flask_sqlalchemy.flask_script.flask_migrate这三个组件该如何整合到flask项目中,以及如何使用. # 安装组件 pip3 install ...

  3. Flask项目中使用mysql数据库启动项目是发出警告

    Flask项目中使用mysql数据库启动项目是发出警告: Warning: (1366, "Incorrect string value: '\xD6\xD0\xB9\xFA\xB1\xEA ...

  4. flask SQLAlchemy中一对多的关系实现

    SQLAlchemy是Python中比较优秀的orm框架,在SQLAlchemy中定义了多种数据库表的对应关系, 其中一对多是一种比较常见的关系.利用flask sqlalchemy实现一对多的关系如 ...

  5. uwsgi+flask环境中安装matplotlib

    uwsgi+flask的python有自身的virtual environment,可以通过如下命令进入 . venv/bin/activate 虽然通过sudo apt-get install py ...

  6. flask页面中Head标签内容为空问题

    在使用flask时遇到点问题,以前还没有注意到. 生成页面的时候使用的是模板继承方式,当添加meta标题的时候,本来是添加的base.html模板中的head标签中,但是生成页面后,head中的内容却 ...

  7. werkzeug(flask)中的local,localstack,localproxy探究

    1.关于local python中有threading local处理方式,在多线程环境中将变量按照线程id区分 由于协程在Python web中广泛使用,所以threading local不再满足需 ...

  8. python flask route中装饰器的使用

    问题:route中的装饰器为什么感觉和平时使用的不太一样,装饰器带参数和不太参数有什么区别?被修饰的函数带参数和不带参数有什么区别? 测试1:装饰器不带参数,被修饰的函数也不带参数. def log( ...

  9. nginx的rewrite ,如何在flask项目中获取重写前的url

    1. 在flask配一个重写到哪的路由,假设是/rewite/,然后到nginx的配置文件写重写规则,我这里重写全部的请求,接着测试能否重写成功 1. 添加一个路由 配置重写规则 测试成功 2.接下来 ...

随机推荐

  1. centos7 .net core 使用supervisor守护进程,可以后台运行

    1.安装supervisor yum install supervisor 2.配置supervisor vi /etc/supervisord.conf 拉到最后,这里的意思是 /etc/super ...

  2. SpringMVC+Hibernate 使用 session.update(obj) 未更新的问题

    1.使用spring控制事务 2.使用session.update(obj)执行更新 spring事务配置: <bean id="transactionBese" class ...

  3. 转:Linux下同时启动两个Tomcat进行设置

    转: Linux下同时启动两个Tomcat进行设置 解压tar.gz:tar -zxvf apache-tomcat-6.0.41.tar.gz 至相应的路径下,可解压至两个不同的路径或者相同的路径下 ...

  4. 设置使用的python版本

    一.查看当前使用的python版本,或设置使用的python版本 二.python2中默认使用ASCII码,无法识别中文,报错如图,解决办法,设置字符集为utf-8

  5. 【bzoj2813】 奇妙的Fibonacci数列 线性筛

    Description Fibonacci数列是这样一个数列: F1 = 1, F2 = 1, F3 = 2 . . . Fi = Fi-1 + Fi-2 (当 i >= 3) pty忽然对这个 ...

  6. 洛谷P2764 最小路径覆盖问题(最大流)

    传送门 先说做法:把原图拆成一个二分图,每一个点被拆成$A_i,B_i$,若原图中存在边$(u,v)$,则连边$(A_u,B_v)$,然后$S$对所有$A$连边,所有$B$对$T$连边,然后跑一个最大 ...

  7. ant实例

    <?xml version="1.0" encoding="UTF-8" ?> <project name="javaTest&qu ...

  8. 洛谷 P1486 [NOI2004]郁闷的出纳员

    题目描述 OIER公司是一家大型专业化软件公司,有着数以万计的员工.作为一名出纳员,我的任务之一便是统计每位员工的工资.这本来是一份不错的工作,但是令人郁闷的是,我们的老板反复无常,经常调整员工的工资 ...

  9. Qt 学习之路 2(53):自定义拖放数据

    Qt 学习之路 2(53):自定义拖放数据 豆子  2013年5月26日  Qt 学习之路 2  13条评论上一章中,我们的例子使用系统提供的拖放对象QMimeData进行拖放数据的存储.比如使用QM ...

  10. struts2学习笔记(六)—— 拦截器

    一.拦截器概述 拦截器,在AOP(Aspect-Oriented Programming)中用于在某个方法或字段被访问之前进行拦截,然后在之前或之后加入某些操作.拦截器是AOP的一种实现策略. 在We ...