非HTTP負荷テスト。 パート2ガトリング

記事の最初の部分では、 JMeterの Javaロードツールの比較分析を行い、XMLテスト計画から遠ざかり、1台のマシンで30K RPSに達し、 Apache Thriftの例を使用して非HTTPサービスをロードしました。



この記事では、負荷テスト用の別のツール- ガトリングを検討します。先に約束したように、パフォーマンスを数十倍にしようとします。









ガトリング



Gatlingは、Scalaでロードスクリプトを作成するためのオープンソースツールです。 お気に入りのビルドツールを使用して、プロジェクトに簡単に接続できます。 内部では、優れたパフォーマンスで知られるAkka俳優モデルが回転しています。



「非HTTP」プロトコルのロードスクリプトの記述に深く入る前に、HTTPの最も単純な例を分析します。



class BasicSimulation extends Simulation { val httpConf: HttpProtocolBuilder = http .baseURL("http://123.random.com") val scn: ScenarioBuilder = scenario("BasicSimulation") .exec(http("request_1") .get("/")) setUp( scn.inject(atOnceUsers(1)) ).protocols(httpConf) }
      
      





プロトコルビルダーで、アドレスを指定します。 スクリプトビルダーでは、スクリプトの名前、特定の要求の名前、およびHTTPメソッドを記述します。 負荷プロファイル設定ブロックで、負荷シナリオを追加し、プロトコルを定義します。 詳細については、 ドキュメントを参照してください。



負荷の選択



これは小さな余談でした-結局、非HTTPをテストするつもりでした。 JMeterの場合と同様に、簡単に変更可能なプラグインが必要です。このプラグインのフレームでは、クライアントのみを置き換えてさまざまなプロトコルをロードできます。 したがって、標準の拡張機能は私たちには適していません。



Githubのかなり古くても理解しやすいコードに基づいています。 彼は、現在のガトリング(2.3.1)のバージョンとすでに競合していますが、古いバージョンで動作しました。 彼の主な利点は、俳優と仕事をすることでした。



古い実装
 class PerfCustomProtocolSimulation extends Simulation { val mine = new ActionBuilder { def build(next: ActorRef, protocols: Protocols) = { system.actorOf(Props(new MyAction(next))) } } val userLog = csv("user_credentials.csv").circular val scn = scenario("My custom protocol test") .feed(userLog) { exec(mine) } setUp( scn.inject( atOnceUsers(10) ) ) } class MyAction(val next: ActorRef) extends Chainable { def greet(session: Session) { // Call any custom code you wish, say an API call } def execute(session: Session) { var start: Long = 0L var end: Long = 0L var status: Status = OK var errorMessage: Option[String] = None try { start = System.currentTimeMillis; greet(session) end = System.currentTimeMillis; } catch { case e: Exception => errorMessage = Some(e.getMessage) logger.error("FOO FAILED", e) status = KO } finally { val requestStartDate, requestEndDate = start val responseStartDate, responseEndDate = end val requestName = "Test Scenario" val message = errorMessage val extraInfo = Nil DataWriter.dispatch(RequestMessage( session.scenarioName, session.userId, session.groupHierarchy, requestName, requestStartDate, requestEndDate, responseStartDate, responseEndDate, status, message, extraInfo)) next ! session } } }
      
      







新しいバージョンへの単純な移行は、ガトリング開発者によるマイナーバージョンの内部互換性のグローバルリファクタリングによる下位互換性の崩壊によって影を落としました。 特に、このコミットは、特に必要のないアクターを取り除きます。









アッカアクターズモデル



理解できない大規模なウィキペディアの記事と、 Habréの記事からの少し理解しやすい抜粋が、俳優と俳優モデルとは何かについて書かれています。

Akkaアクターは、相互作用する複数のコンポーネントで構成されています。 ActorRef-これはアクターの論理アドレスであり、「送信して忘れた」という原則に基づいて、アクターに非同期でメッセージを送信できます。 ディスパッチャは、アクターのメールボックスにつながるキューにメッセージを配置し、このメールボックスにキューから1つ以上のメッセージを削除するように指示しますが、メッセージは一度に1つだけで、処理のためにアクターに送信します。 Akkaは、アクターへの直接アクセスを許可しないため、アクターと対話する唯一の方法が非同期メッセージであることを保証します。 アクターのメソッドを呼び出すことができません。



さらに、アクターへのメッセージの送信とアクターによるこのメッセージの処理は、異なるスレッドで発生する可能性が高い2つの別個の操作であることに注意してください。 もちろん、Akkaは、状態の変更がすべてのスレッドから見えるようにするために必要な同期を提供します。
難しいです。 構築の例を説明してみましょう。









修理の顧客(あなたと私)は、建設会社にいくつかの部屋の修理を依頼します。 職長は、キューとビルダーの読み込みを管理し、過労やアイドル状態にならないようにします。 ビルダーと直接通信するのではなく、その代表者(会社)と協力します。 ゆっくりと構築している方法を知っているので、タスクを放棄し、その迅速な実行を望んでさえいません(結果を待つのではなく、自分のビジネスに取り掛かります)。



負荷シナリオ



HTTPの例と同様に、カスタムプロトコルを説明するための古いスクリプトを基礎として、独自のビルダーを作成します。 ロードのためにクライアントアクションを実行し、アクターと連携します。



  val mine = new ActionBuilder { def build(ctx: ScenarioContext, next: Action): Action = { new ActorDelegatingAction(name, ctx.system.actorOf( Props(new MyAction(next, lient, ctx)))) } }
      
      





実行結果ハンドラーは、ロードスクリプトの標準実装の一部ではありません。 古い例のコードはもう機能しなかったため、移行後、Gatlingソースを掘り下げてHTTP実装を調査する必要がありました。



  val engine: StatsEngine = ctx.coreComponents.statsEngine engine.logResponse( session, requestName, ResponseTimings(start, end), status, None, message, Nil)
      
      





最初の実行スクリプト



記事の最初の部分で、Thriftのクライアントを既に作成しました。 ロードされるマシンがセットアップされます。 マイクロサービスも同じままで、50K RPSを保持しています。



出荷する時が来ました。 60秒間、1 Kから2 K RPSまで直線的に増加する負荷で撮影してみましょう。









1100 RPSからの生産性の伸びの欠如と、ガトリングユーザー-リクエストハンドラーの雪崩のようなグラフが表示されます。 テストの実行時間が長くなると、奇妙さが増します。 答えよりもはるかに多くの質問。



最も単純で最も正しい解決策は、クライアントを呼び出す代わりにスリープを追加し、その後でリクエストが長いキューに入るようにすることでした。 単一アクターロードファシリティを作成したようです。 より多くの俳優が必要です! ActionBuilderに1行だけ追加して、プールを作成してみましょう。



 ctx.system.actorOf(RoundRobinPool(POOL_SIZE).props( Props(new MyAction(next, lient, ctx)))))
      
      





プール付きのスクリプト



テストを再度実行し、2K RPSに到達したことを確認します。









10Kを試してみましょう:









悪くはないが、18Kの場合:









繰り返しますが、15Kでも同様の問題がありますが、プールを膨らませる場所はありません。 Gatlingリポジトリを調べて、開発者がAkkaアクターモデル自体の構成を再構成する機能を追加したことを発見しました。 これはgatling-akka-defaults.confファイルを使用して行われ、デフォルトでは次のようになります。



 actor { default-dispatcher { throughput = 20 } }
      
      





オプションのガトリングを提供しましょう:



 actor { default-dispatcher { type = Dispatcher executor = "fork-join-executor" fork-join-executor { parallelism-min = 10 parallelism-factor = 2.0 parallelism-max = 30 } throughput = 100 } }
      
      





ディスパッチャーをどのように使用するかは、エグゼキューターの戦略によって決まります。 並列処理のプレフィックスを使用した設定は、スレッドの数(最終的に記憶している)を担当し、マシンの機能とCPUの数に依存します。 スループットは、ストリームが別のアクターにメッセージを送信する前に、あるアクターによって処理されるメッセージの最大値を定義します。 また、これらのパラメーターの係数の選択に正しくアプローチする必要があります。



新しい設定とプールで実行します:









18Kに対応しますが、GCとGatlingユーザーを追加する戦略に関連する定期的な沈下に気付き始めました。



究極の負荷



マシンがJMeterで30K RPSを出力したことを思い出して、Gatlingに同様の負荷をかけようとすると、32Kになります。









結論









JMeterを使用すると、結果をより速く簡単に取得できますが、パフォーマンスがわずかに低下します。 ガトリングを使用すると、大量に出荷することができます(必要ではない場合があります)が、取り扱いがより困難です。 選択はあなた次第です。



All Articles