Heritrix源碼分析(十三) Heritrix的控制中心(大腦)CrawlController(二)

[color=red]轉自:http://guoyunsky.iteye.com/blog/650744[/color]

1.Heritrix的初始化:

/** * 初始化CrawlController * @param sH 配置文件(order.xml)對象 * @throws InitializationException 初始化異常 */
public void initialize(SettingsHandler sH) throws InitializationException { // 給監聽器發送狀態爲準備狀態 sendCrawlStateChangeEvent(PREPARING, CrawlJob.STATUS_PREPARING); // 重入鎖,保證只有1個線程使用本對象
this.singleThreadLock = new ReentrantLock();
this.settingsHandler = sH;//order.xml對象 // 工具類方法,把爬蟲的SettingsHandler填充進本線程global持有人,如此使得接下來本線程的反序列化操作能夠找到它
installThreadContextSettingsHandler();
this.order = settingsHandler.getOrder();//獲得order.xml管理對象 this.order.setController(this);//設置order.xml由當前控制中心控制
this.bigmaps = new Hashtable<String, CachedBdbMap<?, ?>>();//初始化備份中心數據裝載器
sExit = "";
this.manifest = new StringBuffer();// 初始化所有日誌名記錄器
String onFailMessage = "";
try {
onFailMessage = "You must set the User-Agent and From HTTP" + " header values to acceptable strings. \n" + " User-Agent: [software-name](+[info-url])[misc]\n" + " From: [email-address]\n"; order.checkUserAgentAndFrom();// 檢查user-agent,主要通過正則表達式進行驗證
onFailMessage = "Unable to setup disk";
if (disk == null) { setupDisk();// 創建logs、states、checkpoint等路徑(這裏只是創建這幾個文件夾)
}
onFailMessage = "Unable to create log file(s)";
setupLogs();// 創建日誌文件,並且設置好各種日誌文件格式
onFailMessage = "Unable to test/run checkpoint recover"; this.checkpointRecover = getCheckpointRecover();//獲得備份恢復器
if (this.checkpointRecover == null) {//如果備份恢復器爲空則新建一個 this.checkpointer = new Checkpointer(this, this.checkpointsDisk); } else {
//如果不爲空,則先對備份數據進行恢復,主要是構造備份定時器以及填充備份數據到調度中心
setupCheckpointRecover(); } // 創建DBD環境,這裏只創建BDB數據庫環境 onFailMessage = "Unable to setup bdb environment."; setupBdb(); // 創建跟蹤統計器
onFailMessage = "Unable to setup statistics"; setupStatTracking(); // 初始化了Scope、Frontier、ServerCache以及ProcessorChain
onFailMessage = "Unable to setup crawl modules"; setupCrawlModules();
} catch (Exception e) {
String tmp = "On crawl: " + settingsHandler.getSettingsObject(null).getName() + " " + onFailMessage; //異常日誌 LOGGER.log(Level.SEVERE, tmp, e);
throw new InitializationException(tmp, e); } // 創建DNS緩存
Lookup.getDefaultCache(DClass.IN).setMaxEntries(1); // 設置線程池,從配置文件獲取線程個數後初始化
setupToePool(); // 設置基於crawl order的最大字節數,文檔樹和時間,這些都是從配置文件中獲取
setThresholds(); // 設置應急處理內存,這裏是6M
reserveMemory = new LinkedList<char[]>();
for (int i = 1; i < RESERVE_BLOCKS; i++) { reserveMemory.add(new char[RESERVE_BLOCK_SIZE]);
}
}


CrawlController的初始化實際上是創建與之相關的各個不見,如日誌文件、BDB、處理器、統計跟蹤器等。其中日誌文件用於記錄各個日誌、BDB是個嵌入式數據庫用於存放URL以及備份數據、處理器之前的博客有介紹、統計跟蹤器則主要用來統計跟蹤抓取情況,如多少URL被抓取等。由於這些並不複雜,裏就不再陳述...


2.Heritrix的啓動:
 

/** * 啓動Heritrix,開始抓取 */
public void requestCrawlStart() { //初始化各種處理器 runProcessorInitialTasks(); // 將狀態置爲就緒狀態(會發給每一個事件監聽器) sendCrawlStateChangeEvent(STARTED, CrawlJob.STATUS_PENDING);
String jobState; state = RUNNING;
jobState = CrawlJob.STATUS_RUNNING; // 將狀態置爲正在運行狀態(會發給每一個事件監聽器,並記錄日誌)
sendCrawlStateChangeEvent(this.state, jobState); //存在狀態 this.sExit = CrawlJob.STATUS_FINISHED_ABNORMAL; //初始化統計監聽器線程 Thread statLogger = new Thread(statistics); statLogger.setName("StatLogger"); //啓動統計監聽器 statLogger.start();
//啓動調度器
frontier.start(); }

啓動Heritrix的代碼相對較少.可以看見這裏主要是初始化處理器,以及發送狀態,同時開啓統計監聽器線程。最後啓動調度器,喚醒所有等待線程。如此開始進入抓取。


3.Heritrix的暫停
/** * Stop the crawl temporarly. * 暫停抓取 */ 
public synchronized void requestCrawlPause() {
if (state == PAUSING || state == PAUSED) {
// Already about to pause return;
}
sExit = CrawlJob.STATUS_WAITING_FOR_PAUSE;// 設置退出狀態
frontier.pause();
sendCrawlStateChangeEvent(PAUSING, this.sExit);// 發送狀態
if (toePool.getActiveToeCount() == 0) {// 獲取出於活動狀態的線程數,如果數字爲0,則表示暫停結束 //
if all threads already held, complete pause now // (no chance to trigger off later held thread)
completePause();
}
}


可以看到Heritrix的暫停實際上是線程的暫停,不過每一個多線程應用的暫停也都是線程的暫停吧。Heritrix首先暫停調度器,如此使得抓取線程無法獲取URL,然後發送暫停命令,使得每個處理器接受到暫停命令後暫停各自的處理工作。最後查看線程池中是否還有活動狀態線程,沒有的話則表明暫停完成,這些都可以在UI界面中查看到。


4.Heritrix的重啓
 /** * Resume crawl from paused state * 從暫停狀態恢復抓取 */
public synchronized void requestCrawlResume() {
if (state != PAUSING && state != PAUSED && state != CHECKPOINTING) {// 不是暫停火車checkpoing狀態 // Can't resume if not been told to pause or if we're in middle of // a checkpoint. return;
} multiThreadMode();// 回到多線程模式
frontier.unpause();// frontier取消暫停,這意味着重新開始抓取任務 LOGGER.fine("Crawl resumed.");
sendCrawlStateChangeEvent(RUNNING, CrawlJob.STATUS_RUNNING);// 發送事件,讓所有環節重新啓動 }

通過註釋可以看到,從暫停狀態重啓Heritrix實際上是單線程切換到多線程,然後調度器首先要重啓,最後也是發送命令給所有處理器讓他們重啓

5.Heritrix的停止
由於Heritrix有兩種停止方式,一種是強制終止,一般是接收WEB UI命令後的強行終止抓取,這時的抓取獲取並沒有抓取完成。還有一種是自然停止,也就是所有的URL都抓取完畢。所以前一種停止是被謀殺,而後一種是壽寢正終。下面先介紹下強制終止:

       /** * 開始停止抓取 */ 
public void beginCrawlStop() {
LOGGER.fine("Started."); //發送停止命令
sendCrawlStateChangeEvent(STOPPING, this.sExit);
if (this.frontier != null) {
this.frontier.terminate();
this.frontier.unpause();
}
LOGGER.fine("Finished."); }







可以看到和暫停、重啓基本雷同。也是調度器的停止和發送停止命令給各個處理器。只不過這裏是先要停止各個處理器,然後再停止調度器。只不過這裏後面還多了個調度器的重啓,有些讓人費解。這裏我說下自己的理解,由於Heritrix停止後允許讓Heritrix暫停,可以通過配置命令配置,所以這裏並沒有做到大家想象中的那種停止,而是讓Heritrix的調度中心啓動在那,而所有的處理模塊卻都停止了,實際上還是無法進行任何抓取。


然後是Heritrix的正常停止
/** * Called when the last toethread exits. * 當沒有活動狀態的線程,則爬蟲終止 * */ 
protected void completeStop() {
LOGGER.fine("Entered complete stop.");
runProcessorFinalTasks();// 運行所有處理器的最後處理環節 // Ok, now we are ready to exit. sendCrawlStateChangeEvent(FINISHED, this.sExit); // 發送事件表明爬蟲結束 synchronized (this.registeredCrawlStatusListeners) { this.registeredCrawlStatusListeners .removeAll(this.registeredCrawlStatusListeners);//移除所有的監聽事件 this.registeredCrawlStatusListeners = null;
}
closeLogFiles();// 關閉所有的日誌處理器 // Release reference to logger file handler instances.
this.fileHandlers = null;
this.uriErrors = null;
this.uriProcessing = null;
this.localErrors = null;
this.runtimeErrors = null;
this.progressStats = null;
this.reports = null;
this.manifest = null; // Do cleanup. this.statistics = null; this.frontier = null;
this.disk = null;
this.scratchDisk = null;
this.order = null;
this.scope = null;
if (this.settingsHandler != null) {
this.settingsHandler.cleanup();
}
this.settingsHandler = null;
this.reserveMemory = null;
this.processorChains = null;
if (this.serverCache != null) {
this.serverCache.cleanup();
this.serverCache = null;
}
if (this.checkpointer != null) { // 關閉Checkpointer,這裏是關閉CheckPointer定時器
this.checkpointer.cleanup();
this.checkpointer = null;
}
if (this.bdbEnvironment != null) { // 關閉BDB數據庫 try { this.bdbEnvironment.sync();
this.bdbEnvironment.close();
} catch (DatabaseException e) {
e.printStackTrace();
}
this.bdbEnvironment = null;
}
this.bigmaps = null;
if (this.toePool != null) {
this.toePool.cleanup();// 清理線程 }
this.toePool = null; LOGGER.fine("Finished crawl."); }


可以看到這段代碼是一個清理的過程,同時觸發這個方法的緣由是因爲沒有活動狀態的線程.之前說過當Heritrix中的抓取線程ToeThread沒有URL可抓取的時候就會自然死亡(不處於活動狀態),當沒有URL可抓取也就是意味着沒有處於活動狀態的線程,所以就會觸發這個方法,導致抓取的結束。





6.總結:

以上只是介紹了主要的方法和屬性,其中還有一些重要的組件沒有介紹,如Checkpoint,如果不去看源碼可能根本不會注意到Checkpoint。不過以上的介紹大概說明了下Heritrix,你也可以通過這些方法調試一步步深入下去...
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章