【nginx】Flask 阻塞的问题

在用Flask搭建一个网站,其中需要通过oauth来进行认证登录,我发现在获取access_token这一步事,通过requests请求facebook,如果服务器不用代理的话,请求超时,整个服务器都处于不能相应状态(就算重新打开一个别的链接也是)。

像这种情况,说明哪怕在requests时每次请求只需要1s,人多了整个服务器就会不能响应?

我知道Flask不是异步的,我尝试用Tornado做容器,不能解决这个问题。

那么问题来了,是不是我应该用Nginx来做服务器?

或者我的Flask使用方法不对?

或者我应该在使用requests时异步请求?

我总觉得我的Flask用的不太对:

app.py(部分):

#!/usr/bin/env python

# coding: UTF-8

from __future__ import unicode_literals

import os

from flask import Flask,redirect,request,session,render_template

from modules.Login import Login

import json

app = Flask(__name__)

app.debug = True

app.secret_key = '*******'

@app.route('/login/<login_type>')

def login(login_type):

login=Login(login_type)

return login.login()

@app.route('/on-login/<login_type>')

def on_login(login_type):

login=Login(login_type)

return login.on_login()

if __name__ == '__main__':

app.run()

Login.py(部分):

class Login(object):

"""登录类"""

def __init__(self,login_type=''):

self.login_type=login_type #登录方式

self.login_types=['weibo','qq','google','facebook']

def login(self):

"""根据参数调用对应的登录方法"""

if self.login_type in self.login_types:

func=getattr(Login(),'login_'+self.login_type)

return func()

else:

return '404'

def login_facebook(self):

callback_url=quote(CALLBACK_URL+'/facebook')

url='https://www.facebook.com/dialog/oauth?client_id='+FACEBOOK_APP_ID+'&redirect_uri='+callback_url

return redirect(url)

def on_login_facebook(self):

callback_url=quote(CALLBACK_URL+'/facebook')

code = request.args.get('code', '')

request_url='https://graph.facebook.com/v2.3/oauth/access_token?client_id='+FACEBOOK_APP_ID+'&redirect_uri='+callback_url+'&client_secret='+FACEBOOK_APP_SECRET+'&code='+code

res=getHtml(request_url,True)

access_token=json.loads(res)['access_token']

fields=quote('id,name,picture,age_range,gender,cover')

user_info=getHtml('https://graph.facebook.com/v2.5/me?fields='+fields+'&access_token='+access_token,True)

user=json.loads(user_info)

user_id=user['id']

avatar_url='http://graph.facebook.com/v2.5/'+user_id+'/picture?height=100'

return user_info

common.py:

#!/usr/bin/env python

# coding: UTF-8

from __future__ import unicode_literals

import requests

PROXIES = {

'http': 'http://****:****',

'https': 'http://****:****'

}

def getHtml(url, isProxy=False):

if isProxy:

r = requests.get(url, proxies=PROXIES, verify=False)

else:

r = requests.get(url, verify=False)

html = r.content

return html

def getPost(url, data, isProxy=False):

if isProxy:

r = requests.post(url, data=data, proxies=PROXIES, verify=False)

else:

r = requests.post(url, data=data, verify=False)

html = r.content

return html

回答

Flask 自带的服务器显然不能用在生产环境,我个人建议跑在 gunicorn 或者 uwsgi 上,外层还应该有 nginx,通过一些精心设计的参数(没有万能公式,需要在实践基础上调整)能够在服务器资源占用并发能力上取得较好的平衡。总之就是绝对不能单进程也不使用任何并发技术就放在生产环境,那绝对不耐操啊!

我之前的经验使用 gevent 效果并不是那么理想(那时候还没有 1.0)并且 monkey patch 会导致一些额外的问题。所以不是特别推荐。

回到 OAuth 这个问题上,既然提到问题是出在获取 access_token 这一步,那么就是说采用的是 OAuth 的 Authorization Code Grant 流程,并且进入了最后一步。我们回顾一下这个流程(摘自 OAuth 2 规范):

     +----------+

| Resource |

| Owner |

| |

+----------+

^

|

(B)

+----|-----+ Client Identifier +---------------+

| -+----(A)-- & Redirection URI ---->| |

| User- | | Authorization |

| Agent -+----(B)-- User authenticates --->| Server |

| | | |

| -+----(C)-- Authorization Code ---<| |

+-|----|---+ +---------------+

| | ^ v

(A) (C) | |

| | | |

^ v | |

+---------+ | |

| |>---(D)-- Authorization Code ---------' |

| Client | & Redirection URI |

| | |

| |<---(E)----- Access Token -------------------'

+---------+ (w/ Optional Refresh Token)

Note: The lines illustrating steps (A), (B), and (C) are broken into

two parts as they pass through the user-agent.

理论上,浏览器通过步骤 (C) 将请求发给 Flask 之后,就没浏览器什么事了(后续的事情不在流程里了),Flask 可以简单给一个 200 OK,并设计让浏览器通过 AJAX 稍后再从 Flask 请求数据,不必再苦苦等待 Flask 去执行步骤 (D) 并等待 (E) 了((E) 就是出现问题的步骤)。

Flask 当然可以直接去请求,如果做好了前面的并发处理和负载均衡,且用户数不是非常多的话。当然,如果请求规模足够大,Flask 可以发起一个异步任务,让专门的任务执行部件去执行操作。

之前,我开发的一个项目使用了 celery 通过 RabbitMQ 发送任务,由专门的程序处理这些需要一定时间才能完成的工作,任务结果存储在 Redis 中,每天处理数千万次类似的接口调用。

具体的,当 (C) 的请求到达 Flask 之后,Flask 向 RabbitMQ 发一个任务,就返回 200 OK 了。相应的任务执行部件执行步骤 (D) 并得到响应 (E),结果写入 Redis。浏览器过一段时间再次请求 Flask 要后续的信息,这时候 Flask 去 Redis 查询任务执行成功了没有,并做出对应的响应。

希望对题主有所帮助。

你可以使用geventFlask 异步化。

在程序最开始处加入:

from gevent import monkey

monkey.patch_all()

参考:

Flask自带的serverwerkzeug,默认的werkzeug是单线程,对外请求就block了。可以入 @柳易寒 那样开启多线程即可。

Flask本身不是单进程单线程的,生产环境下,通过uwsgi或者gunicorn实现多线程方式。flask的用法没错,flask部署方式得结合uwsgigunicron甚至是gevent

如果不用关心异步请求的结果,可以使用 Redis实现简单消息队列celery实现异步请求

flask本来就是单线程的,可以通过它的wsgi,在前面加个nginx或者gunicorn之类的服务器实现多线程。tornado是单线程异步的,好像无法与flask兼容吧。

我来看看大家如何解决这个问题。囧。。。

ps:题主既然使用了DEBUG模式,可以把日志贴出来。

我在视图中用urllib2试了一下访问谷歌,还是有反应的,只不过最终会报错:

【nginx】Flask 阻塞的问题

在测试的时候,Flask响应的时间的确很慢。处理这种异步任务,建议使用Celery,目前也在学习使用Celery

自答一下,Flask可以开启多线程,不过生产环境还是要用Nginx才行:

if __name__ == '__main__':

app.run(host='*.*.*.*', port=80,threaded=True)

以上是 【nginx】Flask 阻塞的问题 的全部内容, 来源链接: utcz.com/a/86535.html

回到顶部