监视等待传输的TCP消息队列

时间:2013-03-13 20:26:27

标签: performance wcf throughput wcf-callbacks

我有一个使用NetTcp的WCF客户端和服务器。服务器位于Windows服务中的ServiceHost中。客户端订阅WCF服务并注册其回调接口及其InstanceContext。回调接口有几个单向方法调用。我把它扼杀了。

这一切都很棒。但是,在我的测试中,我的Windows服务中的代码经历了一个紧密的循环,通过单向方法调用之一尽可能快地将消息发送回客户端。我已经超出了TCP连接传递数据的能力,结果是消息排队了。这就是我的预期。

问题是:服务器上是否有任何方法可以了解如何备份队列,以便根据实时吞吐量限制发送邮件的速度?

1 个答案:

答案 0 :(得分:0)

我们从来没有找到答案,但我们创建了自己的解决方法,似乎可以解决这个问题。为了完整起见,我将在此发布。我希望它可以帮助其他人面对类似的情况。

要求:

  1. 我们有一个长期运行的任务,将在硬件服务器上运行。当我说长跑时,我的意思是从一天到很多天。
  2. 我们希望有一个可以在网络中的任何其他桌面上启动的用户界面,以图形方式查看长时间运行的任务中的统计信息。
  3. 用户界面可以多次启动和停止,一次可以发生多个实例。
  4. 用户界面应该对长时间运行的任务产生过多的负担。运行多个UI不应该减慢速度。
  5. 设计:

    1. 长时间运行的任务包含在DLL中。有一个主类具有run()方法,可以启动长时间运行的任务。
    2. 我们已经创建了一个将在硬件服务器上自动运行的Windows服务。
    3. Windows服务将创建主类的实例,并通过调用run()方法启动任务。
    4. Windows服务还将创建ServiceHost实例并启动WCF服务的实例。
    5. Windows服务会将主类的引用传递给WCF服务。
    6. WCF服务将为主类可以引发的六个事件创建处理程序。
    7. 从主类到WCF服务的所有通信都是单向的,并通过提升这六个事件。
    8. UI将是WCF服务的客户端,连接将与NetTcp绑定。
    9. WCF服务有一个subscribe()方法和一个unsubscribe()方法,以便潜在的UI可以加入和离开。
    10. 当UI调用subscribe()方法时,它会将唯一标识符作为字符串传递。 WCF服务将标识符及其OperationContext放入ConcurrentDictionary。
    11. 当UI调用unsubscribe()方法时,该条目将从ConcurrentDictionary中删除。
    12. UI和WCF服务之间的契约具有从WCF服务到客户端的单向消息,用于长时间运行的任务可以引发的每种类型的事件。
    13. 在长时间运行的任务中引发事件时,WCF服务处理该事件并遍历已注册的UI并向UI发送单向消息。
    14. 这一切都在这一点上起作用。

      问题:

      当我们对此系统进行压力测试时,我们创建了一个场景,其中长时间运行的任务以尽可能快的速度轰炸WCF服务。这将是最糟糕的情况,但我们必须能够处理它。 WCF服务能够处理事件并将消息放在Tcp通道上。由于消息是单向的,因此WCF服务不会阻止等待发送完成,这使得它能够跟上正在引发的事件。

      当用户界面没有像服务器推送消息那样快速地从消息中拉出消息时,会出现问题。消息会备份并最终开始超时并导致通道进入故障状态。我们希望在故障状态发生之前检测到这种状况,这样我们就可以开始扔掉信息了。不幸的是,我们找不到检测此渠道积压的机制。如果我们将消息更改为双向,则WCF服务将阻塞,直到消息完成且通道不会被备份,但是,这将影响长时间运行的服务并减慢它的速度。不好。

      解决方案:

      我们通过在包含长时间运行任务的同一DLL中创建一个特殊类来解决这个问题。此类负责与任何连接的用户界面进行通信。此通信对象包含要引发的每个事件的ConcurrentQueue。当长时间运行的任务通常会将事件提升回WCF服务时,它现在会调用此通信对象中的方法。

      在此方法中,通信对象会将事件args输入到该事件的ConcurrentQueue中。通信对象还具有在创建对象时在单独的线程上启动的方法。这个新方法将不断循环concurrentQueues并弹出事件args并实际引发事件。我们将NetTcp调用更改为双向,因此线程中的例程将绑定到TCP通道的速度,但由于它位于单独的线程中,因此不会减慢长时间运行任务的主要处理速度。

      既然我们有一个ConcurrentQueue,我们可以开始,我们可以检查积压。我们在逻辑上为concurrentQueues设置了一些限制(在当前情况下为10)。当长时间运行的任务调用方法将事件args添加到队列时,它首先检查队列的计数,如果它小于我们的逻辑限制,它会将事件args排队,否则它只是丢弃并继续。这样,长时间运行队列的速度不会受到影响,WCF服务也不会备份并导致出现故障的信道状态。

      摘要:

      我们欢迎任何反馈或其他想法。这似乎对我们来说很好,似乎有弹性。

      class UI
      {
          #region Class Scoped Variables
          private Int32 _threashold = 10;
          private bool _continue = true;
          #endregion Class Scoped Variables
      
          #region Public Delegate Definitions
          public delegate void OnPlanSelectionChangedDelegate(PlanSelectionChangedEventArgs e);
          // other lines deleted for brevity
          #endregion Public Delegate Definitions
      
          #region Local Delegate Instances
          private OnPlanSelectionChangedDelegate _onPlanSelectionChangedDelegate = null;
          // other lines deleted for brevity
          #endregion Local Delegate Instances
      
          #region Local Queues for Delegates
          private ConcurrentQueue<PlanSelectionChangedEventArgs> _planSelectionChangedQueue
              = new ConcurrentQueue<PlanSelectionChangedEventArgs>();
          // other lines deleted for brevity
          #endregion Local Queues for Delegates
      
          #region Constructor
          public UI(OnPlanSelectionChangedDelegate onPlanSelectionChanged)
          {
              _onPlanSelectionChangedDelegate = onPlanSelectionChanged;
              // other lines deleted for brevity
              ThreadPool.QueueUserWorkItem(new WaitCallback(DoWork), null);
          }
          #endregion Constructor
      
          #region Public Methods
          public void Shutdown()
          {
              _continue = false;
          }
          public void SendPlanSelection(PlanSelectionChangedEventArgs e)
          {
              if (_planSelectionChangedQueue.Count < _threashold)
              {
                  if (_cntPlanSelectionDropped > 0)
                  {
                      e.Dropped = _cntPlanSelectionDropped;
                  }
                  _planSelectionChangedQueue.Enqueue(e);
                  _cntPlanSelectionDropped = 0;
              }
              else
              {
                  _cntPlanSelectionDropped++;
              }
          }
          // other lines deleted for brevity
          #endregion Public Methods
      
          #region Private Asychronous Method
          private void DoWork(object dummy)
          {
              PlanSelectionChangedEventArgs planSelectionChangedEventArgs = null;
              while (_continue)   // process this loop until told to quit
              {
                  // Plan Selection Changed
                  // Try to get the next event args in a thread safe way
                  if (_planSelectionChangedQueue.TryDequeue(out planSelectionChangedEventArgs))
                  {
                      // We got an event args from the queue, do we have a valid delegate?
                      if (_onPlanSelectionChangedDelegate != null)
                      {
                          // We have a delegate, call it with the event args and rais the event
                          _onPlanSelectionChangedDelegate(planSelectionChangedEventArgs);
                      }
                  }
      
                  // other lines deleted for brevity
              }
          }
          #endregion Private Asychronous Method
      }