スケーリングプレイ! 何千もの同時リクエストに
公開: 2022-03-11Scala Web開発者は、何千人ものユーザーが同時にアプリケーションにアクセスした場合の影響を考慮に入れていないことがよくあります。 おそらくそれは、ラピッドプロトタイピングが大好きだからです。 おそらくそれは、そのようなシナリオのテストが単純に難しいためです。
とにかく、適切なツールセットを使用し、適切な開発慣行に従えば、スケーラビリティを無視することは思ったほど悪くはないということを主張します。
Lojinha and the Play! フレームワーク
少し前に、オークションサイトを構築するために、Lojinha(ポルトガル語で「小さな店」を意味する)というプロジェクトを開始しました。 (ちなみに、このプロジェクトはオープンソースです)。 私の動機は次のとおりです。
- もう使わない古いものを売りたかったのです。
- 私は伝統的なオークションサイト、特にここブラジルにあるオークションサイトは好きではありません。
- Playで「遊び」たかった! フレームワーク2(しゃれを意図)。
明らかに、前述のように、私はPlayを使用することにしました! フレームワーク。 構築にかかった正確な数はわかりませんが、http://lojinha.jcranky.comに展開された単純なシステムでサイトを立ち上げて実行するまで、そう長くはかかりませんでした。 実際、私は開発時間の少なくとも半分をTwitter Bootstrapを使用する設計に費やしました(覚えておいてください:私は設計者ではありません…)。
上記の段落では、少なくとも1つのことを明確にする必要があります。Lojinhaを作成するときに、パフォーマンスについてあまり心配していませんでした。
そして、それがまさに私のポイントです。適切なツールを使用することには力があります。適切な軌道に乗ることができるツール、まさにその構造によって最良の開発慣行に従うことを奨励するツールです。
この場合、それらのツールはPlay!です。 フレームワークとScala言語、Akkaが「ゲスト出演」している。
私が何を意味するのかをお見せしましょう。
不変性とキャッシング
可変性を最小限に抑えることは良い習慣であると一般的に認められています。 簡単に言うと、可変性により、特に並列処理や並行性を導入しようとする場合に、コードについて推論することが難しくなります。
遊び! Scalaフレームワークを使用すると、不変性をかなりの時間使用できます。Scala言語自体も同様です。 たとえば、コントローラーによって生成された結果は不変です。 この不変性を「厄介」または「迷惑」と考える場合もありますが、これらの「グッドプラクティス」は理由から「良い」ものです。
この場合、最終的にいくつかのパフォーマンステストを実行することを決定したとき、コントローラーの不変性は絶対に重要でした。ボトルネックを発見し、それを修正するために、この不変の応答を単にキャッシュしました。
キャッシュとは、応答オブジェクトを保存し、同じインスタンスをそのまま新しいクライアントに提供することを意味します。 これにより、サーバーは結果を最初からやり直す必要がなくなります。 この結果が変更可能である場合、複数のクライアントに同じ応答を提供することはできません。
欠点:短期間(キャッシュの有効期限)、クライアントは古い情報を受け取る可能性があります。 これは、遅延を許容せずに、クライアントが最新のデータにアクセスする必要があるシナリオでのみ問題になります。
参考までに、キャッシュせずに製品のリストを含むスタートページをロードするためのScalaコードを次に示します。
def index = Action { implicit request => Ok(html.index(body = html.body(Items.itemsHigherBids(itemDAO.all(false))), menu = mainMenu)) }
次に、キャッシュを追加します。
def index = Cached("index", 5) { Action { implicit request => Ok(html.index(body = html.body(Items.itemsHigherBids(itemDAO.all(false))), menu = mainMenu)) } }
とても簡単ですね。 ここで、 「インデックス」はキャッシュシステムで使用されるキーであり、5は秒単位の有効期限です。
この変更の効果をテストするために、いくつかのJMeterテスト(GitHubリポジトリに含まれています)をローカルで実行しました。 キャッシュを追加する前に、1秒あたり約180リクエストのスループットを達成しました。 キャッシュ後、スループットは1秒あたり最大800リクエストになりました。 これは、2行未満のコードで4倍以上の改善です。

メモリ消費
適切なScalaツールが大きな違いを生む可能性があるもう1つの領域は、メモリ消費です。 ここでも、Play! 正しい(スケーラブルな)方向にあなたをプッシュします。 Javaの世界では、サーブレットAPIで記述された「通常の」Webアプリケーション(つまり、ほとんどすべてのJavaまたはScalaフレームワーク)の場合、APIは簡単に作成できるため、ユーザーセッションに大量のジャンクを入れたくなります。これを可能にするメソッドを呼び出す:
session.setAttribute("attrName", attrValue);
ユーザーセッションに情報を追加するのはとても簡単なので、悪用されることがよくあります。 結果として、おそらく正当な理由がないためにメモリを使いすぎるリスクも同様に高くなります。
プレイで! フレームワーク、これはオプションではありません。フレームワークにはサーバー側のセッションスペースがありません。 遊び! フレームワークのユーザーセッションはブラウザのCookieに保持されており、それを使用する必要があります。 これは、セッションスペースのサイズとタイプが制限されていることを意味します。文字列のみを保存できます。 オブジェクトを保存する必要がある場合は、前に説明したキャッシュメカニズムを使用する必要があります。 たとえば、現在のユーザーの電子メールアドレスまたはユーザー名をセッションに保存したい場合がありますが、ドメインモデルのユーザーオブジェクト全体を保存する必要がある場合は、キャッシュを使用する必要があります。
繰り返しになりますが、これは最初は苦痛に思えるかもしれませんが、実際には、Play! 正しい軌道に乗って、メモリ使用量を慎重に検討する必要があります。これにより、実質的にクラスタ対応のファーストパスコードが生成されます。特に、クラスタ全体に伝播する必要のあるサーバー側のセッションがないため、寿命が長くなります。非常に簡単です。
非同期サポート
次はこのプレイで! フレームワークレビューでは、Play! async(hronous)サポートでも輝いています。 そして、そのネイティブ機能を超えて、Play! 非同期処理のための強力なツールであるAkkaを埋め込むことができます。
Lojinhaは、Playとの単純な統合であるAkkaをまだ十分に活用していません。 本当に簡単に:
- 非同期の電子メールサービスをスケジュールします。
- さまざまな製品のオファーを同時に処理します。
簡単に言うと、AkkaはErlangによって有名になったアクターモデルの実装です。 Akkaアクターモデルに慣れていない場合は、メッセージを介してのみ通信する小さなユニットとして想像してみてください。
電子メールを非同期で送信するには、最初に適切なメッセージとアクターを作成します。 次に、私がする必要があるのは次のようなものだけです。
EMail.actor ! BidToppedMessage(item.name, itemUrl, bidderEmail)
電子メール送信ロジックはアクター内に実装されており、メッセージはアクターに送信する電子メールを通知します。 これはファイアアンドフォーゲットスキームで実行されます。つまり、上記の行はリクエストを送信し、その後は何でも実行し続けます(つまり、ブロックされません)。
Play!のネイティブAsyncの詳細については、公式ドキュメントをご覧ください。
結論
要約すると、私は、スケールアップとスケールアウトが非常にうまくできる小さなアプリケーションLojinhaを急速に開発しました。 問題が発生したり、ボトルネックを発見したりしたとき、修正は迅速かつ簡単でした。使用したツール(Play!、Scala、Akkaなど)のおかげで、効率とスケーラビリティ。 パフォーマンスをほとんど気にすることなく、何千もの同時リクエストに拡張することができました。
次のアプリケーションを開発するときは、ツールを慎重に検討してください。