背景
週末の間、私たちのサービスを大量に実行することが急務でした。 私がYandex Tankをインストールするために最初に行ったのは、やっぱりすべてがDebianの下に投獄されていることでした。 OK Google、Macに稼働中のマシンがあり、そのために仮想マシンをインストールしたくなかったので、依存関係に問題があり、メモリが不足しているテストサーバーに行きました。 週末に管理者をノックする気はありませんでした。自分の手でシンプルで面白いユーティリティを書くために、私の手はますます顔を掻きました。 ストレスが現れました。
私はあなたを戦車やjMeterから思いとどまらせませんが、素早く簡単な(セットアップ)ツールが必要な場合は、それが役に立つことを願っています。
Node.jsを選ぶ理由
まず、言語の非同期性は、1つのコアでクエリを同時に実行するためのコードの記述を簡素化するのに役立ちます。
第二に、労働者のための便利な組み込みクラスターと、労働者のための通信チャネル。
第三に、ブラウザのレポート用の組み込みhttpサーバーとsocket.io。
拡張性
拡張不可能なツールは死んだツールです。 私たちの場合、以下のカスタマイズが必要になる場合があります。
- リモートサーバーからの応答を処理する
- リクエスト戦略
- 結果の集約
- ブラウザでグラフを描く
これらはすべて特定の戦略のモジュールであり、攻撃者と呼ぶことにしました。 多くの攻撃者が存在する可能性があり、自分で作成できます。 各攻撃者は次のモジュールで構成されています。
- Dispatcherはワーカーとレポーターの間で通信します
- レポーターはデータを分析し、レポートを生成します
- 受信者は応答本文を分析し、統計を読み取ります
- フロントエンドはブラウザでグラフィックスを描画します
現時点では、ステップ攻撃者は1人だけ作成されています。 その動作はtank stepに似ていますが、ほとんどのタスクにはこれで十分です。 彼はまた、すべてのクエリ、集計結果をログに書き込み、グラフを作成します。
コード記述
見た目では、単純なアーキテクチャは、並列クエリを使用する必要があるために隠れています。 ご存知のように、Node.jsには1つの作業スレッドしかないため、多数のHTTPリクエストを同時に開始すると、キューが開始され、待ち時間が長くなります。 そのため、すぐにコアの数をワーカーに分岐させ、組み込みのJSONチャネルを介してメッセージと通信します。
Stress.prototype.fork = function (cb) { var self = this; var pings = 0; var worker; if (cluster.isMaster) { for (var i = 0; i < numCPUs; i++) { worker = cluster.fork(); worker.on("message", function (msg) { var data = JSON.parse(msg); if (data.type === "ping") { pings++; if (pings === self.workers.length) cb(); // , } else { self.attack.masterHandler(data); // } }); self.workers.push(worker); // } } else { process.send(JSON.stringify({type: "ping"})); process.on("message", function (msg) { var data = JSON.parse(msg); if (data.taskIndex === undefined) { process.send("{}"); } else { workerInstance.run(data); // } }); } };
Dispatcherは、すべてのコア間でリクエストを均等に分散するように設計されています。
コンストラクターでは、initのすべての種類の準備タスクと並行してこのメソッドを呼び出します。
async.parallel([ this.init.bind(this), this.fork.bind(this) ], function () { if (cluster.isMaster) { self.next(); } });
次のメソッドは、構成で指定されたタスクの反復を開始します。
Stress.prototype.next = function () { var task = this.tasks[this.currentTask]; if (!task) { console.log("\nDone"); process.exit(); } else { var attacker = this.attackers[task.attack.type]; this.attack = new attacker.dispatcher(this.workers, this.currentTask, this.attackers); this.attack.on("done", this.next.bind(this)); this.attack.run(); this.currentTask++; } };
DispatcherとReporterは、現在のタスクに関連するすべてを実行します。 ワーカー自体は非常にシンプルで、 リクエストのラッパーです
task.request.jar = request.jar(new FileCookieStore(config.cookieStore)); async.each(arr, function (_, next) { request(task.request, receiver.handle.bind(receiver, function (result) { result.pid = process.pid; result.reqs = reqs; result.url = task.request.url; result.duration = duration; reporter.logAll(result); next(); })); }, function () { process.send(JSON.stringify(receiver.report)); });
ご覧のとおり、リクエストオブジェクトにあるのは同じ名前のライブラリのオプションだけで、設定ですべての機能を使用できます。 また、完全なテストのためにサービスの閉じた部分の負荷をチェックすることが必要になることが多いため、リクエストにはタフCookieファイルストアが使用され、タスクからリクエストチェーンを構築できます。
とりわけ、Dispatcherは、Reporterが収集したデータをどこにでも簡単に転送できます。たとえば、Google Chartが待っているクライアントに転送できます。
Step.prototype.masterHandler = function (data) { this.answers++; if (Object.keys(data).length) this.summary.push(data); if (this.answers === this.workers.length) { var aggregated = this.attacker.reporter.logAggregate(this.summary); this.attacker.frontend.emit("data", { aggregated: aggregated, step : this.currentStep }); this.answers = 0; this.currentStep = this.currentStep + this.task.attack.step; this.run(); } };
configでwebReport = trueを設定し、コンソールのリンクをたどることを忘れない場合、RPSの増加に伴って遅延がどのように増加するかを見ることができます。
インストールと起動
git clone https://github.com/yarax/stress cd stress npm i npm start
configsフォルダーには、Googleリクエストを含むデフォルトファイルがあります。独自の設定ファイルを作成して、次のように実行することもできます。
npm start myConfigName
誰かがこの記事が有用だと思ったら嬉しいだろうし、プルリクエストも歓迎だ:)