在后台服务器中引入并行性时,需要查询多个负载均衡器,每个负载均衡器有 5 个不同的查询,并将结果发送回 Web 客户端。后台使用 Tornado 框架,根据文档,使用 @gen.Task 或 gen.coroutine 应该可以并行处理多个任务。但实际所有请求都是一个接一个地处理的。
2. 解决方案
问题在于 ELBQuery 函数是一个阻塞函数。如果不将它让给另一个协程,协程调度程序就无法交错调用。
如果 calc_range 调用是问题所在,可以将其分解为更小的部分,每个部分都让位于下一个部分,这样调度程序有机会在每个部分之间进入。
但最有可能的是,boto 调用是阻塞的,大部分时间都花在等待 get_metric_statistics 返回,而其他任何事情都无法运行。
有以下几种方法来解决这个问题:
-
为每个
boto任务启动一个线程。Tornado使得将协程透明地包装在一个线程或线程池任务中变得非常容易,这神奇地取消了所有阻塞。 -
将
boto任务调度到线程池,而不是一个线程。 与解决方案 1 类似的权衡,尤其是如果只有少数任务。 -
重写或修补
boto以使用协程。 这是理想的解决方案,但工作量最大,并且必须维护更新后的boto代码。 -
使用
greenlets并修补足够的库依赖项,以便使其成为异步的。 这听起来很麻烦,但实际上可能是最好的解决方案。 -
使用
greenlets并修补整个stdlib,以便欺骗boto和tornado而无需意识到。 这听起来像是一个可怕的想法。 -
使用单独的进程(甚至是一个进程池),该进程使用类似
gevent的东西。
如果不了解更多信息,建议先考虑解决方案 2 和 4,但无法保证它们会成为最佳答案。
import tornado.gen
import boto.ec2
@tornado.gen.coroutine
def query_elb(fn, region, elb_name, period):
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")