简体   繁体   English

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

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

I've written a service using PollingDuplexHttpBinding which has a Silverllight client that consumes it. 我使用PollingDuplexHttpBinding编写了一个服务,它有一个消耗它的Silverllight客户端。 My Service basically has a given number of clients sending their data (quite often, every second, and the data is quite large, each call is around 5KB) to the service, and also listening for new data sent by other clients to the service to be routed to them, very similar to a chat room architecture. 我的服务基本上有一定数量的客户端向服务发送数据(通常,每秒一次,数据非常大,每次调用大约5KB),并且还监听其他客户端发送给服务的新数据。被路由到他们,非常类似于聊天室架构。

The problem I'm noticing is that when clients connect to the service over the internet, after a few minutes the service's response becomes slow and the replies become lagged. 我注意到的问题是,当客户端通过互联网连接到服务时,几分钟后服务的响应变慢,回复变得滞后。 I've come to the conclusion that when the service hosts upload capacity is reached (internet upload speed, on the server its about 15KB/s only), the messages sent by other clients are buffered and processed accordingly when there is bandwidth available. 我得出的结论是,当服务主机上传容量达到(互联网上传速度,在服务器上仅约15KB / s)时,其他客户端发送的消息会在有可用带宽时进行缓冲和处理。 I'm wondering how exactly can I limit the seize of this buffer that the service uses to store the received messages from the clients? 我想知道如何限制服务用来存储来自客户端的接收消息的缓冲区的占用量? It's not so critical that my clients get all the data, but rather that they get the latest data sent by others, so real time connectivity is what i'm looking for at the cost of guaranteed delivery. 我的客户获得所有数据并不是那么重要,而是他们获得了其他人发送的最新数据,因此实时连接正是我所寻求的,但代价是保证交付。

In short I want to be able to clean my queue/buffer at the service whenever it fills up, or a certain cap is reached and start filling it again with the received calls to get rid of the delay. 简而言之,我希望能够在服务填满时清理我的队列/缓冲区,或者达到一定的上限并开始用接收到的呼叫填充它以消除延迟。 How do I do this? 我该怎么做呢? Is the MaxBufferSize property what I need to decrease on the service side as well as on the client side? MaxBufferSize属性是否需要在服务端以及客户端减少? Or do I need to code this functionality in my service? 或者我是否需要在我的服务中编写此功能? Any ideas? 有任何想法吗?

Thanks. 谢谢。

EDIT: 编辑:

Here is my service architecture: 这是我的服务架构:

//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);
}

The service config: 服务配置:

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

The client will call the service typically between 500ms-1000ms periods like this: 客户端通常会在500毫秒到1000毫秒的时间段内调用该服务,如下所示:

_client.PublishNotificationAsync(byte[] data);

and the callback will notify the client of notifications sent by other clients: 并且回调将通知客户端其他客户端发送的通知:

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

So to recap, when the number of clienst increase, and the service hosts upload speed over the internet is limited, the messages sent out by the service to the subscribers get buffered somewhere and get processed in a queue, which is whats causing the problem, I don't know where these messages are buffered. 因此,回顾一下,当客户数量增加,服务主机上传速度超过互联网时,服务发送给订户的消息会在某处缓冲并在队列中处理,这是导致问题的原因,我不知道这些消息在哪里被缓冲。 In a LAN the service works fine because the server has an upload speed equal to the its download speed (for 100KB/s of incoming calls, it sends out 100KB/s of notifications). 在局域网中,服务工作正常,因为服务器的上传速度等于其下载速度(对于100KB / s的传入呼叫,它发出100KB / s的通知)。 Where are these messages getting buffered? 这些消息在哪里被缓冲? And how can I clear this buffer? 我该如何清除这个缓冲区呢?

I did something experimental to try and see if the messages are buffered at the service, I tried calling this method on the client, but it always returns 0, even when one client is still in the process of receiving notifications that somebody else sent 4-5 minutes ago: 我做了一些实验性尝试,看看消息是否在服务上缓冲,我尝试在客户端调用此方法,但它总是返回0,即使一个客户端仍在接收其他人发送的通知4 5分钟前:

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

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

I did some math for your situation. 我根据你的情况做了一些数学计算。

  • message size = 100KB 邮件大小= 100KB
  • upload channel = 15KB/s 上传频道= 15KB / s
  • Download channel = 100KB/s 下载频道= 100KB / s
  • Client call the service 1-2 times a second 客户端每秒呼叫服务1-2次

client will call the service typically between 500ms-1000ms periods 客户端通常会在500毫秒到1000毫秒的时间段内调用该服务

Is this correct? 它是否正确?

For one client your download traffic only for messages will be 100-200KB/s and this is only message body. 对于一个客户端,仅针对消息的下载流量将为100-200KB / s,这仅是消息正文。 There will be more with header and much more with security enabled. 在启用安全性的情况下,将有更多标题和更多内容。

Messages are going to be combined for asynchronous call. 消息将被组合用于异步调用。 So if we have 3 client and every sent a message callback contains 2 messages for every client. 因此,如果我们有3个客户端并且每个发送一条消息,则回调包含每个客户端的2条消息。 4 client - 3 messages in every callback. 4个客户端 - 每个回调中有3条消息。

For 3 clients it will be 200-400KB/s in download channel. 3个客户端的下载频道为200-400KB / s。

For me it looks like messages are too big for bandwidth you declared. 对我来说,看起来消息对于您声明的带宽来说太大了。

Check if you can: 检查你是否可以:

  1. Reduce message size. 减少邮件大小。 I do not know nature of your business so cannot give advice here. 我不知道你的业务性质,所以不能在这里给出建议。

  2. Use compression for messages or traffic. 对邮件或流量使用压缩。

  3. Increase network bandwidth. 增加网络带宽。 Without that even ideal solution will have very high latency. 没有它,即使理想的解决方案也会有很高的延迟。 You can spend days and weeks optimizing your code and even if you properly utilized your network solution is still going to be slow. 您可以花费数天和数周来优化代码,即使您正确使用网络解决方案仍然会很慢。

I know that sounds like Captain Obvious but some questions just have no right answer unless you change question. 我知道这听起来像是Captain Obvious但是有些问题没有正确答案,除非你改变问题。

After performing steps above work with ServiceThrottlingBehavior in combination with custom code that will manage callback queue. 执行上述步骤后,将ServiceThrottlingBehavior与将管理回调队列的自定义代码结合使用。

ServiceThrottlingBehavior rejects requests if boundary is reached. 如果达到边界, ServiceThrottlingBehavior拒绝请求。

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

This is trivial sample. 这是一个微不足道的样本。 Real numbers should be defined specifically for your environment. 应根据您的环境专门定义实数。

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

Update: 更新:

Big mistake on my part in the question, each call is 5KB/s, 我在这个问题上犯了大错,每个电话都是5KB / s,

Even with 5KB messages 15KB/s is not enough. 即使有5KB的消息,15KB / s还不够。 Let's calculate traffic for only 3 users. 我们只计算3个用户的流量。 3 Incoming message per second. 3每秒传入消息。 System now should use duplex to notify users what other did send. 系统现在应该使用双工通知用户其他人发送了什么。 Every user should get messages from his partners. 每个用户都应该从他的合作伙伴处获得消 We have 3 messages total. 我们共有3条消息。 One (that belongs to sender) may be skipped so every user should get 2 messages. 可以跳过一个(属于发件人),因此每个用户都应该收到2条消息。 3 users will get 10KB (5KB + 5KB = one message 10KB) = 30KB. 3个用户将获得10KB(5KB + 5KB =一个消息10KB)= 30KB。 One message per second will make 30KB/sec in upload channel for 3 users. 每秒一封邮件将为3个用户上传频道提供30KB /秒的速度。

Where are these messages getting buffered? 这些消息在哪里被缓冲? And how can I clear this buffer? 我该如何清除这个缓冲区呢?

It depends on how do you host your service. 这取决于您如何托管您的服务。 If it is self-hosted service it is not buffered at all. 如果是自托管服务,则根本不进行缓冲。 You have your code that tries to send message to receiver but it goes very slow because the channel is flooded. 您的代码尝试向接收方发送消息,但由于通道泛滥,因此速度非常慢。 With IIS there could be some buffering but it is not good practice to touch it from outside. 使用IIS可能会有一些缓冲,但从外部触摸它并不是一个好习惯。

Right solution is throttling. 正确的解决方案是限制。 With limitation in bandwidth you should not try to send all messages to all clients. 由于带宽限制,您不应尝试将所有消息发送到所有客户端。 Instead you should for example limit amount of threads that send messages. 相反,您应该限制发送消息的线程数量。 So from 3 users above instead of sending 3 messages in parallel you may send them sequentially or decide that only one user will get update in that round and next one in next round. 因此,从上面的3个用户而不是并行发送3个消息,您可以按顺序发送它们,或者决定只有一个用户将在该轮次中获得更新,下一轮中的下一个用户将获得更新。

So general idea is not try to send everything to everybody as soon as you have data but send only amount of data you can afford and control that using threads count or response speed. 因此,一般的想法是,只要您拥有数据,就不会尝试将所有内容发送给所有人,但只发送您可以承受的数据量,并使用线程数或响应速度来控制数据。

MaxBufferSize isn't going to help you with this problem. MaxBufferSize不会帮助您解决此问题。 You're going to have to code it yourself I dont know of any existing solution/framework. 您将不得不自己编写代码我不知道任何现有的解决方案/框架。 It does however sound like an interesting problem. 然而,它听起来像一个有趣的问题。 You could start by maintaining a Queue<Message> for each connected client and when pushing to this queue (or when the client calls dequeue) you can re-evaluate what Message should be sent. 您可以从为每个连接的客户端维护Queue<Message>开始,当推送到此队列时(或当客户端调用出队时),您可以重新评估应发送的Message

UPDATE: Firstly, I would forget about trying to do this from the client side and in the configuration, you're going to have to code this yourself 更新:首先,我会忘记尝试从客户端和配置中执行此操作,您将不得不自己编写代码

Here I can see where you are sending to your clients: 在这里,我可以看到您发送给客户的位置:

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

so instead of asynchronously pushing these notifications to each client you should push them into a Queue<byte[]> . 因此,不应将这些通知异步推送到每个客户端,而应将它们推送到Queue<byte[]> Each client connection will have its own queue and you should probably construct a class dedicated to each client connection: 每个客户端连接都有自己的队列,您可能应该构建一个专用于每个客户端连接的类:

note this code won't compile out of the box and likely has some logic errors, use only as a guide 请注意,此代码不会开箱即用,可能会出现一些逻辑错误,仅供参考

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

Imagine a scenario where one message is pushed to the client and while sending 9 more messages call ClientConnection.Enqueue . 想象一下这样一种情况,即一条消息被推送到客户端,而另外9条消息则调用ClientConnection.Enqueue When the first message has finished it checks the queue which should contain only the last (9th message) 第一条消息完成后,它会检查应该只包含最后一条消息的队列(第9条消息)

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

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