如何对 IIS 托管的 WCF 服务中超出的 MaxConcurrentSessions 进行故障排除
How to troubleshoot MaxConcurrentSessions exceeded in IIS hosted WCF Service
我已经离开了我的舒适区,请耐心等待我提供相关信息。我们刚刚将 IIS 托管的 WCF 服务移动到新服务器,调用此服务的客户端开始出现超时。回收应用程序池后大约 10 分钟一切正常,然后一切开始超时。我们启用了 WCF 跟踪,我可以看到它说已超过 MaxConcurrentSessions。文档说该值默认为 2 x [# of processors] 所以它对我们来说应该是 200。
服务器在负载平衡器后面,但目前是唯一的服务器。我们注意到性能监视器中的连接以每秒 6 左右的速度挂起,但当超时发生时会上升到 30 左右,并从那里继续上升。
客户端正在使用 wsHttpBinding
TransportWithMessageCredential
安全性进行连接。该服务使用配置为在服务器绑定行为上使用的自定义 UserNamePasswordValidator
中的 asp.net 成员身份提供程序来验证消息中提供的凭据。客户端不会在其绑定上启用 reliableSession
。该服务使用默认的 SessionMode
和 InstanceContextMode
,我认为它们分别是 Allowed
和 PerSession
?我们不会在服务代理上调用 Close
,因为在过去的调查中,我发现这只会在选项上设置一个标志,防止它被重新使用,而我们的总是超出范围......但现在正在测试以查看这是否会关闭连接。
如果我正确地解释了 WCF 跟踪日志(并且我不理解我在那里阅读的大部分内容),那么我们似乎每分钟处理大约 30-40 条消息并且每个请求都已完成在不到 300 毫秒的时间内(通常更少,在极少数情况下接近 1 秒。)我通过在几个 1 分钟的跨度内计算 Processing message n
消息来确定消息的数量。因此,如果我们每分钟获得 40 个,而那些 connections/sessions 超时和关闭需要 100 秒,那么在第一个开始超时之前,我们仍然只能同时打开大约 68 个。不接近 200 的限制。单个客户端请求的连接是否获得多个会话?
奇怪的是我们之前没有任何超时,并且将服务和 web.config 直接复制到新服务器。我相信服务器和 IIS 版本已升级(服务器 2016,IIS 10。)您能否帮我确定并提供相关信息以追踪导致这些超时的问题?
编辑:
根据我的阅读,一切似乎都表明客户端必须调用 Close
否则服务器将保持连接打开直到超时。但是,在我们的测试中,我们看到在 perf 中创建了一个连接。星期一但在调用 Close
之后它仍保持打开状态。所以我无法确定是否需要调用 close 是谣言,或者我们是否误解了我们的监控。真正的测试是到处调用 Close
并查看它是否消除了我们的超时。
将 MaxConcurrentSessions
增加到 400 后,在性能监视器中,我们看到并发会话和实例的数量以每秒约 1 的速度稳步上升,最高达到约 225,最终趋于平稳,并在附近徘徊.所以看起来会话没有关闭。
好吧,我们想通了。没有任何东西弹出来告诉我们问题出在哪里,这需要大量的头脑风暴,但我们是这样做的:
启用 WCF 跟踪。走遍痕迹,能看得懂,基本能看出来流量没有异常。所有事件似乎都是针对预期数量和类型的服务呼叫。查看svctraceviewer,似乎不是DOS攻击之类的。我们只是使用了那个 link 中的默认配置,但如果您知道那是什么,它看起来可以非常自定义以提供您所需要的特定信息。
在这种情况下真正有用的是找到 WCF Performance Counters. Initially we were using ASP.NET performance counters to look at sessions open which was not the right metric. This codeproject guide 帮助我们启用 WCF 性能计数器让我们深入了解 session 的数量和限制实时.
它还有助于复习 WCF session 和实例之间的关系以及安全上下文的创建:
我们能够看到正在使用的最大 WCF session 的百分比,并观察到它越来越接近默认限制 200(每个处理器 100),但最终稳定在 150 和200. 这种趋于平稳,加上在给定时间存在的 session 远远多于我们的 WCF 跟踪中看到的每分钟平均请求数,表明 session 正在关闭但似乎仍然存在打开直到超时,而不是在服务器完成请求后立即关闭。
在 Stack Overflow 的某个地方,我一直找不到,我曾经询问过 [ClientBase<TChannel>.Close][4]
方法的用途(a.k.a。WCF 服务代理的关闭方法)和,有点不正确,得出的结论是它所做的只是在代理 object 上设置一个标志,标记它已关闭,这样就不能再次使用它。文档对该方法的描述似乎与此一致:
Causes the ClientBase<TChannel>
object to transition from its current
state into the closed state.
好吧,在我调用 Close
的时候,我的引用总是超出范围,无论如何允许垃圾 collection 清理它,所以这看起来毫无意义。但我认为一个关键因素是关于无状态的 basicHttpBindings。在这种情况下,我们使用的是有状态的 wsHttpBindings,这意味着服务器会保留 session 并在完成请求后保持连接打开,以便客户端的后续调用可以在同一连接上进行。因此,虽然我找不到任何文档或在源代码中找到它发生的位置,但似乎 WCF 客户 必须 在他们的服务代理上调用 Close
最后一个请求是为了告诉服务器它可以关闭连接并释放 session 插槽。我没有机会在调用 Close
时查找发送到服务器的消息来执行此操作,但我们能够使用性能计数器观察到 session 的数量从1 到 0,之前在我们的客户调用服务后它将保持为 1。
但是我们说的是我们可能无法控制的 WCF 客户端能够损害服务器性能,并且如果他们不勤于编码并记住调用 Close
并且服务器无法控制自己的性能??这听起来像是灾难的根源。好吧,您可以在服务器上做两件事来缓解这种情况。首先,您可以增加 session 的最大数量。在我们的案例中,我们徘徊在 175 左右,但偶尔会出现超过 200 的流量高峰。我们暂时将其提高到 800 以确保我们不会超过最大值。 trade-off 将更多的服务器资源用于保存那些可能永远不会再使用的 session,直到它们超时。幸运的是,服务器还控制超时。该服务可以使用 ReceiveTimeout
and the InactivityTimeout
. Both default to 10 minutes but the lesser of the two will be used. If you're thinking, "Receive timeout sounds wrong. That controls the amount of time the service can take to receive a large message", you're not alone. However, that's incorrect 控制这些 session 保持打开的长度。在服务器端:
ReceiveTimeout – used by the Service Framework Layer to initialize the session-idle timeout which controls how long a session can be idle before timing out.
而在 client-side 上则未使用。所以我们将 ReceiveTimeout
设置为 30 秒,session 显着下降。这实际上可能太低了,因为代码中的某些点 re-use 服务代理(例如在循环中进行多次调用,或者在调用之间进行一些数据处理)现在在尝试调用时出错session 后的服务已关闭。所以你必须找到合适的平衡点。但最好的做法似乎是关闭您的连接。
需要注意的一个问题是在您的服务代理上使用 Dispose
。我一直尝试输入 .dispo
以查看智能感知是否会在我的代理上弹出 Dispose
方法,但发现它没有,所以假设它没有实现 IDisposable
并且不需要将被关闭或处置。事实证明它确实实现了 IDisposable
但它是明确实现的,因此您必须将其转换为 IDisposable
才能在其上调用 Dispose
。可是等等!暂时不要将代理放在 using
语句中。执行器Dispose
愚蠢地只是在代理上调用 Close
,如果代理处于故障状态(即,如果服务调用抛出异常),这将抛出异常。所以你不能安全地做这样的事情:
using(MyWcfClient proxy = new MyWcfClient())
{
try
{
proxy.Calculate();
}
catch(Exception)
{
}
}
因为如果 Calculate
抛出异常,using
块的右括号在尝试处理您的代理时也会抛出异常。相反,您只需在最后一次调用服务方法后调用 Close
。显然你也可以在 catch
中调用 Abort
,但我不确定它是否真的与服务器通信以结束 session。
MyWcfClient proxy = new MyWcfClient
try
{
proxy.Calculate();
proxy.Close();
}
catch(Exception)
{
proxy.Abort();
}
附录
我们推测我们在移动服务器时开始遇到这种情况并且以前没有遇到过的原因是我们以前使用梭子鱼产品现在使用 Oracle,也许旧的负载平衡器或防火墙正在为我们关闭打开的连接。
我已经离开了我的舒适区,请耐心等待我提供相关信息。我们刚刚将 IIS 托管的 WCF 服务移动到新服务器,调用此服务的客户端开始出现超时。回收应用程序池后大约 10 分钟一切正常,然后一切开始超时。我们启用了 WCF 跟踪,我可以看到它说已超过 MaxConcurrentSessions。文档说该值默认为 2 x [# of processors] 所以它对我们来说应该是 200。
服务器在负载平衡器后面,但目前是唯一的服务器。我们注意到性能监视器中的连接以每秒 6 左右的速度挂起,但当超时发生时会上升到 30 左右,并从那里继续上升。
客户端正在使用 wsHttpBinding
TransportWithMessageCredential
安全性进行连接。该服务使用配置为在服务器绑定行为上使用的自定义 UserNamePasswordValidator
中的 asp.net 成员身份提供程序来验证消息中提供的凭据。客户端不会在其绑定上启用 reliableSession
。该服务使用默认的 SessionMode
和 InstanceContextMode
,我认为它们分别是 Allowed
和 PerSession
?我们不会在服务代理上调用 Close
,因为在过去的调查中,我发现这只会在选项上设置一个标志,防止它被重新使用,而我们的总是超出范围......但现在正在测试以查看这是否会关闭连接。
如果我正确地解释了 WCF 跟踪日志(并且我不理解我在那里阅读的大部分内容),那么我们似乎每分钟处理大约 30-40 条消息并且每个请求都已完成在不到 300 毫秒的时间内(通常更少,在极少数情况下接近 1 秒。)我通过在几个 1 分钟的跨度内计算 Processing message n
消息来确定消息的数量。因此,如果我们每分钟获得 40 个,而那些 connections/sessions 超时和关闭需要 100 秒,那么在第一个开始超时之前,我们仍然只能同时打开大约 68 个。不接近 200 的限制。单个客户端请求的连接是否获得多个会话?
奇怪的是我们之前没有任何超时,并且将服务和 web.config 直接复制到新服务器。我相信服务器和 IIS 版本已升级(服务器 2016,IIS 10。)您能否帮我确定并提供相关信息以追踪导致这些超时的问题?
编辑:
根据我的阅读,一切似乎都表明客户端必须调用 Close
否则服务器将保持连接打开直到超时。但是,在我们的测试中,我们看到在 perf 中创建了一个连接。星期一但在调用 Close
之后它仍保持打开状态。所以我无法确定是否需要调用 close 是谣言,或者我们是否误解了我们的监控。真正的测试是到处调用 Close
并查看它是否消除了我们的超时。
将 MaxConcurrentSessions
增加到 400 后,在性能监视器中,我们看到并发会话和实例的数量以每秒约 1 的速度稳步上升,最高达到约 225,最终趋于平稳,并在附近徘徊.所以看起来会话没有关闭。
好吧,我们想通了。没有任何东西弹出来告诉我们问题出在哪里,这需要大量的头脑风暴,但我们是这样做的:
启用 WCF 跟踪。走遍痕迹,能看得懂,基本能看出来流量没有异常。所有事件似乎都是针对预期数量和类型的服务呼叫。查看svctraceviewer,似乎不是DOS攻击之类的。我们只是使用了那个 link 中的默认配置,但如果您知道那是什么,它看起来可以非常自定义以提供您所需要的特定信息。
在这种情况下真正有用的是找到 WCF Performance Counters. Initially we were using ASP.NET performance counters to look at sessions open which was not the right metric. This codeproject guide 帮助我们启用 WCF 性能计数器让我们深入了解 session 的数量和限制实时.
它还有助于复习 WCF session 和实例之间的关系以及安全上下文的创建:
我们能够看到正在使用的最大 WCF session 的百分比,并观察到它越来越接近默认限制 200(每个处理器 100),但最终稳定在 150 和200. 这种趋于平稳,加上在给定时间存在的 session 远远多于我们的 WCF 跟踪中看到的每分钟平均请求数,表明 session 正在关闭但似乎仍然存在打开直到超时,而不是在服务器完成请求后立即关闭。
在 Stack Overflow 的某个地方,我一直找不到,我曾经询问过 [ClientBase<TChannel>.Close][4]
方法的用途(a.k.a。WCF 服务代理的关闭方法)和,有点不正确,得出的结论是它所做的只是在代理 object 上设置一个标志,标记它已关闭,这样就不能再次使用它。文档对该方法的描述似乎与此一致:
Causes the
ClientBase<TChannel>
object to transition from its current state into the closed state.
好吧,在我调用 Close
的时候,我的引用总是超出范围,无论如何允许垃圾 collection 清理它,所以这看起来毫无意义。但我认为一个关键因素是关于无状态的 basicHttpBindings。在这种情况下,我们使用的是有状态的 wsHttpBindings,这意味着服务器会保留 session 并在完成请求后保持连接打开,以便客户端的后续调用可以在同一连接上进行。因此,虽然我找不到任何文档或在源代码中找到它发生的位置,但似乎 WCF 客户 必须 在他们的服务代理上调用 Close
最后一个请求是为了告诉服务器它可以关闭连接并释放 session 插槽。我没有机会在调用 Close
时查找发送到服务器的消息来执行此操作,但我们能够使用性能计数器观察到 session 的数量从1 到 0,之前在我们的客户调用服务后它将保持为 1。
但是我们说的是我们可能无法控制的 WCF 客户端能够损害服务器性能,并且如果他们不勤于编码并记住调用 Close
并且服务器无法控制自己的性能??这听起来像是灾难的根源。好吧,您可以在服务器上做两件事来缓解这种情况。首先,您可以增加 session 的最大数量。在我们的案例中,我们徘徊在 175 左右,但偶尔会出现超过 200 的流量高峰。我们暂时将其提高到 800 以确保我们不会超过最大值。 trade-off 将更多的服务器资源用于保存那些可能永远不会再使用的 session,直到它们超时。幸运的是,服务器还控制超时。该服务可以使用 ReceiveTimeout
and the InactivityTimeout
. Both default to 10 minutes but the lesser of the two will be used. If you're thinking, "Receive timeout sounds wrong. That controls the amount of time the service can take to receive a large message", you're not alone. However, that's incorrect 控制这些 session 保持打开的长度。在服务器端:
ReceiveTimeout – used by the Service Framework Layer to initialize the session-idle timeout which controls how long a session can be idle before timing out.
而在 client-side 上则未使用。所以我们将 ReceiveTimeout
设置为 30 秒,session 显着下降。这实际上可能太低了,因为代码中的某些点 re-use 服务代理(例如在循环中进行多次调用,或者在调用之间进行一些数据处理)现在在尝试调用时出错session 后的服务已关闭。所以你必须找到合适的平衡点。但最好的做法似乎是关闭您的连接。
需要注意的一个问题是在您的服务代理上使用 Dispose
。我一直尝试输入 .dispo
以查看智能感知是否会在我的代理上弹出 Dispose
方法,但发现它没有,所以假设它没有实现 IDisposable
并且不需要将被关闭或处置。事实证明它确实实现了 IDisposable
但它是明确实现的,因此您必须将其转换为 IDisposable
才能在其上调用 Dispose
。可是等等!暂时不要将代理放在 using
语句中。执行器Dispose
愚蠢地只是在代理上调用 Close
,如果代理处于故障状态(即,如果服务调用抛出异常),这将抛出异常。所以你不能安全地做这样的事情:
using(MyWcfClient proxy = new MyWcfClient())
{
try
{
proxy.Calculate();
}
catch(Exception)
{
}
}
因为如果 Calculate
抛出异常,using
块的右括号在尝试处理您的代理时也会抛出异常。相反,您只需在最后一次调用服务方法后调用 Close
。显然你也可以在 catch
中调用 Abort
,但我不确定它是否真的与服务器通信以结束 session。
MyWcfClient proxy = new MyWcfClient
try
{
proxy.Calculate();
proxy.Close();
}
catch(Exception)
{
proxy.Abort();
}
附录
我们推测我们在移动服务器时开始遇到这种情况并且以前没有遇到过的原因是我们以前使用梭子鱼产品现在使用 Oracle,也许旧的负载平衡器或防火墙正在为我们关闭打开的连接。