Heritrix中定制Queue-assignment-policy的相关研究

2008-03-31 – 1:36 am

我在开始使用Heritrix时,因为过不了公司的Proxy,只能试着抓取本地的网站。(127.0.0.1)但是发现虽然我设置的最大线程数是50,但是活跃的线程数总是1或者0。开始以为是线程调度出的问题,后来看了书的10.3.2,发现是queue-assignment-policy的问题。 在heritrix中,我设置的50个线程是开始就创建好了的。实际需要抓取的链接都是在BdbMultipleWorkQueues中管理的。每个链接对应一个Key,Key相同的链接在一个Queue中。负责把每一个新发现的需要抓取的链接分配给合适的线程的队列的算法就是queue-assignment-policy。但是系统缺省的Policy算法是HostnameQueue,就是基于链接对应的主机名字来分配。这样对应于同一个主机名称的情况(比如我这种情况)基本所有的链接都被分配给了同一个队列,就永远只有1个或0个线程在工作了。

Heritrx决定这个算法的是org.archive.crawler.frontier.QueueAssignmentPolicy类。这是一个抽象类,其中只有一个函数,

public abstract String getClassKey(CrawlController controller, CandidateURI cauri);

根据链接地址返回一个表示Key的字符串。

对这个问题的第一个反映就是做一个随机算法,把新链接随机放到50个队列里面好了。看了一段Heritrix的代码,发现没有这么简单。例如 HostnameQueueAssignmentPolicy类实际返回的Key是主机名www.hostname.com,实际还是外面的算法根据这个Key再对应到相关的线程队列中。不知道Heritrix对于重复链接的判断是针对某一个Key的还是针对全部已有链接的。如果只针对相同Key进行是否有重复链接的判断,那样随机生成Key的话就会有问题。而且在网上搜索了一下,发现了写书的邱哲和符滔滔的网站的一个帖子,这个问题也没有解决,而且书上说的ELFHash算法也有一定的问题。

先做了一个最简单的实验。把HostnameQueueAssignmentPolicy中的getClassKey函数修改为直接返回全部的URL字符串。马上,可以看到活动线程数到50了,而且总体速度大大加快。看样子这个确实是问题的瓶颈。

再把这个函数改为返回一个0-9之间的随机数看看。速度比前面明显慢了很多,因为从50个线程降到了10个(后来发现不是这样),但是对于判断重复链接好像并没有影响。可以确认Heritrix的重复链接判断与Key没有关系。这样可以放心修改QueueAssignmentPolicy了。

经过对于代码的分析,感觉Heritrix对于这个问题不是很灵活。系统有效的QueueAssignmentPolicy实际是配在 conf目录下的heritrix.properties文件中的。里面有这么一段:

org.archive.crawler.frontier.AbstractFrontier.queue-assignment-policy = \
org.archive.crawler.frontier.HostnameQueueAssignmentPolicy \
org.archive.crawler.frontier.IPQueueAssignmentPolicy \
org.archive.crawler.frontier.BucketQueueAssignmentPolicy \
org.archive.crawler.frontier.SurtAuthorityQueueAssignmentPolicy

将所有可以用的Queue-assignment-policy都配在这里

在 org.archive.crawler.frontier.AbstractFrontier的构造函数中有

String queueStr = System.getProperty(AbstractFrontier.class.getName() +
“.” + ATTR_QUEUE_ASSIGNMENT_POLICY,
HostnameQueueAssignmentPolicy.class.getName() + ” ” +
IPQueueAssignmentPolicy.class.getName() + ” ” +
BucketQueueAssignmentPolicy.class.getName() + ” ” +
SurtAuthorityQueueAssignmentPolicy.class.getName());

这里去读配置文件中配的可用Policy,如果没有,缺省就是下面4个。

在相应Job下面的order.xml中有这个Job使用的Policy的设置

<newObject name=”frontier” class=”org.archive.crawler.frontier.BdbFrontier”>
<float name=”delay-factor”>4.0</float>
<integer name=”max-delay-ms”>20000</integer>
<integer name=”min-delay-ms”>2000</integer>
<integer name=”max-retries”>30</integer>
<long name=”retry-delay-seconds”>900</long>
<integer name=”preference-embed-hops”>1</integer>
<integer name=”total-bandwidth-usage-KB-sec”>0</integer>
<integer name=”max-per-host-bandwidth-usage-KB-sec”>0</integer>
<string name=”queue-assignment-policy”> org.archive.crawler.frontier.HostnameQueueAssignmentPolicy</string>
<string name=”force-queue-assignment”/>

比较麻烦的是我没有找到可以改变这个设置的地方(只能看不能改),只好在生成Job以后手工去改这个配置了。自己做一个QueueAssignmentPolicy,改一下这两个地方的配置,就应该可以使用了。

现在我还发现一个问题,我用随机返回0-49之间Key的办法,但是同时工作的线程多数情况下只有几个(最常见的是1-3个),直接返回URL的情况下一般都是48、49,而且速度比直接返回URI慢很多,说明在外面还有再针对Key的算法来分队列。

继续阅读代码,发现实际线程池在开始的时候就初始化好了。而线程在运行中实际是在等待run函数中的下面一句

CrawlURI curi = controller.getFrontier().next();

在等待从Frontier取得下一个URI。

终于在WorkQueueFrontier(BdbFrontier的父类)的next函数中发现了下面这么一段:

String currentQueueKey = getClassKey(curi);
if (currentQueueKey.equals(curi.getClassKey())) {
// curi was in right queue, emit
noteAboutToEmit(curi, readyQ);
inProcessQueues.add(readyQ);
return curi;
}

看样子是对每一个URI重新调用getClassKey再算一次Key,如果新算出来的Key和生成URI时的Key一致才进行处理。这样我用的随机Key就要反复来碰运气了。

修改自己的算法,改成了累积全部URI字符的ASCII求和再模前线程池大小。重新实验,果然跑得飞快了。

Popularity: 15% [?]

Leave a Reply

You must be logged in to post a comment.