2

これは、バックエンド サーバーに並列処理を導入する必要がある場合です。

それぞれ 5 つの異なるクエリに対して N 個の ELB をクエリし、その結果を Web クライアントに送り返したいと考えています。

バックエンドは Tornado で、過去にドキュメントで何度も読んだことによると、@gen.Task または gen.coroutine を使用すると、複数のタスクを並行して処理できるはずです。

ただし、すべてのリクエスト (20 個、4 elb * 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")
4

1 に答える 1

3

問題は、それELBQueryがブロッキング機能であることです。yield別のコルーチンがない場合、コルーチン スケジューラが呼び出しをインターリーブする方法はありません。(これがコルーチンの要点です。コルーチンは協調的であり、プリエンプティブではありません。)

問題がcalc_range呼び出しのようなものである場合は、おそらく簡単に対処できます。それを小さな断片に分割し、それぞれが次の断片に譲歩するようにします。これにより、スケジューラは各断片の間に割り込む機会が与えられます。

しかし、ほとんどの場合、ブロックしているのは boto 呼び出しであり、関数の時間のほとんどは戻りを待つことに費やされget_metric_statistics、他には何も実行できません。

それで、これをどのように修正しますか?

  1. boto タスクごとにスレッドをスピンオフします。Tornado を使用すると、スレッドまたはスレッドプール タスクをコルーチンで透過的にラップすることが非常に簡単になり、魔法のようにすべてのブロックが解除されます。もちろん、スレッドの使用にもコストがかかります。
  2. スレッドごとではなく、スレッド プールで boto タスクをスケジュールします。特に少数のタスクしかない場合は、#1 と同様のトレードオフがあります。(ただし、500 人の異なるユーザーに対してそれぞれ 5 つのタスクを実行できる場合は、おそらく共有プールが必要です。)
  3. コルーチンを使用するには、boto を書き換えるか、monkeypatch boto を使用します。これは理想的な解決策です... しかし、それは最も手間がかかります (そして、理解できないコードを壊すリスクが最も高く、boto 更新などとして維持する必要があります)。asyncbotoただし、プロジェクトのように、少なくともこれを開始した人がいます。
  4. greenletsライブラリの依存関係を十分に使用してモンキーパッチを適用し、それをだまして非同期にします。これはハックに聞こえますが、実際には最善の解決策かもしれません。これについては、ボトとトルネードの結婚を参照してください。
  5. greenletsstdlib ala 全体を使用してモンキーパッチを適用し、 geventboto と tornado をだまして、気付かないうちに連携させます。これはひどい考えに思えます。アプリ全体を に移植したほうがよいでしょうgevent
  6. のようなものを使用する別のプロセス (またはそれらのプール) を使用しますgevent

詳細がわからない場合は、最初に #2 と #4 を確認することをお勧めしますが、それらが最適な回答になるとは限りません。

于 2013-08-21T20:04:56.687 に答える