如何让Flask/Gunicorn处理相同路由的并发请求?

13

简述:使用route修饰的方法在Flask被部署在多个worker和thread的gunicorn后无法处理并发请求,但是两个不同的方法可以正常处理并发请求。为什么会出现这种情况?如何使相同的路由可同时处理请求?


我有一个简单的Flask应用程序:

from flask import Flask, jsonify
import time
app = Flask(__name__)

@app.route('/foo')
def foo():
    time.sleep(5)
    return jsonify({'success': True}), 200

@app.route('/bar')
def bar():
    time.sleep(5)
    return jsonify({'success': False}), 200

如果我通过以下方式运行:

gunicorn test:app -w 1 --threads 1

如果我在浏览器中快速打开两个不同的选项卡分别进入 /bar/foo,那么先按回车的选项卡将在5秒内加载,第二个则需要10秒。这是因为gunicorn只运行一个拥有一个线程的worker。

如果我通过以下任一方式运行它:

gunicorn test:app -w 1 --threads 2
gunicorn test:app -w 2 --threads 1
在这种情况下,同时在两个不同的选项卡中打开/foo/bar都需要5秒钟。这是有道理的,因为gunicorn要么运行带有两个线程的1个工作进程,要么运行具有每个线程的1个工作进程,因此可以同时提供两个路由。
然而,如果我同时打开两个/foo,无论gunicorn配置如何,第二个标签页始终需要10秒钟。
如何使被route修饰的相同方法能够提供并发请求?
1个回答

12
这个问题可能不是由Gunicorn或Flask引起的,而是由浏览器引起的。 我刚试图重现它。使用两个Firefox选项卡可以正常工作;但是,如果我在不同的终端中运行两个curl进程,则它们将以预期方式进行服务(并行),并且它们的请求将由不同的工作程序处理-可以通过在启动gunicorn时启用--log-level DEBUG来检查这一点。
我认为这是因为Firefox(和其他浏览器)为每个URL打开单个连接;当您在两个选项卡上打开一个页面时,它们的请求通过相同的(保持活动状态的)连接发送,导致请求到达相同的工作程序。
因此,即使使用像eventlet这样的异步工作程序也无济于事:异步工作程序可能同时处理多个连接,但是当两个请求降落在同一个连接上时,它们必定会被一个接一个地处理。

1
有趣的是,最初浏览器关于连接的行为是为了通过避免建立新连接的额外开销来加快加载速度。但在这种情况下(服务器端处理缓慢),我们看到它可能会导致事情变慢。 - MarSoft
我刚刚在Chrome中打开一个选项卡,在Firefox中打开另一个选项卡,结果符合预期。谢谢你的提示,干得漂亮。 - Matthew Moisen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接