在 Flask 应用中使用 gevent

普通的 flask 应用

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

# app.py
from flask import Flask
app = Flask( __name__ ) @app.route( '/')
def hello():
return 'Hello World' if __name__ == '__main__':
app.run( debug = True )

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

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

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

# app.py

from time import sleep
@app.route('/testsleep')
def test_sleep():
sleep( 10 )
return 'Hi, You wait for about 10 seconds, right?'

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

在 flask 中使用 gevent

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

# app.py
from gevent import monkey
monkey.patch_all() # 打上猴子补丁 from flask import flask
... if __name__ == '__main__':
from gevent import pywsgi
app.debug = True
server = pywsgi.WSGIServer( ('127.0.0.1', 5000 ), app )
server.serve_forever()

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

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

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

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

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

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

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

尝试自动重新加载

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

# app.py

...

if __name__ == '__main__':
... from werkzeug.serving import run_with_reloader
server = pywsgi.WSGIServer( ( '127.0.0.1', 5000 ), dapp )
run_with_reloader( server ).serve_forever()

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

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

import gevent.wsgi
import werkzeug.serving @werkzeug.serving.run_with_reloader
def runServer():
app.debug = True ws = gevent.wsgi.WSGIServer(('', 5000), app)
ws.serve_forever()

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

def run_with_reloader(*args, **kwargs):
# People keep using undocumented APIs. Do not use this function
# please, we do not guarantee that it continues working.
from werkzeug._reloader import run_with_reloader
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. EF 配置实现建表与迁移

    通过EF 作为操作数据库的工具有一段时间了,也做了几个相对不大的项目,慢慢的也对EF的使用摸索出来了一些规则,虽然说不是技术难点,但是,我说的是但是,能够提高我们开发效率的棉花糖有时我们还是必须要吃的 ...

  2. SourceTree使用

    SourceTree的基本使用   1. SourceTree是什么 拥有可视化界面的项目版本控制软件,适用于git项目管理 window.mac可用 2. 获取项目代码 1. 点击克隆/新建 2. ...

  3. SQLServer备份恢复助手(太强大了!)

    下载地址: http://download.csdn.net/detail/gguozhenqian/8105779

  4. 【01】循序渐进学 docker:到底是啥

    写在前面的话 首先说一下,我本身是做运维的,4 年工作,多家公司.所以可能接下来谈到的更多的是一些在工作过程中积累的个人看法.且有些并不具备普遍性,有不合适的地方,全当我在吹牛逼就行. 一开始我们得谈 ...

  5. Centos7.5的定制化安装

    一.前言 关于定制化centos7.5的镜像真的是历经波折,前前后后.来来回回尝试了不少于20次,上网找了各种关于定制7系统的方法,都没有成功... 但最终功夫不负有心人终于解决了,O(∩_∩)O哈哈 ...

  6. C语言#include的用法

    1.#include 命令介绍 #include 命令是预处理命令的一种,预处理命令可以将别的源代码内容插入到所指定的位置:可以标识出只有在特定条件下才会被编译的某一段程序代码: 可以定义类似标识符功 ...

  7. leecode刷题(7)-- 加一

    leecode刷题(7)-- 加一 加一 描述: 给定一个由整数组成的非空数组所表示的非负整数,在该数的基础上加一. 最高位数字存放在数组的首位, 数组中每个元素只存储一个数字. 你可以假设除了整数 ...

  8. 小白如何将代码上传到github上?

    网上已经有很多关于这个的教程,有一步步操作的,但有些感觉已经颇旧了.现在更新一个最新版的github小白教程.尽管以后此教程也会变成旧的,至少在这一段时期,本文还是最新的.就按照github官网上教程 ...

  9. js去重方法

    function remove(array){ var obj={}; newarray=[]; for(var i in array){ console.log(i); var arg=array[ ...

  10. SDUT OJ 数据结构实验之图论四:迷宫探索

    数据结构实验之图论四:迷宫探索 Time Limit: 1000 ms Memory Limit: 65536 KiB Submit Statistic Discuss Problem Descrip ...