繁体   English   中英

如何清除服务器上的传出消息缓冲区?

[英]How to clear the outgoing message buffers at the Server?

我使用PollingDuplexHttpBinding编写了一个服务,它有一个消耗它的Silverllight客户端。 我的服务基本上有一定数量的客户端向服务发送数据(通常,每秒一次,数据非常大,每次调用大约5KB),并且还监听其他客户端发送给服务的新数据。被路由到他们,非常类似于聊天室架构。

我注意到的问题是,当客户端通过互联网连接到服务时,几分钟后服务的响应变慢,回复变得滞后。 我得出的结论是,当服务主机上传容量达到(互联网上传速度,在服务器上仅约15KB / s)时,其他客户端发送的消息会在有可用带宽时进行缓冲和处理。 我想知道如何限制服务用来存储来自客户端的接收消息的缓冲区的占用量? 我的客户获得所有数据并不是那么重要,而是他们获得了其他人发送的最新数据,因此实时连接正是我所寻求的,但代价是保证交付。

简而言之,我希望能够在服务填满时清理我的队列/缓冲区,或者达到一定的上限并开始用接收到的呼叫填充它以消除延迟。 我该怎么做呢? 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
        }

因此,回顾一下,当客户数量增加,服务主机上传速度超过互联网时,服务发送给订户的消息会在某处缓冲并在队列中处理,这是导致问题的原因,我不知道这些消息在哪里被缓冲。 在局域网中,服务工作正常,因为服务器的上传速度等于其下载速度(对于100KB / s的传入呼叫,它发出100KB / s的通知)。 这些消息在哪里被缓冲? 我该如何清除这个缓冲区呢?

我做了一些实验性尝试,看看消息是否在服务上缓冲,我尝试在客户端调用此方法,但它总是返回0,即使一个客户端仍在接收其他人发送的通知4 5分钟前:

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

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

我根据你的情况做了一些数学计算。

  • 邮件大小= 100KB
  • 上传频道= 15KB / s
  • 下载频道= 100KB / s
  • 客户端每秒呼叫服务1-2次

客户端通常会在500毫秒到1000毫秒的时间段内调用该服务

它是否正确?

对于一个客户端,仅针对消息的下载流量将为100-200KB / s,这仅是消息正文。 在启用安全性的情况下,将有更多标题和更多内容。

消息将被组合用于异步调用。 因此,如果我们有3个客户端并且每个发送一条消息,则回调包含每个客户端的2条消息。 4个客户端 - 每个回调中有3条消息。

3个客户端的下载频道为200-400KB / s。

对我来说,看起来消息对于您声明的带宽来说太大了。

检查你是否可以:

  1. 减少邮件大小。 我不知道你的业务性质,所以不能在这里给出建议。

  2. 对邮件或流量使用压缩。

  3. 增加网络带宽。 没有它,即使理想的解决方案也会有很高的延迟。 您可以花费数天和数周来优化代码,即使您正确使用网络解决方案仍然会很慢。

我知道这听起来像是Captain Obvious但是有些问题没有正确答案,除非你改变问题。

执行上述步骤后,将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每秒传入消息。 系统现在应该使用双工通知用户其他人发送了什么。 每个用户都应该从他的合作伙伴处获得消 我们共有3条消息。 可以跳过一个(属于发件人),因此每个用户都应该收到2条消息。 3个用户将获得10KB(5KB + 5KB =一个消息10KB)= 30KB。 每秒一封邮件将为3个用户上传频道提供30KB /秒的速度。

这些消息在哪里被缓冲? 我该如何清除这个缓冲区呢?

这取决于您如何托管您的服务。 如果是自托管服务,则根本不进行缓冲。 您的代码尝试向接收方发送消息,但由于通道泛滥,因此速度非常慢。 使用IIS可能会有一些缓冲,但从外部触摸它并不是一个好习惯。

正确的解决方案是限制。 由于带宽限制,您不应尝试将所有消息发送到所有客户端。 相反,您应该限制发送消息的线程数量。 因此,从上面的3个用户而不是并行发送3个消息,您可以按顺序发送它们,或者决定只有一个用户将在该轮次中获得更新,下一轮中的下一个用户将获得更新。

因此,一般的想法是,只要您拥有数据,就不会尝试将所有内容发送给所有人,但只发送您可以承受的数据量,并使用线程数或响应速度来控制数据。

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
        }
    }
}

想象一下这样一种情况,即一条消息被推送到客户端,而另外9条消息则调用ClientConnection.Enqueue 第一条消息完成后,它会检查应该只包含最后一条消息的队列(第9条消息)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM