在搞定了ActiveMQ的 性能/内存泄露 问题之后,我的重心转移到了网页链接提取这一块。
在详细说之前,需要说一下网络爬虫链接抽取的一个最基础原则:

为了避免对一个URL重复的访问,需要维护一个URL总库,对于不是超大型的应用而言,应该是一个独立的库。(没有接触过真实搜索引擎的实现,他们不大能是只有一个总库的)

这个库维护的是对于下载过或发现了的链接

那么,对于链接的抽取,主要就有以下任务:

  1. 从页面中抽取出链接【集合】
  2. 遍历链接集合,判断每一个链接是否在之前已经发现过了,或者下载过,近期不再会下载的。过滤掉一些链接
  3. 将剩下的链接存储入库

项目里选用的URL总库存储方式是Lucene,于是问题是:没有办法并发的读写。若不加以控制,类似于ObtainLockException的异常会频繁的遇到。
我的处理方法是加了一层数据库,用来做Lucene的Cache,然后另起一个服务,不断的把Cache里的数据Flush处理进Lucene。

看似问题解决了,但是速度还是不行,多个线程多台机器并发的写,获取MySQL的锁依然费时。加上synchronized关键字也一样。

这里又涉及到了一点业务需求,我们的系统是用来做文本分析的,最后还得往另一个队列继续放消息。

也就是说,因为判断链接、过滤链接这个环节让整个体系都慢了下来。
我一个人在这个问题上折腾了近一周(主要是算法),包括和领导讨论、请教,依然没搞定。。。

最后的解决方法,并不是在程序的处理算法上进行神马优化。而是对整个处理逻辑的更换(跳出既有思维):

其实最后判断一个URL是否需要生产文本分析任务,并不依赖于抽出了多少链接,而是其他的逻辑(纯算法判断,不涉及IO操作),既然如此,根本就没有必要非得在链接抽取完毕再生产此任务。那么也就是,把原来在一个大方法里做的两件事情分成在两个完全独立的地方做,多一种消息类型而已。

用了这个方法之后,原来我为了速度而在多台机器上部署的链接抽取代码,都用不着了。只需要单机器,线程稍微多开两个。

因为对于链接抽取来说,他需要保证的,有且只有一条:保证找到链接的速度大于爬取模块下载页面的速度,就可以了。
维护一个1000W未下载的链接库和维护一个10W的库,对于爬取模块来说没有区别。

把这个经历写出来,其实真是没有什么技术含量的,但是呢,比技术更重要的,是解决问题的思路。
所谓【大巧若拙】便是如此:看起来没什么牛逼的东西,可问题就是实实在在的搞定了。

关于用Lucene存储的链接库,我正在做的是分库的重构。基本算法是维护一组Directory的Hash,注意锁、更新时间是否需要重开Reader等等的细节问题,大算法上我觉得并无太值得一说的。虽然写起来还是有些费时。。。

anyShare分享到:
          

没准儿您会对以下内容感兴趣: