我有一个使用NetTcp的WCF客户端和服务器。服务器位于Windows服务中的ServiceHost中。客户端订阅WCF服务并注册其回调接口及其InstanceContext。回调接口有几个单向方法调用。我把它扼杀了。
这一切都很棒。但是,在我的测试中,我的Windows服务中的代码经历了一个紧密的循环,通过单向方法调用之一尽可能快地将消息发送回客户端。我已经超出了TCP连接传递数据的能力,结果是消息排队了。这就是我的预期。
问题是:服务器上是否有任何方法可以了解如何备份队列,以便根据实时吞吐量限制发送邮件的速度?
答案 0 :(得分:0)
我们从来没有找到答案,但我们创建了自己的解决方法,似乎可以解决这个问题。为了完整起见,我将在此发布。我希望它可以帮助其他人面对类似的情况。
要求:
设计:
这一切都在这一点上起作用。
问题:
当我们对此系统进行压力测试时,我们创建了一个场景,其中长时间运行的任务以尽可能快的速度轰炸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
}