HttpWebRequest研磨停止,可能只是因为页面大小
本文关键字:是因为 HttpWebRequest | 更新日期: 2023-09-27 18:20:29
我有一个WPF应用程序,它处理很多url(数千个),每个url都发送到自己的线程,进行一些处理并将结果存储在数据库中。
url可以是任何东西,但有些似乎是非常大的页面,这似乎会大大增加内存使用量,使性能非常糟糕。我在网络请求上设置了一个超时,所以如果它花费的时间超过20秒,它就不会打扰那个url,但似乎没有太大区别。
这是代码部分:
HttpWebRequest req = (HttpWebRequest)HttpWebRequest.Create(urlAddress.Address);
req.Timeout = 20000;
req.ReadWriteTimeout = 20000;
req.Method = "GET";
req.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;
using (StreamReader reader = new StreamReader(req.GetResponse().GetResponseStream()))
{
pageSource = reader.ReadToEnd();
req = null;
}
它似乎也会使读卡器上的内存停滞/增加。ReadToEnd();
我本以为缩短20秒会有帮助,有更好的方法吗?我认为使用异步web方法没有多大好处,因为每个url下载都在自己的线程上。。
感谢
通常,建议您使用异步HttpWebRequest,而不是创建自己的线程。我在上面链接的文章还包括一些基准测试结果。
我不知道你在阅读流结束后对页面源做了什么,但使用字符串可能是个问题:
System.String类型用于任何.NET应用程序。我们有条件如:名称、地址、说明、错误消息、警告甚至应用程序设置。每个应用程序都必须创建、比较或格式化字符串数据。考虑不变性和任何对象可以转换为字符串,所有可用内存都可以被大量不需要的重复字符串吞噬或无人认领字符串对象。
其他一些建议:
- 您有防火墙限制吗我在工作中看到了很多问题,防火墙启用了速率限制,而获取页面却陷入了停顿(这种情况经常发生在我身上)
- 我认为您将使用该字符串来解析HTML,所以我建议您使用
Stream
初始化解析器,而不是传入包含页面源的字符串(如果可以的话) - 如果您将页面源存储在数据库中,那么就没有什么可做的了
- 尝试通过注释掉页面源代码来消除对它的读取,因为它可能会导致内存/性能问题
- 使用流式HTML解析器,如Majestic 12-无需将整个页面源加载到内存中(如果需要解析,请再次使用)
- 限制你要下载的页面的大小,比如说,只下载150KB。平均页面大小约为100KB-130KB
此外,你能告诉我们你最初的页面获取率是多少吗?它到底是多少?在获取页面时,您是否看到来自web请求的任何错误/异常?
更新
在评论部分,我注意到你正在创建数千个线程,我想说你不需要这么做。从少量线程开始,并不断增加它们,直到您看到系统的性能。一旦您开始添加线程,并且性能看起来逐渐下降,那么请停止添加线程。我无法想象你会需要超过128个线程(即使看起来很高)。创建固定数量的线程,例如64个,让每个线程从队列中获取一个URL,获取页面,进行处理,然后再次返回队列中获取页面。
您可以使用缓冲区枚举,而不是调用ReadToEnd,如果时间太长,则可以登录并放弃-类似于:
static void Main(string[] args)
{
Uri largeUri = new Uri("http://www.rfkbau.de/index.php?option=com_easybook&Itemid=22&startpage=7096");
DateTime start = DateTime.Now;
int timeoutSeconds = 10;
foreach (var s in ReadLargePage(largeUri))
{
if ((DateTime.Now - start).TotalSeconds > timeoutSeconds)
{
Console.WriteLine("Stopping - this is taking too long.");
break;
}
}
}
static IEnumerable<string> ReadLargePage(Uri uri)
{
int bufferSize = 8192;
int readCount;
Char[] readBuffer = new Char[bufferSize];
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
using (StreamReader stream = new StreamReader(response.GetResponseStream(), Encoding.UTF8))
{
readCount = stream.Read(readBuffer, 0, bufferSize);
while (readCount > 0)
{
yield return new string(readBuffer, 0, bufferSize);
readCount = stream.Read(readBuffer, 0, bufferSize);
}
}
}
Lirik总结得很好。
我想补充一点,如果我要实现这一点,我会制作一个单独的过程来读取页面。所以,这将是一条管道。第一阶段将下载URL并将其写入磁盘位置。然后将该文件排入下一阶段。下一阶段从磁盘读取并进行解析&数据库更新。这样,您将在下载和解析方面获得最大吞吐量。您还可以调整线程池,以便有更多的工作人员进行解析,等等。这种体系结构也非常适合分布式处理,在分布式处理中,您可以让一台机器下载,另一台主机进行解析等等。
另一件需要注意的事情是,如果您从多个线程访问同一台服务器(即使您使用的是Async),那么您将遇到最大传出连接限制。您可以限制自己保持在该值以下,或者增加ServicePointManager类的连接限制。