问题描述
我有一个用 C# 编写的 ASP.NET 3.5 服务器应用程序.它使用 HttpWebRequest 和 HttpWebResponse 向 REST API 发出出站请求.
I have an ASP.NET 3.5 server application written in C#. It makes outbound requests to a REST API using HttpWebRequest and HttpWebResponse.
我已经设置了一个测试应用程序以在不同的线程上发送这些请求(以模糊地模仿服务器的并发性).
I have setup a test application to send these requests on separate threads (to vaguely mimic concurrency against the server).
请注意,这更像是单声道/环境问题,而不是代码问题;所以请记住,下面的代码不是逐字记录的;只是功能位的剪切/粘贴.
Please note this is more of a Mono/Environment question than a code question; so please keep in mind that the code below is not verbatim; just a cut/paste of the functional bits.
这是一些伪代码:
// threaded client piece
int numThreads = 1;
ManualResetEvent doneEvent;
using (doneEvent = new ManualResetEvent(false))
{
for (int i = 0; i < numThreads; i++)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(Test), random_url_to_same_host);
}
doneEvent.WaitOne();
}
void Test(object some_url)
{
// setup service point here just to show what config settings Im using
ServicePoint lgsp = ServicePointManager.FindServicePoint(new Uri(some_url.ToString()));
// set these to optimal for MONO and .NET
lgsp.Expect100Continue = false;
lgsp.ConnectionLimit = 100;
lgsp.UseNagleAlgorithm = true;
lgsp.MaxIdleTime = 100000;
_request = (HttpWebRequest)WebRequest.Create(some_url);
using (HttpWebResponse _response = (HttpWebResponse)_request.GetResponse())
{
// do stuff
} // releases the response object
// close out threading stuff
if (Interlocked.Decrement(ref numThreads) == 0)
{
doneEvent.Set();
}
}
如果我在 Visual Studio Web 服务器的本地开发机器 (Windows 7) 上运行应用程序,我可以增加 numThreads 并接收相同的平均响应时间,而无论是 1 个用户"还是 100 个用户".
If I run the application on my local development machine (Windows 7) in the Visual Studio web server, I can up the numThreads and receive the same avg response time with minimal variation whether it's 1 "user" or 100.
在 Mono 2.10.2 环境中将应用程序发布和部署到 Apache2,响应时间几乎呈线性增长.(即 1 个线程 = 300 毫秒,5 个线程 = 1500 毫秒,10 个线程 = 3000 毫秒).无论服务器端点(不同的主机名、不同的网络等)如何,都会发生这种情况.
Publishing and deploying the application to Apache2 on a Mono 2.10.2 environment, the response times scale almost linearly. (i.e, 1 thread = 300ms, 5 thread = 1500ms, 10 threads = 3000ms). This happens regardless of server endpoint (different hostname, different network, etc).
使用 IPTRAF(和其他网络工具),应用程序似乎只打开 1 或 2 个端口来路由所有连接,其余响应必须等待.
Using IPTRAF (and other network tools), it appears as though the application only opens 1 or 2 ports to route all connections through and the remaining responses have to wait.
我们已经构建了一个类似的 PHP 应用程序并部署在 Mono 中,具有相同的请求并且响应适当地扩展.
We have built a similar PHP application and deployed in Mono with the same requests and the responses scale appropriately.
我已经完成了我能想到的 Mono 和 Apache 的每一个配置设置,两个环境之间唯一不同的设置(至少在代码中)是有时 Mono 中的 ServicePoint SupportsPipelining=false,而它是在我的机器上是真的.
I have run through every single configuration setting I can think of for Mono and Apache and the ONLY setting that is different between the two environments (at least in code) is that sometimes the ServicePoint SupportsPipelining=false in Mono, while it is true from my machine.
似乎由于某种原因在 Mono 中没有更改 ConnectionLimit(默认值为 2),但我在代码和指定主机的 web.config 中将其设置为更高的值.
It seems as though the ConnectionLimit (default of 2) is not being changed in Mono for some reason but I am setting it to a higher value both in code and the web.config for the specified host(s).
要么我和我的团队忽略了一些重要的事情,要么这是 Mono 中的某种错误.
Either me and my team are overlooking something significant or this is some sort of bug in Mono.
推荐答案
我相信您在 HttpWebRequest
中遇到了瓶颈.每个 Web 请求都使用 .NET 框架内的公共服务点基础结构.这似乎是为了允许重用对同一主机的请求,但根据我的经验会导致两个瓶颈.
I believe that you're hitting a bottleneck in the HttpWebRequest
. The web requests each use a common service point infrastructure within the .NET framework. This appears to be intended to allow requests to the same host to be reused, but in my experience results in two bottlenecks.
首先,为了符合 HTTP 规范,服务点默认只允许两个到给定主机的并发连接.这可以通过将静态属性 ServicePointManager.DefaultConnectionLimit
设置为更高的值来覆盖.有关详细信息,请参阅此 MSDN 页面.看起来您已经在为单个服务点本身解决此问题,但由于服务点级别的并发锁定方案,这样做可能会导致瓶颈.
First, the service points allow only two concurrent connections to a given host by default in order to be compliant to the HTTP specification. This can be overridden by setting the static property ServicePointManager.DefaultConnectionLimit
to a higher value. See this MSDN page for more details. It looks as if you're already addressing this for the individual service point itself, but due to the concurrency locking scheme at the service point level, doing so may be contributing to the bottleneck.
其次,ServicePoint
类本身的锁定粒度似乎存在问题.如果您反编译并查看 lock
关键字的源代码,您会发现它使用实例本身进行同步,并且在许多地方都这样做.由于服务点实例在给定主机的 Web 请求之间共享,根据我的经验,随着打开更多 HttpWebRequests
并导致其扩展性不佳,这往往会成为瓶颈.这第二点主要是个人观察和源头摸索,所以持保留态度;我不会认为它是权威来源.
Second, there appears to be an issue with lock granularity in the ServicePoint
class itself. If you decompile and look at the source for the lock
keyword, you'll find that it uses the instance itself to synchronize and does so in many places. With the service point instance being shared among web requests for a given host, in my experience this tends to bottleneck as more HttpWebRequests
are opened and causes it to scale poorly. This second point is mostly personal observation and poking around the source, so take it with a grain of salt; I wouldn't consider it an authoritative source.
不幸的是,我在使用它时没有找到合理的替代品.既然 ASP.NET Web API 已经发布,您不妨给 HttpClient 一看.希望对您有所帮助.
Unfortunately, I did not find a reasonable substitute at the time that I was working with it. Now that the ASP.NET Web API has been released, you may wish to give the HttpClient a look. Hope that helps.
这篇关于HttpWebResponse 不会针对并发出站请求进行扩展的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!