web-dev-qa-db-ja.com

高性能C#サーバーソケットのヒント/テクニック

おそらくソケット処理コードの設計が不十分なために、スケーリングの問題が発生しているように見える.NET 2.0サーバーがあり、パフォーマンスを向上させるためにサーバーを再設計する方法についてのガイダンスを探しています。

使用シナリオ:50〜150クライアント、各クライアントとの間の小さなメッセージ(各10バイト)の高レート(最大100秒/秒)。クライアント接続は長続きします-通常は数時間です。 (サーバーは取引システムの一部です。クライアントメッセージはグループに集約され、少数の「アウトバウンド」ソケット接続を介して取引所に送信されます。各グループが取引所によって処理されると、確認メッセージがクライアントに返送されます。 。)OSはWindows Server 2003、ハードウェアは2 x4コアX5355です。

現在のクライアントソケットの設計:TcpListenerは、クライアントが接続するときに各クライアントソケットを読み取るスレッドを生成します。スレッドはSocket.Receiveでブロックし、着信メッセージを解析して、コアサーバーロジックで処理するためにキューのセットに挿入します。確認メッセージは、交換側と通信するスレッドからの非同期Socket.BeginSend呼び出しを使用して、クライアントソケットを介して送り返されます。

観察された問題:クライアント数が増えると(現在は60-70)、送信中に最大数百ミリ秒の断続的な遅延が発生し始めました。クライアントとの間でデータを受信します。 (各確認メッセージのタイムスタンプをログに記録します。通常は合計数ミリ秒で送信される同じグループのackの束のタイムスタンプシーケンスに時折長いギャップが見られます。)

システム全体のCPU使用率が低く(<10%)、十分な空きRAMがあり、コアロジックとアウトバウンド(交換側)側が正常に実行されているため、問題はクライアント側のソケットコードに限定されているようです。 。サーバーとクライアント(ギガビットLAN)の間には十分なネットワーク帯域幅があり、ネットワークまたはハードウェア層の問題は除外されています。

有用なリソースへの提案やポインタをいただければ幸いです。何が問題になっているのかを正確に把握するための診断またはデバッグのヒントがあれば、それも素晴らしいでしょう。

注:MSDN Magazineの記事があります Winsock:.NETの高性能ソケットでワイヤーに近づきます そしてKodartの「XF.Server」をちらっと見ましたコンポーネント-せいぜい大ざっぱに見えます。

32
McKenzieG1

これの多くは、システムで実行されている多くのスレッドと、それぞれにタイムスライスを与えるカーネルと関係があります。デザインはシンプルですが、拡張性がよくありません。

おそらく、.netスレッドプールで実行されるSocket.BeginReceiveを使用して(使用するスレッドの数をなんらかの方法で指定できます)、非同期コールバックからキューにプッシュすることを検討する必要があります(どのスレッドプールでも実行できます)。 .NETスレッド)。これにより、はるかに高いパフォーマンスが得られるはずです。

18
grepsedawk

.NET 3.5環境では、ソケットI/Oのパフォーマンスが向上しています。パフォーマンスを向上させるために、BeginReceive/BeginSendの代わりにReceiveAsync/SendAsyncを使用できます。これをチェックしてください:

http://msdn.Microsoft.com/en-us/library/bb968780.aspx

22
hakkyu

特にここでの全体的なCPU使用率が低いことを考えると、クライアントあたりのスレッドは非常にやり過ぎのようです。通常、BeginReceiveを使用してすべてのクライアントにサービスを提供するスレッドの小さなプールが必要です。次に、処理をワーカーの1つにディスパッチします(おそらく、すべてのワーカーが待機している同期キューに作業を追加するだけです)。 )。

8
Marc Gravell

私は決してC#の人ではありませんが、高性能ソケットサーバーの場合、最もスケーラブルなソリューションは、CPUに適したアクティブなスレッドの数で I/O完了ポート を使用することです。接続ごとに1スレッドのモデルを使用するのではなく、で実行されているプロセス。

あなたの場合、8コアのマシンでは、合計16のスレッドが必要で、8つが同時に実行されます。 (他の8つは基本的に予備として保持されます。)

6
John Dibling

他の人が示唆しているように、これを実装する最良の方法は、クライアント向けのコードをすべて非同期にすることです。 TcpServer()でBeginAccept()を使用すると、手動でスレッドを生成する必要がなくなります。次に、受け入れられたTcpClientから取得する基盤となるネットワークストリームでBeginRead()/ BeginWrite()を使用します。

ただし、ここで理解できないことが1つあります。あなたは、これらは長命の接続であり、多数のクライアントであると言いました。システムが定常状態に達し、最大クライアント(たとえば70)が接続されていると仮定します。クライアントパケットをリッスンする70のスレッドがあります。その後、システムは引き続き応答するはずです。アプリケーションにメモリ/ハンドルリークがあり、サーバーがページングしているようにリソースが不足している場合を除きます。 Accept()の呼び出しの周りにタイマーを配置して、クライアントスレッドを開始し、それにかかる時間を確認します。また、taskmanagerとPerfMonを起動し、アプリの「非ページプール」、「仮想メモリ」、「ハンドルカウント」を監視して、アプリがリソース不足になっているかどうかを確認します。

Asyncを使用することが正しい方法であることは事実ですが、根本的な問題が本当に解決されるかどうかはわかりません。私が提案したようにアプリを監視し、メモリとハンドルのリークという本質的な問題がないことを確認します。この点で、上記の「BigBlackMan」は正しかった-続行するには、より多くのインストルメンテーションが必要です。彼が反対票を投じられた理由がわからない。

4
feroze

Socket.BeginConnect および Socket.BeginAccept 間違いなく便利です。実装では ConnectEx および AcceptEx 呼び出しを使用していると思います。これらの呼び出しは、最初の接続ネゴシエーションとデータ転送を1つのユーザー/カーネル遷移にラップします。最初の送信/受信バッファはすでに準備ができているので、カーネルはそれをリモートホストまたはユーザースペースに送信することができます。

また、リスナー/コネクタのキューも用意されており、接続の受け入れ/受信とハンドオフ(およびすべてのユーザー/カーネルの切り替え)に伴う遅延を回避することで、おそらく少しブーストされます。

バッファでBeginConnectを使用するには、接続する前に初期データをソケットに書き込む必要があるようです。

3
Luke Quinane

ランダムな断続的な約250ミリ秒の遅延は、TCPで使用されるNagleアルゴリズムが原因である可能性があります。それを無効にして、何が起こるかを確認してください。

3
Addys

私が排除したいことの1つは、ガベージコレクターの実行ほど単純ではないということです。すべてのメッセージがヒープ上にある場合、1秒間に10000個のオブジェクトを生成しています。

100秒ごとに ガベージコレクションを読み取ります

唯一の解決策は、メッセージをヒープから遠ざけることです。

1
Tom Thorne

7〜8年前に同じ問題が発生し、100ミリ秒から1秒の一時停止が発生しました。問題は、ガベージコレクションでした。4ギガから約400メガを使用していましたが、オブジェクトがたくさんありました。

メッセージをC++に保存することになりましたが、ASP.NETキャッシュ(以前はCOMを使用していて、ヒープから移動したもの)を使用できました。

0
user1496062