【问题标题】:Flask and/or Tornado - handling time consuming call to external webserviceFlask 和/或 Tornado - 处理对外部 Web 服务的耗时调用
【发布时间】:2014-09-30 22:31:42
【问题描述】:

我有一个烧瓶应用程序,它通过给定的 URL 连接到外部服务(响应时间不同,但通常很长)并在那里搜索一些东西。之后,对检索到的数据进行了一些 CPU 繁重的操作。这也需要一些时间。

我的问题:外部响应可能需要一些时间。您对此无能为力,但是当您同时有多个请求时,它会成为一个大问题 - 对外部服务的烧瓶请求会阻塞线程,其余的正在等待。

显然是在浪费时间,而且它正在扼杀应用程序。

我听说过这个名为 Tornado 的异步库。还有我的问题:

  1. 这是否意味着它可以处理多个请求并在外部响应后立即触发回调?
  2. 我可以用我当前的烧瓶应用程序实现这一点(我猜可能不是因为 WSGI?)或者我需要将整个应用程序重写为 Tornado?
  3. 那些 CPU 繁重的操作会怎样 - 这会阻塞我的线程吗?无论如何,做一些负载平衡是个好主意,但我很好奇 Tornado 是如何处理的。
  4. 可能的陷阱,陷阱?

【问题讨论】:

    标签: python multithreading web-services flask tornado


    【解决方案1】:

    flask 中内置的网络服务器并不打算用于生产,这正是您列出的原因 - 它是单线程的,如果任何请求阻塞了很长时间,很容易陷入困境。烧瓶文档lists several options for deploying it in a production environmentmod_wsgigunicornuSWGI 等。所有这些部署选项都提供了通过线程、进程或非阻塞 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) 调用实际上是异步的;当请求开始时,它将控制权返回给龙卷风事件循环,并在收到响应后再次恢复。这允许多个异步 HTTP 请求在一个线程中同时运行。但请注意,您在fetch_coroutine 中所做的任何不是异步 I/O 的操作都会阻塞事件循环,并且在该代码运行时无法处理其他请求。

    要处理长时间运行的 CPU 密集型操作,您需要将工作发送到子进程以避免阻塞事件循环。对于 Python,这通常意味着使用 multiprocessingconcurrent.futures。我会查看this question 以获取有关如何最好地将这些库与tornado 集成的更多信息。请注意,您不希望维护一个大于系统上 CPU 数量的进程池,因此请考虑在任何给定时间您希望运行多少并发 CPU 绑定操作,当您弄清楚如何将其扩展到单台机器之外。

    龙卷风文档has a section dedicated to running behind a load balancer 也是如此。他们建议为此使用 NGINX。

    【讨论】:

      【解决方案2】:

      Tornado 似乎比 Flask 更适合这项任务。在tornado.ioloop 的实例中运行的Tornado.web.RequestHandler 的子类应该为您提供非阻塞请求处理。我希望它看起来像这样。

      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()
      

      如果您希望您的 post 处理程序是异步的,您可以使用 tornado.gen.coroutine 和 'AsyncHTTPClientorgrequests` 来装饰它。这将为您提供非阻塞请求。您也可以将计算放入协程中,尽管我不完全确定。

      【讨论】:

      • 只是将@tornado.gen.coroutine 装饰器添加到处理程序不会使其异步。您实际上还必须在处理程序内部进行非阻塞调用。如果你在函数中做任何阻塞操作,无论你是否使用coroutine装饰器,整个事件循环都会被阻塞。
      • 你是对的,我的错。你可以使用 grequests 来获取异步请求吗?
      • grequests 使用 gevent 提供异步 I/O,是的。但是,如果您使用的是tornado,您可能希望使用其内置的AsyncHTTPClient,因为它与龙卷风事件循环集成。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-11-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多