Z.S.K.'s Records

Flask学习(flask应用)

距离上一篇有一段时间了,上次扯了点跟flask相关的内容,这次抽空记录下flask的机制.flask之所于上手容易,是因为我们对python语言稍微了解的话,简单几步就可以把一个小应用跑起来,不需要搭建额外的东西,所以对于并发量没有要求的话flask可以轻松应对.

flask单应用

我们在网上看到的flask教程基本都会有这样一句代码

1
app = Flask(__name__)

每个 Flask 应用必须创建一个Flask类的实例,并且把模块的名称传递给该实例,flask支持多应用共存,甚至是可以跟django实例共存这个后续再说,这里只是指定了一个flask实例,并不能运行起来.

Flask()更多的参数请见这里

1
app.run(host="0.0.0.0", port=19090, debug=True, passthrough_errors=True, threaded=True)

有了上面这句话之后,flask实例才真正的运行起来,有几个重要的参数这里要说下:

  • host=0.0.0.0:表示其它主机都能访问
  • debug=True:开启调度模式,一般用于测试环境
  • passthrough_errors=True: 禁用错误捕获
  • threaded=True: 则是开启多线程模式,让应用能够同时处理多个请求

更多的参数请见这里

最基本的例子:

1
2
3
4
5
6
7
from flask import Flask,render_template
app = Flask(__name__) #Flask的一个对象,是一个app,flask支持多app共存
@app.route("/",methods=["GET"]) #@是装饰器的语法糖,route则是上面说的路由转发模块
def index():
return render_template("index.html",pass_to_template="index") #把pass_to_template变量传递给index.html模板
if __name__ == "__main__":
app.run(host="0.0.0.0", port=19090, passthrough_errors=True, threaded=True) #使用flask自带的wsgi-server,监听本机的19090端口,threaded=True则开启多线程模式,自带的wsgi-server性能不好,生产环境不会这么用,一般都使用并发量更大的wsgi架构如uwsgi,tornado等

现在,打开浏览器输入http://ip:19090/index.html即可访问index页面

面对多个请求时 ,flask是如何做到请求隔离的呢,这里要提一个flask的两个上下文机制

两个上下文

这里不深入源代码一探究竟,一句话:临时全局变量,flask很机智的实现了thread.local类似线程隔离功能,并配置这种数据结构让其很轻松的可以将对象推入、弹出、快速获取栈顶对象,当然这些操作也是线程隔离的.

也就是说,在一次请求的一个线程中可以将其设置为全局变量,但是仅限于请求的这个线程内部,不同线程通过“线程标识符”来区别,这样就不会影响到其他线程的请求.

请求上下文

实现线程隔离后,为了在一个线程中更加方便使用这些变量,flask中还有一种堆栈的数据结构(通过werkzeug的LocalStack实现),可以处理这些变量,但是并不直接处理这些变量.假如有一个程序得到一个请求,那么flask会将这个请求的所有相关信息进行打包,打包形成的东西就是处理请求的一个环境.flask将这种环境称为请求上下文(request context),之后flask会将这个请求上下文对象放到堆栈中.

这样,请求发生时,我们一般都会指向堆栈中的“请求上下文”对象,这样可以通过请求上下文获取相关对象并直接访问,例如request、session、current_app,g(后两者为应用上下文).还可以通过调用对象的方法或者属性获取其他信息,例如request.method,等请求结束后,请求上下文会被销毁,堆栈重新等待新的请求上下文对象被放入.

应用上下文

应用上下文的概念是在flask 0.9中增加的

当在一个应用的请求上下文环境中,需要嵌套处理另一个应用的相关操作时(这种情况更多的是用于测试或者在console中对多个应用进行相关处理),“请求上下文”显然就不能很好地解决问题了,因为魔法current_app无法确定当前处理的到底是哪个应用.如何让请求找到“正确”的应用呢?我们可能会想到,可以再增加一个请求上下文环境,并将其推入栈中.由于两个上下文环境的运行是独立的,不会相互干扰,所以通过调用栈顶对象的app属性或者调用current_app(current_app一直指向栈顶的对象)也可以获得当前上下文环境正在处理哪个应用.这种办法在一定程度上可行,但是如果说对第二个应用的处理不涉及到相关请求,那也就无从谈起“请求上下文”,更不可能建立请求上下文环境了.为了应对这个问题,Flask中将应用相关的信息单独拿出来,形成一个“应用上下文”对象.这个对象可以和“请求上下文”一起使用,也可以单独拿出来使用.不过有一点需要注意的是:在创建“请求上下文”时一定要创建一个“应用上下文”对象.有了“应用上下文”对象,便可以很容易地确定当前处理哪个应用,这就是魔法current_app.在0.1版本中,current_app是对_request_ctx_stack.top.app的引用,而在0.9版本中current_app是对_app_ctx_stack.top.app的引用.其中_request_ctx_stack和_app_ctx_stack分别是存储请求上下文和应用上下文的栈

请求上下文: request、session

应用上下文: g、current_app

实现高并发

上面说了flask自带的wsgi的性能不好,一般都会选择其它能够实现高并发的http server,如gunicorn,greenlet,uswgi.

其中gunicorn几乎不需要什么配置就可直接整合到flask项目中,但是gunicorn只支持unix,而uswgi的配置比较复杂,但是性能比gunicorn稍好.

上面启动flask是直接在主函数中使用app.run(),这里我们结合gunicorn启动app(假设已pip install gunicorn)

gunicorn基于‘pre-fork worker’模型,意味着有一个中心主控master进程,用它来管理一组worker进程.

worker进程可以支持不同的IO方式(sync,gevent,eventlet,tornado等)

命令非常简洁:

1
gunicorn -D -w 4 -b 127.0.0.1:4000 myproject:app

其中:

  • -D: 以deamon形式后台运行
  • -w: 指定work进程数为4
  • -b: 指定ip 和端口
  • myproject:app 程序入口,前为工程入口文件,后为flask启动的应用

这样flask就以gunicorn的方式启动了,并发数也大大提高.

gunicorn处理静态资源的能力不是很好,所以一般都会在前面再部署个nginx,只需要将proxy_pass 指向gunicorn的监听端口即可,可以参考这里

更多的flask实现高并发部署方式请见这里

参考文章:

转载请注明原作者: 周淑科(https://izsk.me)


 wechat
Scan Me To Read on Phone
I know you won't do this,but what if you did?