鱼喃

听!布鲁布鲁,大鱼又在那叨叨了

StreamSpider 测试与分析

测试是软件开发中重要的一环,对于分布式系统来说更是如此。通过测试,既是检查程序是否按照预期运行,又是对系统的性能进行测试,只有通过测试才能发现静态代码分析不能找到的问题。分布式爬虫系统编码完毕之后就是在已经搭建好的平台上进行测试,通过长时间的运行并观察系统状态,继而分析系统存在的缺陷的不足。

测试参数说明

进行了多次测试,每次的参数基本都相同。

由于整个互联网内容过于庞大,依靠实验环境远远无法满足要求,并且考虑到教育网内速度较快,故将抓取范围限制在中国教育网。添加模式 https?://[a-z0-9A-Z.-]+.edu.cn.*,并且设置参数 limitation=300interval=300parallelism=5,每个站点最多分配5个爬虫线程并且在300s内单个站点最大抓取次数不超过300,网页超时时间留空,使用默认值7d。

系统设置方面,负责从待抓取队列选择URL的url-reader线程数目为1,负责调度的url-filter线程数目为3,下载模块downloader线程数目为100,执行网页解析并提取新URL的url-parser线程数目为1,将网页文本发送的RabbitMQ的html-saver线程数目为1,负责将新链接加入到待抓取队列的url-saver线程数目为3。使用4个storm worker,并设置maxSpoutPending为5000以此限制系统最大等待队列的长度。

1
2
3
4
5
6
7
8
9
10
conf.setMaxSpoutPending(5000);
conf.setNumWorkers(4);
conf.setMessageTimeoutSecs(60);

int url_reader_parallelism = 1;
int url_filter_parallelism = 3;
int downloader_parallelism = 100;
int html_saver_parallelism = 1;
int html_parser_parallelism = 1;
int url_saver_parallelism = 3;

另外,为了模拟实际运行时环境,编写了一个单线程的消息队列消费者,负责从消息队列RabbitMQ中接收网页数据并插入到文档数据库Mongodb中。插入的过程是先检查对应URL的记录是否存在,如果存在则更新网页数据,否则插入一条新的记录。基本结构是{url, html, time, charset}。由于记录数量较多,查找费时,给url加上索引。因为URL链接的长度不固定,有些长度超过简单索引的长度限制,所以采用将URL哈希后再索引的方式。

1
2
3
use StreamSpider
db.createCollection("pages", {})
db.pages.ensureIndex({"url":"hashed"})

测试步骤如下:
1、清理原有系统的状态信息,包括redis数据库中的待抓取队列。
2、打开web UI,清理原有模式,添加模式并自定义相关参数限制
3、删除mongodb中对应的集合pages,重新创建集合并建立索引
4、在nimbus节点提交storm拓扑,启动系统
5、添加清华官网首页作为种子链接
6、观察系统运行状态

运行状态及数据

在最后一次测试中,系统一共持续运行了接近48h。调度器分发下载任务8997861个,实际下载网页8097229个,中间的差值主要由于网页不存在或无法访问导致。停止运行时,待抓取队列中长度为7675555。一共涉及到的站点是1301354个,经过检查发现由于模式串的正则表达式不够严格,导致大量非教育网的站点被系统抓取,在对数据进行清洗后确认实际的教务网站点86345个。mongodb数据库文件大小为90GB。除了redis数据库外,集群其他节点CPU和内存占用率在可接受范围内。

2a60f347748832eb765fc7ab16419e66.png

720e5a4c8682641cd08e3684ec070b10.png

系统运行24小时内的抓取速度

58f9c32bb78d58cc3570c2bdfaccb4e6.png

停止运行时的待抓取队列前10条链接

f667aa6df55be9697f30f7bdc4451deb.png

f9c84a7e969550e6bbcf36591a7ee69c.png

分析

下载速度

通过RabbitMQ运行时数据来看,系统在启动后平均每秒下载速度快速上升到100pps (Page per second),然后保持在50100之间。从storm topology的统计数据来看,单页面下载时间约为500ms,而系统总下载线程数目是100个,也就是说理论上系统每秒最大下载网页数目可达到 100*1/0.5=200pps。但是从实际统计数据来看,系统基本保持在50100pps之间,也就是说不到理论值的一半。结合日志分析,其中损失的主要由错误的网页导致。

1、从调度器分发量和下载线程实际下载量的统计结果来看,约有11%的网页没有被成功下载
2、重试机制,每一个网页最多可能被执行下载两次,按照2次计算
3、错误网页消耗的时间远超过平均下载时间,连接超时30s,下载超时15s,域名无解析约几十毫秒,资源不存在约0.5s。

一般情况下,下载超时的问题不存在,另外域名无解析消耗的时间可以近似看作忽略不计,那么剩下的两项就是连接超时和资源不存在。

连接超时所占比例大约在10%,资源不存在所占比例大约在90%。也就是说每个错误网页消耗的时间是 2*(10% * 30s + 90% * 0.5s) = 6.90s, 而正常的网页消耗是 0.5s。那么计算可以得到正常网页所消耗的资源占比是 (89*0.5)/(11% * 6.90 + 89% * 0.5) = 45.5%/121.4=37.47%37.47% * 200 = 75 (pps),大致符合实际情况。

速度周期性变化

另外,统计信息显示,下载速度呈现周期性变化,约4个小时为一个周期,周期内一半时间速度达到100pps,另一半时间速度为75pps。猜测是虚拟机垃圾回收、redis持久化等因素带来的性能下降。由于缺少详细的数据,无法作出准确判断。

系统停止

程序运行了约48小时候下载速度降为0。检查之后发现redis所在服务器内存和CPU占用异常,内存占用率更是接近100%,尝试连接redis发现连接被拒绝。分析是开启了aof持久化,当数据更新达到一定数目后,redis就会fork出另一个线程,执行持久化操作。由于数据量太大,redis主线程和持久化线程消耗了服务器接近所有的资源,使得redis性能急速下降甚至开始拒绝服务。停止爬虫系统,并等待大约一天后,redis持久化完毕。连接检查数据,发现待抓取队列大小已经达到七百多万,而且用于去重使用的键也达到了近900万,再加上其他部分,4G的内存被完全占用。

泛解析问题

此外,在检查数据时,发现由于正则表达式书写不严格,导致部分域名被漏过,再加上某一恶意域名开启了泛解析模式,且随机生成子域名,导致系统基于域名的限制失效。

数据分析

此外,在爬虫运行过程中,进行了了一些观察。系统运行后约十分钟爬取到吉林大学服务器,相关域名是tv.jlu.edu.cn,但是开始爬取校内办公网oa.jlu.edu.cn却花了约两个小时的时间。检查日志,发现关于该站点的链接都是404错误。原因在于近期网站改版之后没有考虑到seo的问题,旧版网站的链接没有做相关的跳转,并且新版的网址比较混乱,如https://oa.jlu.edu.cn/defaultroot/PortalInformation!getInformation.action?title=吉林大学关于推荐吉林省第四届学科评议组成员候选人的通知&id=3749583&channelName=校内通知&fromflag=login&channelId=179577&orgname=研究生院,很多网站在转发时,链接解析错误,进一步加大了爬虫系统发现站点的难度。(截至撰写论文的时候,网站已经更新,将未知链接统一跳转到首页。)