如何实现与龙卷风gen.Task / gen.coroutine装饰器的并行性

时间:2013-08-21 19:27:49

标签: python parallel-processing tornado future coroutine

这是一个必须将并行性引入后端服务器的情况。

我愿意查询N ELB,每个查询5个不同的查询,并将结果发送回网络客户端。

后端是Tornado,根据我在docs多次阅读的内容,过去,如果我使用@ gen.Task或gen,我应该可以并行处理多个任务。协程。

但是,我必须在这里遗漏一些东西,因为我的所有请求(20个,4个elbs * 5个查询)都是一个接一个地处理。

def query_elb(fn, region, elb_name, period, callback):
    callback(fn (region, elb_name, period))

class DashboardELBHandler(RequestHandler):

    @tornado.gen.coroutine
    def get_elb_info(self, region, elb_name, period):
        elbReq = yield gen.Task(query_elb, ELBSumRequest, region, elb_name, period)
        elb2XX = yield gen.Task(query_elb, ELBBackend2XX, region, elb_name, period)
        elb3XX = yield gen.Task(query_elb, ELBBackend3XX, region, elb_name, period)
        elb4XX = yield gen.Task(query_elb, ELBBackend4XX, region, elb_name, period)
        elb5XX = yield gen.Task(query_elb, ELBBackend5XX, region, elb_name, period)

        raise tornado.gen.Return( 
            [
                elbReq,
                elb2XX,
                elb3XX,
                elb4XX,
                elb5XX,
            ]
        )

    @tornado.web.authenticated
    @tornado.web.asynchronous
    @tornado.gen.coroutine
    def post(self):
        ret = []

        period = self.get_argument("period", "5m")

        cloud_deployment = db.foo.bar.baz()
        for region, deployment in cloud_deployment.iteritems():

            elb_name = deployment["elb"][0]
            res = yield self.get_elb_info(region, elb_name, period)
            ret.append(res)

        self.push_json(ret)



def ELBQuery(region, elb_name,  range_name, metric, statistic, unit):
    dimensions = { u"LoadBalancerName": [elb_name] }

    (start_stop , period) = calc_range(range_name)

    cw = boto.ec2.cloudwatch.connect_to_region(region)
    data_points = cw.get_metric_statistics( period, start, stop, 
        metric, "AWS/ELB", statistic, dimensions, unit)    

    return data_points

ELBSumRequest   = lambda region, elb_name, range_name : ELBQuery(region, elb_name, range_name,  "RequestCount", "Sum", "Count")
ELBLatency      = lambda region, elb_name, range_name : ELBQuery(region, elb_name, range_name,  "Latency", "Average", "Seconds")
ELBBackend2XX   = lambda region, elb_name, range_name : ELBQuery(region, elb_name, range_name,  "HTTPCode_Backend_2XX", "Sum", "Count")
ELBBackend3XX   = lambda region, elb_name, range_name : ELBQuery(region, elb_name, range_name,  "HTTPCode_Backend_3XX", "Sum", "Count")
ELBBackend4XX   = lambda region, elb_name, range_name : ELBQuery(region, elb_name, range_name,  "HTTPCode_Backend_4XX", "Sum", "Count")
ELBBackend5XX   = lambda region, elb_name, range_name : ELBQuery(region, elb_name, range_name,  "HTTPCode_Backend_5XX", "Sum", "Count")

1 个答案:

答案 0 :(得分:3)

问题是ELBQuery是阻塞函数。如果某个地方没有yield另一个协程,则协同调度程序无法交错调用。 (这是协同程序的全部意义 - 它们是合作的,而不是先发制人的。)

如果问题类似于calc_range调用,则可能很容易处理 - 将其分解为较小的片段,其中每个片段都会产生下一个片段,这使得调度器有机会进入每一件。

但最有可能的是,这是阻止的boto调用,并且你的大部分函数都花在等待get_metric_statistics返回,而没有别的东西可以运行。

那么,你如何解决这个问题?

  1. 为每个boto任务分离一个线程。 Tornado使得在一个线程或线程池任务周围透明地包装一个协程变得非常容易,它可以神奇地解锁所有内容。但当然使用线程也需要付出代价。
  2. 在线程池而不是每个线程上安排boto任务。与#1类似的权衡,特别是如果你只有一些任务。 (但如果您可以为500个不同的用户分别执行5个任务,则可能需要共享池。)
  3. 重写或monkeypatch boto使用协同程序。这将是理想的解决方案......但它是最多的工作(以及破坏您不理解的代码的最大风险,并且必须将其维护为boto更新等)。但是,有些人至少已经开始这样做了,比如asyncboto项目。
  4. 使用greenlets和monkeypatch足够的库依赖项来欺骗它成为异步。这听起来很糟糕,但它实际上可能是最好的解决方案;请参阅Marrying Boto to Tornado
  5. 使用greenlets和monkeypatch整个stdlib ala gevent来欺骗boto和龙卷风一起工作,甚至没有意识到它。这听起来像一个可怕的想法;您最好将整个应用移植到gevent
  6. 使用类似gevent
  7. 之类的单独流程(甚至是其中的池)

    在不知道更多细节的情况下,我建议先看看#2和#4,但我不能保证它们会成为你的最佳答案。