当服务器处于繁重的 CPU 负载或线程池繁忙时,WCF 可靠会话故障
WCF 可靠会话中似乎存在设计缺陷,当服务器处于高 CPU 负载(80-100% 范围)或没有立即可用的 IO 线程池线程时,该缺陷会阻止基础结构保持活动消息的发布或接受来处理消息。由于可靠的会话不活动超时,症状表现为明显的随机通道中止。然而,中止逻辑似乎以更高的优先级或通过不同的机制运行,因为即使保持活动定时器无法运行,中止定时器似乎也会触发。
深入研究参考源,看来 ChannelReliableSession 使用 InterruptableTimer 类来处理 inactivityTimer。作为响应,它触发由 ReliableOutputSessionChannel 设置的 PollingCallback,它创建一个 ACKRequestedMessage 并将其发送到远程端点。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,并且通道可能出现故障。