使用Flask和eventlet响应并发请求

时间:2016-01-11 13:02:48

标签: python flask eventlet flask-socketio

我尝试设置一个最小的Flask应用程序,它使用eventlet立即响应并发请求,而不是阻塞并响应一个请求(如标准的Flask调试网络服务器那样)。

先决条件:

pip install Flask
pip install eventlet

根据我对互联网上迄今为止所发现的内容的理解,它应该是这样的:

# activate eventlet
import eventlet
eventlet.monkey_patch()

from flask import Flask

import datetime
from time import sleep

# create a new Flask application
app = Flask(__name__)

# a short running task that returns immediately
@app.route('/shortTask')
def short_running_task():
  start = datetime.datetime.now()
  return 'Started at {0}, returned at {1}'.format(start, datetime.datetime.now())

# a long running tasks that returns after 30s
@app.route('/longTask')
def long_running_task():
  start = datetime.datetime.now()
  sleep(30)
  return 'Started at {0}, returned at {1}'.format(start, datetime.datetime.now())

# run the webserver
if __name__ == '__main__':
    app.run(debug=True)

运行此文件时,然后在webbrowser选项卡中打开http://localhost:5000/longTask,当它仍在处理打开另一个带有http://localhost:5000/shortTask的选项卡时,我希望第二个选项卡能够在第一个选项卡仍处于运行状态时立即返回加载。但是,类似于在标准Werkzeug服务器上运行时,第二个选项卡仅在第一个选项卡在30秒后完成后才返回。

这里有什么问题? 那么,对于Flask来说,这通常被称为“生产就绪的网络服务器”,假设只有很少的并发用户(最多5个)?

顺便说一句,当我使用the Flask-socketio library运行网络服务器时,根据文档,如果安装了eventlet,它会自动选择它,然后按预期工作。

Flask-socketio的完整示例:

# activate eventlet
import eventlet
eventlet.monkey_patch()

from flask import Flask
from flask_socketio import SocketIO

import datetime
from time import sleep

# create a new Flask application
app = Flask(__name__)

# activate Flask-socketio
socketio = SocketIO(app)

# a short running task that returns immediately
@app.route('/shortTask')
def short_running_task():
  start = datetime.datetime.now()
  return 'Started at {0}, returned at {1}'.format(start, datetime.datetime.now())

# a long running tasks that returns after 30s
@app.route('/longTask')
def long_running_task():
  start = datetime.datetime.now()
  sleep(30)
  return 'Started at {0}, returned at {1}'.format(start, datetime.datetime.now())

# run the webserver with socketio
if __name__ == '__main__':
    socketio.run(app, debug=True)

2 个答案:

答案 0 :(得分:13)

当您运行app.run(debug=True)时,您明确告诉Flask在开发Web服务器上运行您的应用程序,该服务器基于Werkzeug。加载eventlet并不重要。

如果要在eventlet Web服务器上运行应用程序,则必须启动一个eventlet Web服务器,该服务器根据the documentation启动,如下所示:

wsgi.server(eventlet.listen(('', 8000)), your_app)

这或多或少是socketio.run()在我的Flask-SocketIO扩展中所做的事情,而且选择性地处理SSL会有一点点复杂性。执行此操作的代码行是:https://github.com/miguelgrinberg/Flask-SocketIO/blob/539cd158f49ce085151911cb63edbacd0fa37173/flask_socketio/init.py#L391-L408。如果你环顾这些行,你会发现有三个不同的启动代码块,一个用于werkzeug,一个用于eventlet,一个用于gevent。它们都不同。

答案 1 :(得分:2)

jan. 11, 2016 2:01:11 DU org.apache.catalina.core.ApplicationContext log
SEVERE: StandardWrapper.Throwable
java.lang.ArrayIndexOutOfBoundsException: 3845
    at org.objectweb.asm.ClassReader.accept(ClassReader.java:470)
    at org.objectweb.asm.ClassReader.accept(ClassReader.java:425)
    at org.glassfish.jersey.server.internal.scanning.AnnotationAcceptingListener.process(AnnotationAcceptingListener.java:169)
    at org.glassfish.jersey.server.ResourceConfig.scanClasses(ResourceConfig.java:879)
    at org.glassfish.jersey.server.ResourceConfig._getClasses(ResourceConfig.java:837)
    at org.glassfish.jersey.server.ResourceConfig.getClasses(ResourceConfig.java:752)
    at org.glassfish.jersey.server.ResourceConfig$RuntimeConfig.<init>(ResourceConfig.java:1159)
    at org.glassfish.jersey.server.ResourceConfig$RuntimeConfig.<init>(ResourceConfig.java:1132)
    at org.glassfish.jersey.server.ResourceConfig.createRuntimeConfig(ResourceConfig.java:1128)
    at org.glassfish.jersey.server.ApplicationHandler.<init>(ApplicationHandler.java:281)
    at org.glassfish.jersey.servlet.WebComponent.<init>(WebComponent.java:298)
    at org.glassfish.jersey.servlet.ServletContainer.init(ServletContainer.java:167)
    at org.glassfish.jersey.servlet.ServletContainer.init(ServletContainer.java:349)
    at javax.servlet.GenericServlet.init(GenericServlet.java:158)
    at org.apache.catalina.core.StandardWrapper.initServlet(StandardWrapper.java:1284)
    at org.apache.catalina.core.StandardWrapper.loadServlet(StandardWrapper.java:1197)
    at org.apache.catalina.core.StandardWrapper.load(StandardWrapper.java:1087)
    at org.apache.catalina.core.StandardContext.loadOnStartup(StandardContext.java:5253)
    at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5543)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1574)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1564)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

jan. 11, 2016 2:01:11 DU org.apache.catalina.core.StandardContext loadOnStartup
SEVERE: Servlet [Jersey Web Application] in web application [/excercise-services] threw load() exception
java.lang.ArrayIndexOutOfBoundsException: 3845
    at org.objectweb.asm.ClassReader.accept(ClassReader.java:470)
    at org.objectweb.asm.ClassReader.accept(ClassReader.java:425)
    at org.glassfish.jersey.server.internal.scanning.AnnotationAcceptingListener.process(AnnotationAcceptingListener.java:169)
    at org.glassfish.jersey.server.ResourceConfig.scanClasses(ResourceConfig.java:879)
    at org.glassfish.jersey.server.ResourceConfig._getClasses(ResourceConfig.java:837)
    at org.glassfish.jersey.server.ResourceConfig.getClasses(ResourceConfig.java:752)
    at org.glassfish.jersey.server.ResourceConfig$RuntimeConfig.<init>(ResourceConfig.java:1159)
    at org.glassfish.jersey.server.ResourceConfig$RuntimeConfig.<init>(ResourceConfig.java:1132)
    at org.glassfish.jersey.server.ResourceConfig.createRuntimeConfig(ResourceConfig.java:1128)
    at org.glassfish.jersey.server.ApplicationHandler.<init>(ApplicationHandler.java:281)
    at org.glassfish.jersey.servlet.WebComponent.<init>(WebComponent.java:298)
    at org.glassfish.jersey.servlet.ServletContainer.init(ServletContainer.java:167)
    at org.glassfish.jersey.servlet.ServletContainer.init(ServletContainer.java:349)
    at javax.servlet.GenericServlet.init(GenericServlet.java:158)
    at org.apache.catalina.core.StandardWrapper.initServlet(StandardWrapper.java:1284)
    at org.apache.catalina.core.StandardWrapper.loadServlet(StandardWrapper.java:1197)
    at org.apache.catalina.core.StandardWrapper.load(StandardWrapper.java:1087)
    at org.apache.catalina.core.StandardContext.loadOnStartup(StandardContext.java:5253)
    at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5543)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1574)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1564)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

不会神奇地将你的代码变成一个可以异步处理请求的多线程野兽(它仍然非常神奇且棒极了)。

正如您在this example中所看到的,您需要使用eventlet wsgi's implementation启动import eventlet eventlet.monkey_patch() 服务器。

如果您需要标准解决方案,请查看如何使用nginx和uwsgi来启动烧瓶应用程序。您也可能对利用创建完整的多线程wsgi处理程序的痛苦的项目Spawning感兴趣。