Python 如何实现与tornado gen.Task/gen.coroutine装饰器的并行性
这里是一个例子,当一个人必须在后端服务器中引入并行性时 我愿意查询N ELB,每个查询5个不同的查询,并将结果发送回web客户端 后端是Tornado,根据我在过去多次阅读的内容,如果我使用@gen.Task或gen.coroutine,我应该能够并行处理多个任务 然而,我必须在这里遗漏一些东西,因为我所有的请求(20个,4个elbs*5个查询)都是一个接一个地处理的Python 如何实现与tornado gen.Task/gen.coroutine装饰器的并行性,python,parallel-processing,tornado,future,coroutine,Python,Parallel Processing,Tornado,Future,Coroutine,这里是一个例子,当一个人必须在后端服务器中引入并行性时 我愿意查询N ELB,每个查询5个不同的查询,并将结果发送回web客户端 后端是Tornado,根据我在过去多次阅读的内容,如果我使用@gen.Task或gen.coroutine,我应该能够并行处理多个任务 然而,我必须在这里遗漏一些东西,因为我所有的请求(20个,4个elbs*5个查询)都是一个接一个地处理的 def query_elb(fn, region, elb_name, period, callback): callb
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")
问题在于
ELBQuery
是一个阻塞函数。如果它没有在某处产生另一个协同路由,那么协同路由调度器就无法交错调用。(这就是合作计划的全部意义——他们是合作的,而不是先发制人的。)
如果问题类似于calc_range
调用,那么很容易将其分解为更小的部分,在这些部分中,每一个部分都会向下一个部分屈服,这使调度程序有机会进入每一部分之间
但很可能是boto调用阻塞了,函数的大部分时间都花在等待get\u metric\u statistics
返回,而其他任何函数都无法运行
那么,你如何解决这个问题
greenlets
和monkeypatch足够多的库依赖项来欺骗它,使其成为异步的。这听起来有点老套,但实际上可能是最好的解决方案;看看这个greenlets
和monkeypatch整个stdlib-alagevent
诱使boto和tornado一起工作,甚至没有意识到这一点。这听起来是个糟糕的主意;您最好将整个应用程序移植到gevent
gevent
的东西在不知道更多细节的情况下,我建议先看一下#2和#4,但我不能保证它们会是最适合你的答案。如果
ELBSumRequest
和朋友们自己阻止函数而不是产生协同路由,那么Tornado就没有办法交错它们。如果你不能重写它们(或者把它们分成更小的部分,这样每一个都会让位给下一个,这就给了调度程序一个进入中间的机会),协同程序在这里帮不了你;您将不得不使用线程(或者通过monkeypatching阻塞调用使用准抢占式greenlet,但Tornado支持前者而不支持后者,我怀疑您是否想重写gevent
或任何…)中的所有内容。@abarnert,刚刚添加了实现。您可以帮助并建议如何以异步方式重写ELBQuery
?有一个想法我愿意检查一下,那就是,编写一个tornado.web.RequestHandler,一次执行一个boto请求,并使用异步http\U客户端(内部)调用它,怎么样?@TzuryBarYochay:这只是意味着每个boto调用都会阻塞新的RequestHandler,而不是原来的RequestHandler。因此,除非您线程化或异步化该处理程序,否则不会有多大帮助。(你可以使用一个单独的tornado服务器进程来解决这个问题……但在这一点上,tornado并没有真正为你做任何事情;你也可以轻松地编写一个简单的脚本,同步完成所有boto工作。