更新19/2,2013:我们已经清除了一些问号在这,我有一个理论的主要问题是,我会在下面更新。不准备写一个“解决”的反应,但它。
更新24/4,2013年:事情已经稳定在生产(虽然我相信它是暂时的)一段时间,我认为这是由于两个原因。 1)端口增加,以及2)减少的输出(转发)请求的数量。我将在正确的上下文中继续此更新。
我们目前正在调查我们的生产环境,以确定为什么当太多的传出异步Web服务请求(一个传入请求可能触发多个传出请求)时IIS Web服务器不扩展。
cpu只有20%,但我们收到HTTP 503错误传入的请求和许多传出的Web请求得到以下异常:“SocketException:无法执行套接字上的操作,因为系统缺乏足够的缓冲区空间或因为队列完全“显然,在某处存在可扩展性瓶颈,我们需要找出它是什么,如果可以通过配置解决它。
应用上下文:
我们在Windows 2008 R2 64位操作系统上使用.NET 4.5运行IIS v7.5集成管理管道。我们在IIS中只使用1个工作进程。硬件略有不同,但用于检查错误的机器是Intel Xeon 8内核(16个超线程)。
我们使用异步和同步Web请求。那些异步的是使用新的.NET异步支持,使每个传入请求在应用程序中对持久化TCP连接(保持活动)的其他服务器发出多个HTTP请求。同步请求执行时间低0-32 ms(由于线程上下文切换,发生更长的时间)。对于异步请求,执行时间可以在请求中止前最多120 ms。
通常每个服务器最多提供〜1000个传入请求。当问题开始出现时,传出请求约〜300请求/秒高达〜600请求/秒。仅当传出异步时才会出现问题。请求在服务器上启用,我们超过一定级别的传出请求(〜600 req./s)。
问题的可能解决方案:
在这个问题上搜索互联网可以发现大量可能的解决方案。虽然,他们非常依赖于.NET,IIS和操作系统的版本,所以它需要时间在我们的上下文中找到的东西(anno 2013)。
下面是一个解决方案候选列表和我们到目前为止关于我们的配置上下文的结论。我已经分类检测到的问题区域,到目前为止在以下主要类别:
>一些队列填满
> TCP连接和端口的问题(更新19/2,2013:这是问题)
>资源分配太慢
>内存问题(更新19/2,2013:这很可能是另一个问题)
1)一些队列填满
传出的异步请求异常消息确实指示缓冲区的某个队列已经被填满。但它不会说哪个队列/缓冲区。通过IIS forum(和博客文章引用那里)我已经能够区分4可能6(或更多)不同类型的队列在请求管道标记为A-F下面。
虽然应该说明的是,在所有下面定义的队列中,我们看到1.B)ThreadPool性能计数器请求排队在有问题的负载期间变得非常满。所以很可能是问题的原因是在.NET级别,不低于这个(C-F)。
1.A).NET Framework级队列?
我们使用.NET框架类WebClient发出异步调用(异步支持),而不是我们遇到的HttpClient具有相同的问题,但是具有低得多的req / s阈值。我们不知道.NET Framework实现是隐藏任何内部队列还是不隐藏在Thread池上面。我们不认为这是真的。
.NET线程池
线程池作为一个自然的队列,因为.NET线程(默认)调度器从线程池中选择线程来执行。
性能计数器:[ASP.NET v4.0.30319]。[请求排队]。
配置可能性:
>(ApplicationPool)maxConcurrentRequestsPercpu应为5000(而不是前面的12)。所以在我们的情况下,它应该是5000 * 16 = 80.000请求/秒,这应该足够在我们的方案。
>(processModel)autoConfig = true / false,允许根据机器配置设置some threadPool related configuration。我们使用真,这是一个潜在的错误候选,因为这些值可能会错误地设置为我们的(高)需要。
1.C)全局,进程宽,本地队列(仅限IIS集成模式)
如果线程池已满,请求开始在此本机(非管理)队列中堆积。
性能计数器:[ASP.NET v4.0.30319]。[本机队列中的请求]
配置可能性:?
1.D)HTTP.sys队列
这个队列与上面的1.C)不是同一个队列。这里是我所说的解释“HTTP.sys内核队列本质上是一个完成端口,用户模式(IIS)从内核模式(HTTP.sys)接收请求。它有一个队列限制,当超过时,您将收到503状态码。 HTTPErr日志还将通过记录503状态和QueueFull指示这发生。
性能计数器:我找不到此队列的任何性能计数器,但是通过启用IIS HTTPErr日志,应该可以检测此队列是否被洪泛。
配置可能性:这是在IIS中对应用程序池设置,高级设置:队列长度。默认值为1000.我已经看到将建议增加到10.000。虽然尝试这个增加还没有解决我们的问题。
1.E)操作系统未知队列?
虽然不太可能,我猜测OS实际上可能有一个队列在网卡缓冲区和HTTP.sys队列之间的某个地方。
1.F)网卡缓冲区:
当请求到达网卡时,很自然地,它们被放置在某些缓冲区中以便被某些OS内核线程拾取。由于这是内核级别的执行,因此很快,它不可能是罪魁祸首。
Windows性能计数器:[网络接口]。[丢弃的数据包]使用网卡实例。
配置可能性:?
2)TCP连接和端口的问题
这是一个候选人弹出这里和那里,虽然我们的传出(异步)TCP请求是由持久(保持活动)TCP连接。因此,随着流量的增长,可用临时端口的数量实际上只会由于传入请求而增长。我们知道,当我们有传出请求启用时,问题才会出现。
然而,由于在请求的较长时间段期间分配端口,所以仍然可能出现问题。外发请求可能需要长达120 ms的时间来执行(在.NET任务(线程)被取消之前),这可能意味着端口数量被分配更长的时间段。分析Windows性能计数器,验证此假设,因为TCPv4的数量[连接已建立]从正常2-3000到总峰值高达几乎12.000的问题发生时。
我们已经验证配置的最大TCP连接数被设置为默认值16384.在这种情况下,它可能不是问题,虽然我们危险地接近最大限制。
当我们尝试在服务器上使用netstat时,它通常返回没有任何输出,也使用TcpView显示很少的项目在开始。如果我们让TcpView运行一段时间,它很快开始显示新的(传入)连接相当迅速(说25连接/秒)。几乎所有连接都处于TIME_WAIT状态,表明他们已经完成并等待清理。这些连接是否使用临时端口?本地端口始终为80,远程端口正在增加。我们想使用TcpView为了看到流出的连接,但我们不能看到他们列出,这是非常奇怪。这两个工具不能处理我们拥有的连接数量?
(要继续….但请填写信息,如果你知道它…)
更多,作为一边踢这里。在本博客文章“ASP.NET Thread Usage on IIS 7.5,IIS 7.0,and IIS 6.0”中建议ServicePointManager.DefaultConnectionLimit应设置为int maxValue,否则可能是一个问题。但在.NET 4.5中,这是从一开始就已经是默认的。
更新19/2,2013:
>可以合理地假设我们确实达到了16.384个端口的最大限制。我们将所有端口数量都增加了一倍,但是只有一个服务器,只有老的服务器会遇到问题,当我们达到传统请求的高峰负载。那么为什么TCP.v4。[Connections Established]在问题时间从来没有告诉我们比〜12.000更高的数字? MY理论:很可能,虽然没有建立为事实(但),性能计数器TCPv4。[建立连接]不等于当前分配的端口数。我还没有时间赶上TCP状态学习,但我猜测,有更多的TCP状态比“连接已建立”显示哪些将使端口被占用。虽然由于我们不能使用“连接已建立”性能计数器作为一种检测端口用尽的危险的方法,重要的是我们找到一些其他方式来检测达到这个最大端口范围。并且如上文所述,我们不能在我们的生产服务器上使用NetStat或应用程序TCPview。这是个问题! (我会写一个即将到来的响应我想这个帖子更多)
>窗口上的端口数量限制为最大65.535(尽管第一个〜1000可能不使用)。但是应该可以通过减少TCP状态TIME_WAIT(默认为240秒)的时间来避免端口耗尽的问题,如在许多地方所描述的。应该更快地释放端口。我第一次有点犹豫这样做,因为我们使用长期运行的数据库查询以及TCP上的WCF调用,我不想减少时间约束。虽然没有赶上我的TCP状态机读取,但我认为这可能不是一个问题。状态TIME_WAIT,我想,只是为了允许握手正确关闭到客户端。因此,由于此时间限制,现有TCP连接上的实际数据传输不应超时。更糟糕的情况下,客户端没有正确关闭,而是反而超时。我想所有的浏览器可能没有正确实现这一点,它可能是一个问题在客户端。虽然我在这里猜一点…
END UPDATE 19/2,2013
更新24/4,2013:
我们已经将端口数增加到最大值。同时,我们不会像之前那样获得多少转发的传出请求。这两个组合应该是我们没有任何事故的原因。但是,它只是暂时的,因为在这些服务器上,未来的传出请求数量将再次增加。因此,问题在于,我认为,用于传入请求的端口必须在用于所转发的请求的响应的时间帧期间保持打开。在我们的应用中,这些转发请求的取消限制是120ms,可以与正常<1ms处理未转发请求相比较。因此,本质上,我相信端口的确定数量是我们使用的这种高吞吐量服务器(在〜16核机器上的> 1000请求/秒)的主要可扩展性瓶颈。这与GC工作在缓存重新加载(见下面)使服务器特别恶劣。 END UPDATE 24/4 3)资源分配过慢 我们的性能计数器显示在线程池(1B)中排队的请求数在问题的时间波动很大。所以潜在地这意味着我们有一个动态情况,其中队列长度开始振荡由于环境的变化。例如,如果存在洪泛保护机制,则在流量洪泛时激活这种情况。就这样,我们有一些这样的机制: 3.A)Web负载均衡器 当事情变得非常糟糕,并且服务器响应HTTP 503错误时,负载平衡器将自动删除Web服务器在生产中活动15秒的时间。这意味着其他服务器将在时间帧期间承担增加的负载。在“冷却期间”,服务器可以完成服务其请求,并且当负载平衡器执行其下一次ping时,服务器将自动恢复。当然这只是好的,只要所有的服务器不会有一个问题。幸运的是,到目前为止,我们还没有在这种情况下。 3.B)专用阀 在Web应用程序中,我们有自己构造的阀门(是的,它是一个“阀门”,而不是“值”)由Windows性能计数器触发的线程池中的排队请求。有一个线程,在Application_Start中启动,每秒检查此性能计数器值。如果值超过2000,则所有传出流量将停止启动。接下来的第二点,如果队列值低于2000,则流出的流量再次开始。 这里奇怪的是,它没有帮助我们达到错误情况,因为我们没有太多的日志记录这种情况发生。这可能意味着,当交通量击中我们很困难,事情变坏了真的很快,所以1秒的时间间隔检查实际上是太高。 3.C)线程池线程缓慢增加(和减少) 还有另一个方面。当应用程序池中需要更多的线程时,这些线程会被分配得非常缓慢。从我读到的,每秒1-2线程。这是因为它是昂贵的创建线程,并且因为你不想太多的线程反正为了避免昂贵的上下文切换在同步的情况下,我认为这是自然的。然而,它也应该意味着,如果一个突然的大突发的流量命中我们,线程的数量不会接近足够满足异步场景中的需要,并且请求的排队将开始。这是一个很可能的问题候选人我想。然后,一个候选解可以增加在ThreadPool中创建的线程的最小量。但我想这也可能影响同步运行的请求的性能。 4)内存问题 (Joey Reyes写了关于这个here in a blog post)
由于对象随后会被异步请求收集(在我们的情况下高达120毫秒),因此可能会出现内存问题,因为对象可以升级到第1代,并且内存不会像应该的那样频繁地被收集。垃圾收集器上增加的压力可能很可能导致扩展的线程上下文切换发生并进一步削弱服务器的容量。
但是,在问题发生的时候,我们没有看到GC和cpu使用率的增加,所以我们认为建议的cpu节流机制不是我们的解决方案。
UPDATE 19/2,2013:我们使用一个缓存交换机制在正常的intervalls,一个(几乎)完整的内存缓存重新加载到内存,旧缓存可以得到垃圾收集。在这些时候,GC将不得不更努力地工作,并从正常的请求处理中窃取资源。使用Windows性能计数器用于线程上下文切换,它显示上下文切换的数量从高GC使用时的正常高值显着减少。我认为在这样的缓存重新加载期间,服务器是额外的漏洞排队请求,并有必要减少GC的占用。解决这个问题的一个可能的办法是只填充缓存,而不是一直分配内存。多一点工作,但它应该是可行的。
更新24/4,2013:
我仍然在缓存的中间重新加载内存调整,以避免GC运行尽可能多。但是当GC运行时,我们通常会暂时有大约1000个排队请求。由于它在所有线程上运行,因此它从正常请求处理中窃取资源。一旦这个调整部署,我将更新此状态,我们可以看到一个差异。
END UPDATE 24/4
解决方法
为了缩放,必须将processModel设置为false并微调线程池。我发现,与processModel默认值的文档相反,许多线程池未正确配置,当processModel设置为true时。 maxConnection设置也很重要,因为它限制了您的可扩展性,如果限制设置得太低。见http://support.microsoft.com/default.aspx?scid=kb;en-us;821268
由于套接字上的TIME_WAIT延迟,您的应用程序运行的端口不足,我也面临同样的问题,因为我在240秒内从有限的一组机器注入流量超过64k的请求。我将TIME_WAIT降低到30秒没有任何问题。
我也错误地在几个线程中重用了一个代理对象到Web服务端点。虽然代理没有任何状态,我发现GC有很多问题收集与其内部缓冲区(String []实例)相关联的内存,并导致我的应用程序耗尽内存。
您应该监视的一些有趣的性能计数器是与排队请求,执行中的请求和ASP.NET应用程序类别下的请求时间相关的计数器。如果您看到排队的请求或执行时间较少,但客户端看到长的请求时间,那么您的服务器中会出现某种争用。还监视LocksAndThreads类别下的计数器,以查找争用。