5

当服务器处于繁重的 CPU 负载或线程池繁忙时,WCF 可靠会话故障

WCF 可靠会话中似乎存在设计缺陷,当服务器处于高 CPU 负载(80-100% 范围)或没有立即可用的 IO 线程池线程时,该缺陷会阻止基础结构保持活动消息的发布或接受来处理消息。由于可靠的会话不活动超时,症状表现为明显的随机通道中止。然而,中止逻辑似乎以更高的优先级或通过不同的机制运行,因为即使保持活动定时器无法运行,中止定时器似乎也会触发。

深入研究参考源,看来 ChannelReliableSession 使用 InterruptableTimer 类来处理 inactivityTimer。作为响应,它触发由 ReliableOutputSessionChannel 设置的 PollingCallback,它创建一个 ACK​​RequestedMessage 并将其发送到远程端点。InactivityTimer 使用 WCF 内部的 IOThreadTimer/IOThreadScheduler 来调度自己。这取决于一个可用的(非繁忙的)IO ThreadPool 线程来为定时器提供服务。如果 CPU 负载很高,则线程池似乎不会产生新线程。因此,如果有多个线程正在执行(在我的 4 核机器上似乎是 8 个线程;在 15 秒 inactivityTimeout 7 将中止并失败),则没有线程可用于发送 keep-alive。但是,如果您将客户端上的可靠会话不活动超时修改为比服务器更长,即使在这些情况下,服务器仍将单方面中止通道,因为它希望在更短的时间内收到消息。因此,中止逻辑似乎以更高的优先级运行,或者向其中一个正在执行的线程抛出异常(不确定是哪个);我预计服务器上的中止会由于 CPU 高而延迟,并且客户端的超时时间较长最终会命中,但事实并非如此。如果 CPU 负载较低,那么即使并发调用需要 30-90 秒才能返回,这种完全相同的场景也可以正常工作。

您的 InstanceMode 是什么、最大并发连接、会话或实例是什么、其他任何超时值是什么都无关紧要(recieveTimeout 必须大于 inactivityTimeout 除外)。这完全是WCF实现的设计缺陷;它应该使用隔离的高优先级或实时线程来为保持活动消息提供服务,因此不会产生虚假的中止。

简短的版本是:只要 CPU 负载保持在低水平,我可以发出 1000 个需要 60 秒才能完成的并发请求和 15 秒的可靠会话不活动超时,没有问题。一旦 CPU 负载变重,调用将随机开始中止,包括不占用任何 CPU 时间的调用或空闲等待使用的双工会话如果传入的调用也增加了 CPU 负载,那么服务将进入死亡螺旋,因为执行时间被浪费在保证中止的请求上,而其他请求则位于入站队列中。在停止所有请求、所有运行中的线程完成并且 CPU 负载下降之前,该服务无法恢复到健康状态。这种行为似乎使 Reliable Sessions 成为最不可靠的通信机制之一。

同样的行为也适用于客户端;在这种情况下,WCF 客户端可能会受到盒子上其他进程的支配,但在高 CPU 负载下,它将随机中止可靠会话,除非所有操作的完成时间少于 inactivityTimeout,但如果您不发出新的调用很快,WCF 可能仍然无法发送 keep-alive,并且通道可能出现故障。

4

1 回答 1

5

记录我的答案:

如果您使用 ThreadPool.SetMinThreads(X, Y) 可以稍微缓解该问题,其中 Y 比执行并发 WCF 请求的线程数大一些。然后可能有一个可用的线程++来为保持活动提供服务,并且即使在持续的 100% CPU 负载下,可靠会话也可能不会超时,但这也有其局限性。在我的测试中,我将 IO 线程最少从 2 个增加到 20 个,然后发出大量并发(但只是休眠 10 秒的无操作请求)。之后,我重新运行了我的客户端,但调用了 CPU 浪费,我能够同时成功地执行所有 8 个。由于线程池的延迟初始化,重新启动服务然后立即执行相同的客户端测试失败。碰到这个问题,我最终在 14 个同时调用(10 个调用中止)时再次开始超时,这可能只是调度程序没有获得足够的 CPU 片来正确执行。我怀疑如果你能抓住 IO 线程并提高它们的优先级,你也许可以解决这个问题。

++因为池使用延迟初始化,您必须从客户端发出足够的并发调用,这些调用需要时间才能完成但不使用任何 CPU(例如:Thread.Sleep(5000))来强制池创建不触发 high-CPU-blocks-new-threads 逻辑的最小线程数,否则不会创建最小线程数,问题仍然存在。

另一个潜在的解决方法是使 inactivityTimeout 的值非常大。这将有助于缓解问题,但会引入新的拒绝服务漏洞,即使客户端意外关闭连接失败也是如此。

否则,目前似乎没有解决此问题的方法;由于此缺陷,我个人建议不要使用 Reliable Sessions,因为它使中止在连接中止和中止开始发生的情况下都是随机的。

于 2013-04-05T17:51:05.950 回答