如何在Flask回调中返回HTTP响应,或者说重要吗?

3 投票
1 回答
4808 浏览
提问于 2025-04-18 10:38

我对事件驱动的服务器比较熟悉,但对线程型的服务器不太了解。在像node.js+express或者tornado这样的事件驱动系统中,REST API的一个常见特点是,在处理请求的函数里,进行一些异步的输入输出操作,然后在这个异步操作的回调中返回实际的HTTP响应。比如在Express中,我们会看到这样的代码:

app.post('/products', function (req, res) {
  Product.create(req.body, function (err, product) {
    if (err) return res.json(400, err);
    res.send(201, product);
  });
});

这里的Post.create会去数据库操作,并在产品保存后调用回调函数。无论是返回201(成功)还是400(失败),都是在回调中发送的。这样做的好处是,服务器可以在数据库操作的同时处理其他请求,但从客户端的角度来看,请求似乎需要一些时间才能完成。

假设我想在Flask中做同样的事情(Flask不是事件驱动的服务器)。如果我有一个POST处理函数需要创建多个对象,并且需要进行几次数据库写入,这可能会花费几秒钟的时间,那么我似乎有两个选择:

  1. 我可以立即返回一个202 ACCEPTED,但这样就需要客户端自己去检查所有的写入是否完成。

  2. 我可以直接在处理函数中实现所有的数据库写入。虽然客户端需要等几秒钟才能收到回复,但这样做在客户端看来是同步的,比较简单。

我的问题是,如果我选择第二种方式,Flask是否足够聪明,能够在数据库写入期间阻塞当前请求的线程,以便处理其他请求?我希望服务器在这里不会被阻塞。

顺便说一下,我之前做过长轮询,但这是针对一个公共的REST API,客户端期待的是简单的请求和响应,所以我觉得选择第一种或第二种方式都比较好。对我来说,第一种方式似乎比较少见,但我担心第二种方式会阻塞服务器?我是不是多虑了,Flask(以及线程型服务器)是否足够聪明,让我不必担心这个问题?

1 个回答

6

阻塞与非阻塞

Flask本身(就像express一样)并不是天生阻塞或非阻塞的,它依赖于底层的服务器来提供必要的功能(比如从用户那里读取数据和给用户写响应)。如果服务器不提供事件循环(例如mod_wsgi),那么Flask就会阻塞。如果服务器是非阻塞的(例如gunicorn),那么Flask就不会阻塞。

另一方面,如果你在处理程序中写的代码是阻塞的,那么Flask就会阻塞,即使它运行在非阻塞的服务器上

考虑以下情况:

app.post('/products', function (req, res) {
  var response = Product.createSync(req.body);
  // Event loop is blocked until Product is created
  if (response.isError) return res.json(400, err);
  res.send(201, product);
});

如果你在node服务器上运行这个代码,整个系统会迅速停滞。尽管node本身是非阻塞的,但你的代码不是,它会阻塞事件循环,直到在res.jsonres.send处释放循环,你才能处理来自这个node的任何其他请求。Node的生态系统使得找到非阻塞的IO库变得简单,而在大多数其他常见环境中,你必须主动选择使用非阻塞的IO库来完成你需要的操作。

线程服务器及其工作原理

大多数非事件驱动的服务器使用多个线程来管理并发系统的工作负载。服务器在主线程中接受请求,然后将请求的处理和响应的发送交给其中一个工作线程。工作线程执行处理请求和生成响应所需的(通常是阻塞的)代码。在处理代码运行时,该线程被阻塞,无法处理其他工作。如果请求的速率超过了线程池的总线程数,客户端就会开始排队,等待线程完成。

在线程环境中处理长时间运行请求的最佳方法是什么?

知道阻塞的IO会阻塞一个工作线程后,接下来的问题是“你预计会有多少个并发用户?”(并发指的是“在接受和处理一个请求所需的时间内发生”)如果答案是“少于我工作线程池中的总线程数”,那么你就没问题了——你的服务器可以处理负载,它的非阻塞特性不会对稳定性造成威胁。选择方案#1还是方案#2主要是个人喜好。

另一方面,如果上述问题的答案是“超过我线程池中的工作线程总数”,那么你需要通过将用户的数据交给另一个工作池(通常通过某种队列)来处理请求,并用202响应请求(这是你列表中的选项#1)。这样可以降低响应时间,从而让你处理更多的用户。

总结

  • Flask既不是阻塞的也不是非阻塞的,因为它不进行直接的IO操作
  • 线程服务器在处理请求/响应的线程上阻塞,而不是在接受请求的线程上
  • 根据预期的流量,你几乎肯定会选择方案#1(返回202并将工作推入队列,由不同的线程池/事件驱动的解决方案处理)。

撰写回答