Play Framework 2.0パフォーマンス測定

Play Framework 2.0パフォーマンス測定


Typesafe Stack 2.0ソフトウェアプラットフォームについてはすでに説明しました。 その投稿で、プラットフォームのコンポーネントの1つである、JVMでアクターモデルを実装するAkka 2.0フレームワークについて説明しました。 今日は、Typesafe Stackの別のコンポーネントであるPlay 2.0フレームワークの機能について書きたいと思います。 このコンポーネントの機能はすでにここここで説明さていますが、私の意見では、Play 2.0を実行するソリューションのパフォーマンスのトピックは公開されていません。

フレームワークのテストは、それに基づいて開発された最も単純なアプリケーションを使用して実行されます。 テストの結果、次の質問に答える必要があります。 同時接続の最大可能数はいくつですか? これらの接続はどのくらいのRAMを消費しますか? テストアプリケーションで処理できる単位時間あたりの要求数は?

テストアプリケーション

テスト対象のアプリケーションの説明に進む前に、Play 2.0フレームワークの主要なアーキテクチャ機能を明確にする必要があります。 Play HTTPサーバーは、高性能のNettyライブラリに基づいています。 これにより、サーブレットコンテナの構成を除いて「そのまま」使用できるようになるだけでなく、クライアント要求を非同期的に処理する機能も提供されます。 従来の同期処理バリアントでは、何らかの計算が必要な答えに対する着信要求は、この計算が実行されている間、常にオペレーティングシステムのスレッドを占有します。 Playを使用すると、計算中にストリームをサーバープールに戻し、計算の準備ができたら再びストリームを受け取って応答することができます。 技術的には、これは同期バージョンよりも多くのクライアントを同時に接続できることを意味します。

テスト対象のアプリケーションは、3つの主要な機能を実行します。

開発中、Akka 2.0ライブラリが使用されました。 アプリケーションはScala言語で開発されました。私の観点からは、Javaと比較してAkkaを使用する方が便利だからです。 以下に、コードの主要部分を示し、Play 2.0での接続の操作の単純さのみを示し、この投稿の本質を残しません。 すべてのコードはgitリポジトリから取得できます。このリポジトリへのリンクは、出版物の最後にあります。

俳優のコメットアップ

 ... lazy val channel: PushEnumerator[String] = ch ... def receive = { case Message(message) => { channel.push(message) } ... case Close => { channel.push("closed") channel.close() self ! Quit } } ... 

channel変数は、彗星接続(タイプEnumerator )のデータソースであり、以下に示すように、 Cometアダプター(タイプEnumeratee )を介してComet接続に送信されEnumeratee 。 Playでデータストリームのソース、コンバーター、コンシューマーの操作について詳しくはこちらをご覧ください 。 データは、 channel.push(message)関数を呼び出すことにより、彗星ソケットに送信されます。 channel.close()呼び出して彗星ソケットを閉じる。

アプリケーションのメインアクター

ConnectionSupervisorアクターの機能には、コメット接続の作成、作成された接続へのメッセージの送信、すべての接続のクローズが含まれます。
 ... var connectionActors = Seq.empty[ActorRef] def receive = { case SetConnect(connectionId) => { lazy val channel: PushEnumerator[String] = Enumerator.imperative( onComplete = self ! Disconnect(connectionId) ) val connectionActor = context.actorOf(Props(new ConnectionActor(channel)), connectionId) connectionActors = connectionActors :+ connectionActor sender ! channel } case BroadcastMessage(message) => { connectionActors.foreach(_ ! Message(message)) } case CloseAll => { connectionActors.foreach(_ ! Close) } } ... 

作成されたアクターへのリンクは、connectionActorsシーケンス(タイプ-Seq [ActorRef])に保存されます。 接続が確立されると、チャネルチャネルが作成され、新しいConnectionActorに渡されます。 アクターがアクターのリストに追加されます。 メッセージの送信方法と接続のクローズ方法は、コードから明確になります。

現在の値を保存するためのアクター

値がStorageActorに送信され、いくつかのアクションが実行され、値がすべてのコメット接続に送信され、クライアントにも返されると想定されています。 このようにして、クライアントが要求を行い、それに対する応答を期待するときに、実際のアプリケーションの動作がシミュレートされます。
 ... var value = "" def receive = { case Put(v) => { value = v connectSupervisor ! BroadcastMessage(value) sender ! value } } ... 

アプリケーションコントローラー

 ... object Application extends Controller { ... def waitFor(connectionId: String) = Action { implicit val timeout = Timeout(1.second) AsyncResult { (ActorsConfig.connectSupervisor ? (SetConnect(connectionId)) ).mapTo[Enumerator[String]].asPromise.map { chunks => Ok.stream(chunks &> Comet( callback = "console.log")) } } } def broadcastMessage(message: String) = Action { ActorsConfig.connectSupervisor ! BroadcastMessage(message) Ok } def putValueAsync(value: String) = Action { implicit val timeout = Timeout(1.second) Async { (ActorsConfig.storageActor ? Put(value)).mapTo[String].asPromise.map { value => Ok(value) } } } def closeAll = Action { ActorsConfig.connectSupervisor ! CloseAll Ok } } 

着信HTTP要求のアドレスは、このコントローラーのメソッドにマップされます(ルートの説明はconf/routesファイルにあります)。 ここで最も興味深いのは、彗星ソケットを作成し、タイプEnumerator[String]チャネルをそれに関連付けるwaitForメソッドです。 SetConnectメッセージへの応答として、アクターによってチャネルがソケットに送信されます。 チャネルに到着した各メッセージは、 Comet( callback = "console.log")オブジェクトComet( callback = "console.log")指定された関数のパラメーターとしてクライアントのブラウザーに送信されます。 この場合、これはconsole.log関数です。

クライアント側では、非表示のiframe要素を使用してコメット接続が作成されます。次に例を示します。
 <iframe src='/wait?cid=1' style='display:none'/> 

テストプロセス

テストアプリケーションは、1 GBのRAMと1つのプロセッサコア(物理コンピューターのプロセッサはIntel Core i5-2400 3.1 GHz)を備えたUbuntu 11.10(32ビット)を実行する仮想マシンで実行されました。

標準ツール(JMeter、Visual Studio Load Test)でのテストは失敗しました。 700の並列スレッドの起動でもテストシステムを困惑させたため、少なくとも大きな負荷をかけることは不可能でした。 Gatling Stress Tool (アーキテクチャもAkkaに基づいています)などの特別なテストツールを使用することは、コメット接続のテスト機能がないため不可能でした。 同時に、改訂も難しい作業でした。なぜなら、 開発者向けドキュメントは現在作成中です。 これらの理由により、独自のテストツールが開発されました。

テストスクリプト

シナリオは3つのステップで構成されます。

テストのプロセスでは、確立されたコメット接続の数、各コメット接続での平均受信値の数、送信および成功するリクエストの数、これらのリクエストの平均実行時間に関するデータが収集されます。 スクリプトの各ステップの実行中に、プロセッサの負荷と占有RAMの量が記録されました(コメット接続の場合)。

試験結果

  1. コメット接続の最大数と使用されるRAMの量を測定します。

    このテストグループでは、comet-connectionsを設定した後、唯一の値が送信され、その後、接続が閉じられます。
    コメット接続の数接続頻度(1 / ms)占有メモリ(MB)接続セットアップ中の最大CPU使用率値を送信するときの最大CPU使用率
    500503615%15%
    1000405617.5%15%
    30002014525%62%
    30001014240.6%61%
    3000514059.9%70.8%
    3000313898%69%
    6000426280.5%93.4%
    8000439473.7%80.9%
    10,000448577.1%100%

    4 msの期間で彗星接続を確立するプロセスでのプロセッサの負荷は妥当な制限内であるため、追加の接続を追加することはRAMの問題にすぎません。

  2. 値送信リクエストの最大フローと短期のピーク負荷の測定。

    前のテストからわかるように、値をコメット接続に送信することはリソースを集中的に使用する操作であるため、このテストグループでは、サーバーの最大スループットを測定するために、接続数が削減されます。
    コメット接続の数送信された値の数送信頻度(1 / ms)送信時の最大CPU使用率平均クエリ実行時間(ミリ秒)拒否されたリクエストの数
    10001010085.7%1637
    10001050076.5%450
    100010200100%3742
    5001020077%390
    1001010025%190
    1001005068.9%350
    10010030100%2500
    101002031%120
    1010001061.7%180
    101000598.7%470
    11000458.2%270
    11000292.3%330
    110,0002100%4004636
    180004100%4153217
    150005100%399292
    130007100%1290
    12000年798%580

    実行されたテストは、アプリケーションが1秒あたり最大500リクエストの短期ピーク負荷に対処し、1秒あたり100〜150リクエストの負荷で正常に動作することを示しています。


更新:
テスト済みのアプリケーション:git://github.com/tolyasik/testeeApp.git
テストアプリケーション:git://github.com/tolyasik/testerApp.git

Source: https://habr.com/ru/post/J141960/


All Articles