2008-10-25 50 views
7

我曾经在.NET中写过一个Crawler。为了提高可伸缩性,我试图利用.NET的异步API。.NET没有可靠的异步套接字通信?

System.Net.HttpWebRequest具有异步API BeginGetResponse/EndGetResponse。但是,这对API只是获取HTTP响应头文件和Stream实例,我们可以从中提取HTTP响应内容。所以,我的策略是使用BeginGetResponse/EndGetResponse异步获取响应Stream,然后使用BeginRead/EndRead从响应Stream实例异步获取字节。

一切似乎都是完美的,直到履带车进行压力测试。在压力测试中,Crawler遭受高内存使用。我用WinDbg + SoS检查了内存,并发现大量的字节数组是由System.Threading.OverlappedData实例引起的。在互联网搜索后,我发现这个KB http://support.microsoft.com/kb/947862从微软。

根据知识库,异步I/O的数量应该有一个“上限”,但它不会告诉“建议”的限制值。所以,在我看来,这个KB无济于事。这显然是一个.NET错误。最后,我不得不放弃从响应Stream中进行异步提取字节的想法,只是以同步的方式进行。

在.NET库,允许 异步IO用点网插座 (Socket.BeginSend/ Socket.BeginReceive/ NetworkStream.BeginRead/ NetworkStream.BeginWrite)必须对量的 上限缓冲区 未完成(发送或接收) 与他们的异步IO。

网络应用应当具有 上限 优秀异步IO,它的帖子的数量。

编辑:添加一些问号。

任何人有任何经验在Socket & NetworkStream上做异步I/O? 一般来说,生产中的爬虫是否使用带有同步或异步的互联网进行I/O?

+0

不是一个单独的问号,除了在主题...一个坏的标志。 – 2008-10-25 10:02:19

回答

3

显然你想限制并发请求的数量,不管你的爬虫是同步/异步。这个限制是不固定的,这取决于你的硬件,网络,...取决于你的硬件,网络,...

我不太确定这里有什么问题,因为HTTP /套接字的.NET实现是“OK”。有一些漏洞(关于正确控制超时,请参阅my post),但它可以完成工作(我们有一个每秒抓取数百页的生产爬虫)。

顺便说一句,我们使用同步IO,只是为了方便。每个任务都有一个线程,并且我们限制并发线程的数量。对于线程管理,我们使用了Microsoft CCR

+0

我毫不怀疑Socket上的同步I/O在DotNet中工作正常。我只是不相信它的异步I/O API。 – 2008-10-25 10:37:56

+0

问题是中止/取消操作,它在.NET中无法正常工作。 你应该总是喜欢同步API(超时),这样你就不需要自己取消操作。 – ripper234 2008-10-30 15:05:30

10

Hmya,这不是一个.NET框架问题。链接的知识库文章可能会更加明确:“您使用的是加载枪,这是当您将其瞄准在脚上时发生的情况。”枪中的子弹是.NET,让你能够启动尽可能多的异步I/O请求。它会做你要求它做的事,直到你遇到某种资源限制。在这种情况下,可能在第0代堆中有太多的固定接收缓冲区。

资源管理仍然是我们的工作,而不是.NET的。这与分配无限制内存没有区别。解决这个特定的问题需要您对未完成的BeginGetResponse()请求的数量进行限制。有数以百计的人没有什么意义,他们每个人都必须一次挤过Intertube。添加另一个请求只会导致它花费更长的时间才能完成。或者崩溃你的程序。

0

没有KB文章可以给你一个上限。上限可以根据可用硬件而变化 - 对于装有16g内存的机器,2G内存机器的上限是什么?它也将取决于GC堆的大小,它是多么零散等等。

你应该做的是用你自己的度量来使用信封计算。找出每分钟要下载的页数。这应该确定你想要的异步请求数(N)。一旦你知道N,就创建一段代码(比如生产者 - 消费者管道的消费者端),它可以创建N个未完成的异步下载请求。一旦请求完成(无论是由于超时还是由于成功),通过从队列中提取工作项来启动另一个异步请求。

您还需要确保队列不会超出边界,例如,无论出于何种原因下载速度变慢。

0

当您使用套接字的异步发送(BeginSend)方法时发生这种情况。如果你使用你自己的自定义线程池,并通过线程发送数据同步Send方法主要是解决这个问题。经过测试和证明。

3

这不限于.Net。

这是一个简单的事实,即每个异步请求(文件,网络等)使用内存和(至少在某些时候用于联网请求)非页面缓冲池(请参阅here了解非托管代码中可能遇到的问题的详细信息)。未完成请求的数量因此受内存量的限制。在Vista之前,存在一些严重低的非页面缓冲池限制,会在内存耗尽之前给您带来问题,但在后Vista环境下,非页面缓冲池使用会更好(请参阅here)。

这在托管代码中稍微复杂一点,除了在非托管环境中遇到的问题之外,还必须处理这样一个事实,即用于异步请求的内存缓冲区将被锁定,直到这些请求完成。听起来就像你在读取时遇到了这些问题,但是对于写入来说,如果不是更糟糕的话,它也同样糟糕(只要TCP流量控制在连接上启动,那些发送完成将开始花费更长的时间,因此这些缓冲区固定的时间越长越长 - 请参阅herehere)。

问题不在于.Net异步的东西坏了,只是抽象是这样,它使得它看起来都比实际更容易。例如,为了避免钉住问题,在程序启动时将所有缓冲区分配到一个大的连续块中,而不是根据需要进行分配......

个人而言,我会在非托管代码中编写这样的爬虫程序,但这是只是我;)你仍然会遇到很多问题,但你对它们有更多的控制权。