我们有一个正在生产中运行的服务器,该服务器接收数据并放入queueA
中。另一个线程一次从queueA
获取一个项目,对其进行处理并将其放入queueB
。
此设置实际上是镜像的,因此在主动对主动的冗余设置中,两个镜像服务器都接收完全相同的数据并以相同的方式处理它们。
大约每年一次,其中一台服务器的邮件会大量堆积在queueA
中,而另一台服务器可以很好地处理相同的数据。
因此,问题似乎出在queueA
中,并进行处理并排队到queueB
中。除了(完全没有必要)使用Task库(如以下精简版所示)之外,那里没有什么其他事情了。
public IAsyncResult BeginReceive()
{
Task<object> task = new Task<object>(_ =>
{
object message;
if (!queueA.TryDequeue(out message))
{
if (queueA.IsEmpty)
waitQueueA.WaitOne(10); // waitQueueA is properly signaled whenever an item is put into queueA
}
return message;
}, null, TaskCreationOptions.PreferFairness);
task.ContinueWith((t) =>
{
object receivedMessage = t.Result;
if (receivedMessage != null)
{
lock (bLock) // bLock is only used by this piece of code
{
queueB.Enqueue(receivedMessage);
}
}
else
{
Thread.Sleep(1);
}
BeginReceive(OnReceive, channel);
});
task.Start();
return task;
}
public object EndReceive(IAsyncResult result)
{
Task<object> task = (Task<object>) result;
return task.Result;
}
忽略代码的许多特质(个人而言,我会为此创建一个专用线程,并在一个大的while (true) { }
循环中执行以上所有操作,而无需涉及任何Task
),在什么情况下可能会使它的性能如此差,以至于循环以每秒15次迭代的速度旋转,而在少于50次迭代/秒的情况下达到最大值?我们每5秒记录一次ThreadPool.GetAvailableThreads()
,它表示整个过程中有数千个可用线程。
该代码在大多数时间都能正常运行(但足够),但是当它失败时,它似乎从程序的开始一直到它的结束都失败了,这在一个小时左右的范围内(当内存耗尽时) queueA
及其项目。因此,该程序似乎可以进入某种时髦的状态,无法恢复。
下面是每秒处理的项目数量的图表,对于好机器和坏机器,水平轴是时间轴。 (请注意,垂直轴是对数的)
图表显示例如多数情况下,“坏”服务器的上限大约为17/18个项目/秒,而“好”服务器能够处理3700个项目/秒,这取决于接收和输入新项目的速率queueA
。
由于我不太熟悉任务库的复杂性,我想知道PreferFairness
和BeginReceive
的异步(因此不是真的)“递归”调用的组合是否可以在某些随机情况下导致此问题。还有其他想法如何深入浅出吗?
我删除了几个try {} catch { error.log(); }
构造来简化它。没有记录任何错误,因此我相信代码不会引发异常。并且queueA
不会单调增长,有时会缩小一点,因此该循环似乎很活跃,尽管很慢。
答案 0 :(得分:0)
Here是关于该主题的很好的读物。
将任务调度到默认调度程序时,调度程序将查找 查看是否正在从中排队任务的当前线程 是具有自己的本地队列的ThreadPool线程。如果不是,则 工作项将排队到全局队列中。如果是,则调度程序 还将检查Task的TaskCreationOptions值 包括PreferFairness标志,默认情况下不启用。如果 即使线程确实有自己的本地队列,也会设置标志 调度程序仍会将任务排队到全局队列中,而不是 到本地队列。通过这种方式,该任务将被考虑 公平地与所有其他全局排队的工作项一起。