Frontier是Heritrix最核心的组成部分之一,也是最复杂的组成部分.它主要功能是为处理链接的线程提供URL,并负责链接处理完成后的一些后续调度操作.并且为了提高效率,它在内部使用了Berkeley DB.本节将对它的内部机理进行详细解剖.

在Heritrix的官方文档上有一个Frontier的例子,虽然很简单,但是它却解释Frontier实现的基本原理.在这里就不讨论,有兴趣的读者可以参考相应文档.但是不得不提它的三个核心方法:
(1)next(int timeout):为处理线程提供一个链接.Heritrix的所有处理线程(ToeThread)都是通过调用该方法获取链接的.

(2)schedule(CandidateURI caURI):调度待处理的链接.

(3)finished(CrawlURI cURI):完成一个已处理的链接.

整体结构如下:Frontier图

(1)BdbFrontier链接工厂

initQueue()初始化等待队列,继承了WorkQueueFrontier,是Heritrix唯一个具有实际意义的链接工厂.

 
  
  1. package org.archive.crawler.frontier; 
  2. public class BdbFrontier extends WorkQueueFrontier implements Serializable  
  3.     /** 所有待抓取的链接*/ 
  4.     protected transient BdbMultipleWorkQueues pendingUris; 
  5.  
  6.     //初始化pendingUris,父类为抽象方法 
  7.     protected void initQueue() throws IOException { 
  8.         try { 
  9.             this.pendingUris = createMultipleWorkQueues(); 
  10.         } catch(DatabaseException e) { 
  11.             throw (IOException)new IOException(e.getMessage()).initCause(e); 
  12.         } 
  13.     } 
  14.  
  15.    private BdbMultipleWorkQueues createMultipleWorkQueues() 
  16.     throws DatabaseException { 
  17.         return new BdbMultipleWorkQueues(this.controller.getBdbEnvironment(), 
  18.             this.controller.getBdbEnvironment().getClassCatalog(), 
  19.             this.controller.isCheckpointRecover()); 
  20.     } 
  21.     protected BdbMultipleWorkQueues getWorkQueues() { 
  22.         return pendingUris; 
  23.     } 
  24. .............................. 

 

(2)next():为处理线程提供一个链接.Heritrix的所有处理线程(ToeThread)都是通过调用该方法获取链接的.

就是WorkQueueFrontiernext()方法。

*说明:WorkQueueFrontier的next方法实际是调用WorkQueue的peek()方法,WorkQueue的peek()方法又由BdbWorkQueue的peekItem()来实现,BdbWorkQueue的peekItem()方法又调用BdbFrontier的getWorkQueues()方法拿到BdbMultipleWorkQueues队列也就是等待队列,在调用BdbMultipleWorkQueues的get()方法调用getNextNearestItem()方法从等待队列中拿出链接并加入正在处理队列。

 
  
  1. public CrawlURI next() 
  2.     throws InterruptedException, EndedException { 
  3.         while (true) { // loop left only by explicit return or exception 
  4.             long now = System.currentTimeMillis(); 
  5.  
  6.             // Do common checks for pause, terminate, bandwidth-hold 
  7.             preNext(now); 
  8.              
  9.             synchronized(readyClassQueues) { 
  10.                 int activationsNeeded = targetSizeForReadyQueues() - readyClassQueues.size(); 
  11.                 while(activationsNeeded > 0 && !inactiveQueues.isEmpty()) { 
  12.                     activateInactiveQueue(); 
  13.                     activationsNeeded--; 
  14.                 } 
  15.             } 
  16.                     
  17.             WorkQueue readyQ = null
  18.             Object key = readyClassQueues.poll(DEFAULT_WAIT,TimeUnit.MILLISECONDS); 
  19.             if (key != null) { 
  20.                 readyQ = (WorkQueue)this.allQueues.get(key); 
  21.             } 
  22.             if (readyQ != null) { 
  23.                 while(true) { // loop left by explicit return or break on empty 
  24.                     CrawlURI curi = null
  25.                     synchronized(readyQ) { 
  26.                         /**取出一个URL,最终从子类BdbFrontier的 
  27.                          * pendingUris中取出一个链接 
  28.                          */ 
  29.                         curi = readyQ.peek(this);                      
  30.                         if (curi != null) { 
  31.                             // check if curi belongs in different queue 
  32.                             String currentQueueKey = getClassKey(curi); 
  33.                             if (currentQueueKey.equals(curi.getClassKey())) { 
  34.                                 // curi was in right queue, emit 
  35.                                 noteAboutToEmit(curi, readyQ); 
  36.                                 //加入正在处理队列中 
  37.                                 inProcessQueues.add(readyQ); 
  38.                                 return curi; //返回 
  39.                             } 
  40.                             // URI's assigned queue has changed since it 
  41.                             // was queued (eg because its IP has become 
  42.                             // known). Requeue to new queue. 
  43.                             curi.setClassKey(currentQueueKey); 
  44.                             readyQ.dequeue(this);//出队列 
  45.                             decrementQueuedCount(1); 
  46.                             curi.setHolderKey(null); 
  47.                             // curi will be requeued to true queue after lock 
  48.                             //  on readyQ is released, to prevent deadlock 
  49.                         } else { 
  50.                             // readyQ is empty and ready: it's exhausted 
  51.                             // release held status, allowing any subsequent  
  52.                             // enqueues to again put queue in ready 
  53.                             readyQ.clearHeld(); 
  54.                             break
  55.                         } 
  56.                     } 
  57.                     if(curi!=null) { 
  58.                         // complete the requeuing begun earlier 
  59.                         sendToQueue(curi); 
  60.                     } 
  61.                 } 
  62.             } else { 
  63.                 // ReadyQ key wasn't in all queues: unexpected 
  64.                 if (key != null) { 
  65.                     logger.severe("Key "+ key + 
  66.                         " in readyClassQueues but not allQueues"); 
  67.                 } 
  68.             } 
  69.  
  70.             if(shouldTerminate) { 
  71.                 // skip subsequent steps if already on last legs 
  72.                 throw new EndedException("shouldTerminate is true"); 
  73.             } 
  74.                  
  75.             if(inProcessQueues.size()==0) { 
  76.                 // Nothing was ready or in progress or imminent to wake; ensure  
  77.                 // any piled-up pending-scheduled URIs are considered 
  78.                 this.alreadyIncluded.requestFlush(); 
  79.             }     
  80.         } 
  81.     } 
  82.  
  83.  
  84. //将URL加入待处理队列 
  85.     public void schedule(CandidateURI caUri) { 
  86.         // Canonicalization may set forceFetch flag.  See 
  87.         // #canonicalization(CandidateURI) javadoc for circumstance. 
  88.         String canon = canonicalize(caUri); 
  89.         if (caUri.forceFetch()) { 
  90.             alreadyIncluded.addForce(canon, caUri); 
  91.         } else { 
  92.             alreadyIncluded.add(canon, caUri); 
  93.         } 
  94.     } 

 

(3)schedule(CandidateURI caURI):将caURI放入等待队列,其实就是BdbMultipleWorkQueues管理的,它是对Berkeley DB的简单封装.在内部有一个Berkeley Database,存放所有待处理的链接.

Berkeley Database数据库,用于存放等待的链接。 BdbWorkQueue:代表一个链接队列,该队列中所有的链接都具有相同的键值.它实际上是通过调用BdbMultipleWorkQueues的get方法从等处理链接数据库中取得一个链接的.


(4)BdbUriUniqFilter:实际上是一个过滤器,它用来检查一个要进入等待队列的链接是否已经被抓取过.


(5)finished(CrawlURI cURI):完成一个已处理的链接.