10.1 Scrapy 引擎——一种直观方式
并行系统看起来与管道系统很相似。在计算机科学中,我们使用队列符号来表示队列以及处理中的元素(见图10.1左侧)。队列系统的基本法则是利特尔法则,该法则认为在稳定状态下,队列系统中的元素数量(N)等于系统吞吐量(T)乘以总排队/服务时间(S),即N = T · S。另外两种形式是:T = N / S以及S = N / T,在计算中同样有用。
图10.1 利特尔法则、队列系统以及管道
在管道的几何形状中也有相似的法则(见图10.1右侧)。管道容量(V)等于管道长度L乘以横截面面积(A),即V = L·A。
如果我们想象长度表示服务时间(L~S),容量表示处理系统的元素数量(V~N),横截面面积表示吞吐量(A~N),那么利特尔法则和容量公式实际是相同的事情。
这个类比有道理吗?答案是差不多。如果我们将工作单位想象为小滴液体,以恒定速率在管道内部移动,那么L~S绝对有意义,因为管道越长,水滴移动花费的时间越多。V~N同样有意义,因为管道越大,能够容纳的水滴越多。烦人的是,我们还可以通过施加更大压力的方式压入更多水滴。A~T是不太满足类比的一点。在管道中,实际吞吐量,即每秒进出管道的水滴数量,被称为“体积流量”,除非满足特定条件(孔口),否则其与A2成正比,而不是A。这是因为更宽的管道不只意味着有更多的液体流出,还会使液体流动更快,因为管壁之间存在更大的空间。不过为了本章的学习,我们可以忽略这些技术细节,而是假设生活在一个理想的世界中,在这里压力和速度都是常量,并且吞吐量与横截面面积直接成正比。
利特尔法则和这个简单的体积公式非常相似,这就使得该“管道模型”非常直观有用。让我们更详细地看一下图10.1中的示例(右侧)。假设管道系统表示Scrapy的下载器。第一个非常“细”的下载器,其总体积/并发级别(N)可能是8个并发请求。管道长度/延迟(S)对于一个快速的网站来说,可能S=250ms。在给定N和S时,现在可以计算处理元素的体积/吞吐量,每秒请求数为T = N / S = 8 / 0.25 = 32。
你会发现延迟经常是我们无法控制的,因为它依赖于远端服务器的性能以及网络的延迟。我们比较容易控制的是下载器中并发(N)的级别,可以将其从8增长到16或32个并发请求,即10.1图中的第二个和第三个管道。对于常量的长度(超出我们控制范围之外),可以通过只增加横截面面积的方式增长体积,也就是说增加吞吐量!按照利特尔法则,16个并发请求时,我们得到的每秒请求数为T = N / S = 16 / 0.25 = 64个,而在32个并发请求时,我们得到的每秒请求数是T = N / S = 32 / 0.25 = 128个。太好了!我们似乎可以通过增加并发的方式,使系统无限快。在急于得出这样的结论之前,还需要考虑队列系统级联的影响。
10.1.1 级联队列系统
当将不同横截面面积/吞吐量的几个管道依次连接起来时,可以很直观地理解整个系统的流量将由最窄的(最小吞吐量:T)管道所限制(见图10.2)。
图10.2 不同容量的级联队列系统
你还可以观察到最窄管道(即瓶颈)的位置,决定了其他管道是如何“填满”的。如果考虑到与系统内存需求相关的填充,就会意识到瓶颈的位置是非常重要的。我们最好通过配置保持管道充满,且单个工作单元的花销最少。在Scrapy中,一个工作单元(爬取一个页面)主要是由下载器前的URL(几个字节)以及下载后的URL加上服务器响应(较大)组成。
这就是为什么在Scrapy系统中,通常将瓶颈放置在下载器中。
10.1.2 定义瓶颈
使用管道系统作为类比的一个非常重要的好处是,它在定义瓶颈的过程中更加直观。如果观察图10.2就会发现,“瓶颈”前的所有地方都是满的,而之后的所有地方都不是。
好消息是,在大多数系统中,可以相对容易地使用系统度量监控队列系统是如何填满的。通过仔细检查Scrapy的队列,我们可以了解瓶颈在什么地方,如果发现不在下载器中,则可以调整设置让其变为下载器。没有改善瓶颈的任何改进都不会带来吞吐量的收益。如果修改系统其他部分,只会让事情变得更糟,很有可能将瓶颈转移到别的地方。这个感觉有点像追尾,可能需要很长时间,并且会令你感到绝望。你必须遵循系统方法,定义瓶颈,并且需要在修改任何代码或配置之前,“知道锤子应该击中哪里”。你在大部分例子中(包括本书的大多数例子)可以看到,瓶颈不是总在人们期望的地方出现。
10.1.3 Scrapy性能模型
让我们回到Scrapy,详细看一下其性能模型(见图10.3)。
图10.3 Scrapy性能模型
Scrapy包含如下组成部分。
· 调度器:在这里,多个请求会排队等待下载器处理。它们主要由URL组成,因此会十分紧凑,这就意味着即使拥有大量URL也不会对系统有很大伤害,并且可以让我们在传入不规则请求流的情况下能够充分利用下载器。
· 限流器:这是抓取过程(大储水池)反馈的安全阀,如果正在执行的响应的总计大小超过5MB,那么它会让前往下载器的后续请求停止。这可能会导致不可预料的性能起伏。
· 下载器:这是Scrapy关于性能最重要的组成部分。它对能够并行执行的请求的数量有着复杂的限制。其延迟(管道长度)等于远程服务器响应的时间,加上所有网络/操作系统以及Python/Twisted的延迟。我们可以调整并行请求的数量,不过通常情况下,我们几乎无法控制延迟。下载器的容量由CONCURRENT_REQUESTS*设置限制,我们将会很快看到。
· 爬虫:这是抓取过程中将响应转为Item和后续请求的部分。同时这也是我们编写的部分,通常情况下,只要遵照规则,它们就不会是性能瓶颈。
· Item管道:这是我们编写的代码的第二个部分。我们的爬虫可以对每个请求生成上百个Item,同一时刻只会处理CONCURRENT_ITEMS个。该值十分重要,因为假设你在管道中要处理数据库访问,那么使用默认值(100)就可能会过高,从而在无意间拖垮数据库。
爬虫和管道都应该使用异步代码,并且在必要时引发更多的延迟,但不应因此成为瓶颈。极少情况下,我们的爬虫/管道会处理非常繁重的事情。如果发生此种情况,那么服务器的CPU可能会成为瓶颈。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论