4

PollingDuplexHttpBindingそれを消費する Silverllight クライアントを持つサービスを作成しました。私のサービスには、基本的に、一定数のクライアントがサービスにデータを送信し (非常に頻繁に、毎秒、データは非常に大きく、各呼び出しは約 5KB です)、他のクライアントからサービスに送信された新しいデータをリッスンします。チャット ルーム アーキテクチャに非常によく似ています。

私が気付いている問題は、クライアントがインターネット経由でサービスに接続すると、数分後にサービスの応答が遅くなり、応答が遅れることです。私は、サービス ホストのアップロード容量 (インターネット アップロード速度、サーバー上では約 15KB/秒のみ) に達すると、他のクライアントから送信されたメッセージはバッファリングされ、利用可能な帯域幅がある場合に応じて処理されるという結論に達しました。サービスがクライアントから受信したメッセージを格納するために使用するこのバッファーの占有をどのように正確に制限できるのでしょうか? 私のクライアントがすべてのデータを取得することはそれほど重要ではありませんが、他の人から送信された最新のデータを取得することは重要です。

要するに、サービスがいっぱいになったとき、または特定の上限に達したときはいつでもキュー/バッファをきれいにして、遅延を取り除くために受信した呼び出しで再び埋め始めることができるようにしたい. どうすればいいですか?プロパティはMaxBufferSize、クライアント側だけでなくサービス側でも減らす必要がありますか? または、サービスでこの機能をコーディングする必要がありますか? 何か案は?

ありがとう。

編集:

これが私のサービスアーキテクチャです:

//the service
[ServiceContract(Namespace = "", CallbackContract = typeof(INewsNotification))]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple, InstanceContextMode = InstanceContextMode.Single)]
public class NewsService
{


private static Dictionary<IChatNotification, string> clients = new Dictionary<IChatNotification, string>();
private ReaderWriterLockSlim subscribersLock = new ReaderWriterLockSlim();

[OperationContract(IsOneWay = true)]
public void PublishNotifications(byte[] data)
{
            try
            {
                subscribersLock.EnterReadLock();
                List<INewsNotification> removeList = new List<INewsNotification>();
                lock (clients)
                {
                    foreach (var subscriber in clients)
                    {
                        if (OperationContext.Current.GetCallbackChannel<IChatNotification>() == subscriber.Key)
                        {
                            continue;
                        }
                        try
                        {
                            subscriber.Key.BeginOnNotificationSend(data, GetCurrentUser(), onNotifyCompletedNotificationSend, subscriber.Key);

                        }
                        catch (CommunicationObjectAbortedException)
                        {
                            removeList.Add(subscriber.Key);
                        }
                        catch (CommunicationException)
                        {
                            removeList.Add(subscriber.Key);
                        }
                        catch (ObjectDisposedException)
                        {
                            removeList.Add(subscriber.Key);
                        }

                    }
                }

                foreach (var item in removeList)
                {
                    clients.Remove(item);
                }
            }
            finally
            {
                subscribersLock.ExitReadLock();
            }
        }

}


//the callback contract
[ServiceContract]
public interface INewsNotification
{
       [OperationContract(IsOneWay = true, AsyncPattern = true)]
       IAsyncResult BeginOnNotificationSend(byte[] data, string username, AsyncCallback callback, object asyncState);
       void EndOnNotificationSend(IAsyncResult result);
}

サービス構成:

  <system.serviceModel>
    <extensions>
      <bindingExtensions>
        <add name="pollingDuplex" type="System.ServiceModel.Configuration.PollingDuplexHttpBindingCollectionElement, System.ServiceModel.PollingDuplex, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
      </bindingExtensions>
    </extensions>
    <behaviors>
      <serviceBehaviors>
        <behavior name="">

          <serviceMetadata httpGetEnabled="true" />
          <serviceThrottling maxConcurrentSessions="2147483647" />
          <serviceDebug includeExceptionDetailInFaults="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <bindings>
      <pollingDuplex>

        <binding name="myPollingDuplex" duplexMode="SingleMessagePerPoll" 
                 maxOutputDelay="00:00:00" inactivityTimeout="02:00:00" 
                 serverPollTimeout="00:55:00" sendTimeout="02:00:00"  openTimeout="02:00:00" 
                  maxBufferSize="10000"  maxReceivedMessageSize="10000" maxBufferPoolSize="1000"/>

      </pollingDuplex>
    </bindings>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
    <services>
      <service name="NewsNotificationService.Web.NewsService">
        <endpoint address="" binding="pollingDuplex" bindingConfiguration="myPollingDuplex" contract="NewsNotificationService.Web.NewsService" />
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
      </service>
    </services>
  </system.serviceModel>
    <system.webServer>
        <directoryBrowse enabled="true" />
    </system.webServer>
</configuration>

クライアントは通常、次のように 500 ミリ秒から 1000 ミリ秒の間にサービスを呼び出します。

_client.PublishNotificationAsync(byte[] data);

コールバックは、他のクライアントから送信された通知をクライアントに通知します。

void client_NotifyNewsReceived(object sender, NewsServiceProxy.OnNewsSendReceivedEventArgs e)
        {
                e.Usernamer//WHich client published the data
                e.data//contents of the notification
        }

要約すると、クライアントの数が増加し、インターネットを介したサービス ホストのアップロード速度が制限されている場合、サービスによってサブスクライバーに送信されたメッセージはどこかにバッファリングされ、キューで処理されます。これが問題の原因です。これらのメッセージがどこにバッファリングされているかわかりません。LAN では、サーバーのアップロード速度がダウンロード速度と同じであるため、サービスは正常に動作します (100KB/秒の着信呼び出しに対して、100KB/秒の通知を送信します)。これらのメッセージはどこにバッファリングされますか? そして、どうすればこのバッファをクリアできますか?

メッセージがサービスでバッファリングされているかどうかを確認するために実験的なことを行いました。クライアントでこのメソッドを呼び出してみましたが、他の誰かが送信した通知を 1 つのクライアントがまだ受信中の場合でも、常に 0 を返します 4- 5分前:

[OperationContract(IsOneWay = false)]
public int GetQueuedMessages()
{

            return OperationContext.Current.OutgoingMessageHeaders.Count();
}
4

2 に答える 2

4

私はあなたの状況についていくつかの計算をしました。

  • メッセージサイズ = 100KB
  • アップロード チャネル = 15KB/秒
  • ダウンロード チャネル = 100KB/秒
  • クライアントは 1 秒に 1 ~ 2 回サービスを呼び出します

クライアントは、通常 500 ミリ秒から 1000 ミリ秒の間にサービスを呼び出します

これは正しいです?

1 つのクライアントの場合、メッセージのみのダウンロード トラフィックは 100 ~ 200KB/秒で、これはメッセージ本文のみです。ヘッダーを使用するとさらに多くなり、セキュリティを有効にするとさらに多くなります。

メッセージは、非同期呼び出しのために結合されます。したがって、3 つのクライアントがあり、送信されたすべてのメッセージ コールバックには、クライアントごとに 2 つのメッセージが含まれます。4 クライアント - コールバックごとに 3 つのメッセージ。

3 クライアントの場合、ダウンロード チャネルで 200 ~ 400KB/秒になります。

私には、あなたが宣言した帯域幅に対してメッセージが大きすぎるようです。

次のことができるかどうかを確認します。

  1. メッセージ サイズを小さくします。私はあなたのビジネスの性質を知らないので、ここでアドバイスすることはできません.

  2. メッセージまたはトラフィックに圧縮を使用します。

  3. ネットワーク帯域幅を増やします。それがなければ、理想的なソリューションでも非常に高いレイテンシーが発生します。コードの最適化に何日も何週間も費やすことができます。たとえネットワーク ソリューションを適切に利用したとしても、それでも速度は遅くなります。

キャプテン・オブビウスのように聞こえるかもしれませんが、質問を変更しない限り、いくつかの質問には正解がありません。

上記の手順を実行した後、 ServiceThrottlingBehaviorコールバック キューを管理するカスタム コードと組み合わせて使用​​します。

ServiceThrottlingBehavior境界に達した場合、要求を拒否します。

http://msdn.microsoft.com/en-us/library/ms735114(v=vs.100).aspx

これは簡単なサンプルです。実数は、環境に合わせて定義する必要があります。

<serviceThrottling 
 maxConcurrentCalls="1" 
 maxConcurrentSessions="1" 
 maxConcurrentInstances="1"
/>

アップデート:

質問の私の大きな間違いです。各呼び出しは 5KB/s です。

5KB のメッセージでも 15KB/s では十分ではありません。3 ユーザーのみのトラフィックを計算してみましょう。3 1 秒あたりの着信メッセージ。システムは現在、デュプレックスを使用して、他が送信したものをユーザーに通知する必要があります。すべてのユーザーは、パートナーからメッセージを受け取る必要があります。合計 3 件のメッセージがあります。1 つ (送信者に属する) はスキップされる可能性があるため、すべてのユーザーは 2 つのメッセージを受け取る必要があります。3 人のユーザーは 10KB (5KB + 5KB = 1 つのメッセージ 10KB) = 30KB を取得します。1 秒あたり 1 つのメッセージは、3 人のユーザーのアップロード チャネルで 30KB/秒になります。

これらのメッセージはどこにバッファリングされますか? そして、どうすればこのバッファをクリアできますか?

サービスをどのようにホストするかによって異なります。セルフホステッド サービスの場合は、まったくバッファリングされません。受信者にメッセージを送信しようとするコードがありますが、チャネルがあふれているため非常に遅くなります。IIS ではバッファリングが発生する可能性がありますが、外部からアクセスすることはお勧めできません。

適切な解決策はスロットリングです。帯域幅が制限されているため、すべてのメッセージをすべてのクライアントに送信しようとするべきではありません。代わりに、たとえば、メッセージを送信するスレッドの量を制限する必要があります。したがって、上記の 3 人のユーザーから 3 つのメッセージを並行して送信する代わりに、それらを順番に送信するか、そのラウンドで 1 人のユーザーのみが更新され、次のラウンドで次のユーザーが更新されるように決定することができます。

したがって、一般的な考え方は、データを取得したらすぐにすべての人に送信しようとするのではなく、余裕のある量のデータのみを送信し、スレッド数または応答速度を使用して制御することです。

于 2012-06-09T17:19:44.370 に答える
1

MaxBufferSizeは、この問題を解決するのに役立ちません。自分でコーディングする必要があります。既存のソリューション/フレームワークはわかりません。しかし、それは興味深い問題のように聞こえます。Queue<Message>接続されたクライアントごとにを維持することから始めて、このキューにプッシュするとき (またはクライアントがデキューを呼び出すとき) に、何Messageを送信するかを再評価できます。

更新: まず、クライアント側からこれを実行しようとすることを忘れます。構成では、これを自分でコーディングする必要があります。

ここで、クライアントに送信している場所を確認できます。

 subscriber.Key.BeginOnNotificationSend(data, GetCurrentUser(), onNotifyCompletedNotificationSend, subscriber.Key);

したがって、これらの通知を各クライアントに非同期にプッシュする代わりに、Queue<byte[]>. 各クライアント接続には独自のキューがあり、おそらく各クライアント接続専用のクラスを作成する必要があります。

このコードはそのままではコンパイルされず、ロジック エラーが含まれている可能性があることに注意してください。ガイドとしてのみ使用してください。

public class ClientConnection
{
    private INewsNotification _callback;
    private Queue<byte> _queue = new Queue<byte>();
    private object _lock = new object();
    private bool _isSending
    public ClientConnection(INewsNotification callBack)
    {
           _callback=callback;
    }
    public void Enqueue(byte[] message)
    { 
       lock(_lock)
       {               
           //what happens here depends on what you want to do. 
           //Do you want to only send the latest message? 
           //if yes, you can just clear out the queue and put the new message in there,                         
           //or you could keep the most recent 5 messages.                
           if(_queue.Count > 0)
               _queue.Clear();
          _queue.Enqueue(message);
           if(!_isSending)
               BeginSendMessage();
       }
    }

    private void BeginSendMessage()
    {
       _isSending=true;
        _callback.BeginOnNotificationSend(_queue.Dequeue(), GetCurrentUser(), EndCallbackClient, subscriber.Key);

    }

    private void EndCallbackClient(IAsyncResult ar)
    {
        _callback.EndReceive(ar);
        lock(_lock)
        {
           _isSending=false;
           if(_queue.Count > 0)
              BeginSendMessage();//more messages to send
        }
    }
}

1 つのメッセージがクライアントにプッシュされ、さらに 9 つのメッセージを送信しているときに を呼び出すシナリオを想像してくださいClientConnection.Enqueue。最初のメッセージが終了すると、最後の (9 番目のメッセージ) のみを含むキューをチェックします。

于 2012-06-07T14:19:01.187 に答える