Flask和/或Tornado - 处理耗时的外部Web服务调用

7
我有一个连接到给定URL的Flask应用程序,可以与外部服务(响应时间通常很长)连接并在那里搜索一些内容。之后对检索得到的数据进行了一些需要占用大量CPU时间的操作。这也需要一些时间。
我的问题是:来自外部的响应可能会花费一些时间。你无法做太多事情,但当你有多个请求时,它就成为了一个大问题——flask请求外部服务会阻塞线程,其他请求就必须等待。
这显然是浪费时间,而且会导致应用程序崩溃。
我听说过这个名为Tornado的异步库,这引发了我的几个问题:
  1. 这是否意味着它可以处理多个请求,并在外部响应后立即触发回调?
  2. 我能否在我的当前Flask应用程序中实现这一点(可能不行,因为我猜WSGI的原因?),或者我需要重新编写整个应用程序以使用Tornado?
  3. 那些需要占用大量CPU时间的操作怎么办?它们是否会阻塞我的线程?无论如何进行一些负载平衡都是个好主意,但我很好奇Tornado如何处理这些操作。
  4. 有什么可能的陷阱和注意事项吗?
2个回答

6
Flask内置的Web服务器并不适合用于生产环境,正如您所列举的原因一样——它是单线程的,并且如果任何请求阻塞了大量时间,很容易被拖垮。Flask文档列出了几种在生产环境中部署它的选项:mod_wsgi、gunicorn、uSWGI等。所有这些部署选项都提供了处理并发的机制,可以通过线程、进程或非阻塞I/O来实现。请注意,如果您正在执行CPU密集型操作,则唯一能提供真正并发的选项是使用多个进程。
如果您想使用tornado,则需要按照tornado风格重写应用程序。由于其基于显式异步I/O的体系结构,如果将其部署为WSGI应用程序,则无法使用其异步功能。 "Tornado风格"基本上意味着对所有I/O操作使用非阻塞API,并使用子进程处理任何长时间运行的CPU密集型操作。 Tornado文档介绍了如何进行异步I/O调用,但以下是一个基本示例:
from tornado import gen

@gen.coroutine
def fetch_coroutine(url):
    http_client = AsyncHTTPClient()
    response = yield http_client.fetch(url)
    return response.body
response = yield http_client.fetch(curl)调用实际上是异步的;当请求开始时,它将控制权返回给tornado事件循环,并在接收到响应后恢复。这允许多个异步HTTP请求在一个线程中并发运行。请注意,在fetch_coroutine内部执行的任何不是异步I/O的操作都会阻塞事件循环,而且在该代码运行时无法处理其他请求。
为了处理长时间运行的CPU绑定操作,您需要将工作发送到子进程以避免阻塞事件循环。对于Python来说,通常意味着使用multiprocessingconcurrent.futures。我建议查看这个问题,以获取有关如何最好地将这些库与tornado集成的更多信息。请注意,您不想维护大于系统上CPU数量的进程池,因此在计算如何将其扩展到单个机器之外时,请考虑预计同时运行多少个CPU绑定操作。
tornado文档还有一个专门介绍在负载均衡器后面运行的部分。他们建议使用NGINX来实现这个目的。

1
Tornado似乎比Flask更适合这个任务。一个Tornado.web.RequestHandler的子类在tornado.ioloop的实例中运行,应该可以给你非阻塞的请求处理。我期望它看起来应该是这样的。
import tornado
import tornado.web
import tornado.ioloop
import json

class handler(tornado.web.RequestHandler):
    def post(self):
        self.write(json.dumps({'aaa':'bbbbb'}))


if __name__ == '__main__':
    app = tornado.web.Application([('/', handler)])
    app.listen(80, address='0.0.0.0')
    loop = tornado.ioloop.IOLoop.instance()
    loop.start()

如果您希望您的帖子处理程序是异步的,您可以使用`tornado.gen.coroutine`或者`AsyncHTTPClient`与`grequests`进行修饰。这将为您提供非阻塞请求。您还可以将计算放在协程中,但我不完全确定。

1
只是在处理程序中添加@tornado.gen.coroutine装饰器并不能使其异步化。您还必须在处理程序内部实际进行非阻塞调用。如果在函数中执行任何阻塞操作,整个事件循环都将被阻塞,无论您是否使用coroutine装饰器。 - dano
你说得对,是我的错误。你能使用grequests来获取异步请求吗? - ragingSloth
grequests 使用 gevent 提供异步 I/O,没错。如果你正在使用 tornado,你可能想要使用它内置的 AsyncHTTPClient,因为它与 tornado 事件循环集成。 - dano

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