Heritrix1.14源码分析(12) Heritrix的控制中心(大脑)CrawlController

转自:http://blog.csdn.net/jazywoo123/article/details/8223246


CrawlController的确是Heritrix的大脑,在Heritrix中拥有无上的权利!可以控制Heritrix的启动、暂停、停止,也定时进行数据统计、数据汇报和文件管理。同时CrawlController也基本上贯穿整个Heritrix代码,和CrawlURI一样。同时CrawlController纯代码页进2000行,下面就先介绍里面的属性和主要方法,同时对一些灵活用法也加以介绍:

         1.属性:

Java代码   收藏代码
  1. //状态,Checkpoinging:表示正在备份  
  2. private static final Object CHECKPOINTING = "CHECKPOINTING".intern();  
  3. //状态,FINISHED:表示抓取结束  
  4. private static final Object FINISHED = "FINISHED".intern();  
  5. //状态,NASCENT:表示正在生成一个JOB  
  6. private static final Object NASCENT = "NASCENT".intern();  
  7. //状态,PAUSED:表示暂停结束,该状态Heritrix正暂停任何抓取  
  8. private static final Object PAUSED = "PAUSED".intern();  
  9. //状态,PAUSING:表示正在暂停,传达一个暂停命令到每一个线程暂停中间需要时间  
  10. private static final Object PAUSING = "PAUSING".intern();  
  11. //状态,PREPARING:表示抓取结束  
  12. private static final Object PREPARING = "PREPARING".intern();  
  13. //状态,RUNNING:表示正在运行  
  14. private static final Object RUNNING = "RUNNING".intern();  
  15. //状态,STARTED:表示已经启动  
  16. private static final Object STARTED = "STARTED".intern();  
  17. //状态,STOPPING:表示正在停止,传达一个停止命令到每一个线程暂停中间需要时间  
  18. private static final Object STOPPING = "STOPPING".intern();  
  19.   
  20. //当前类的日志管理器  
  21. private final static Logger LOGGER = Logger.getLogger(CrawlController.class  
  22.         .getName());  
  23. // 活动的日志文件名后缀   
  24. public static final String CURRENT_LOG_SUFFIX = ".log";  
  25.   
  26. // 日志crawl.log.txt的文件名  
  27. private static final String LOGNAME_CRAWL = "crawl";  
  28. // 日志local-errors.log.txt的文件名  
  29. private static final String LOGNAME_LOCAL_ERRORS = "local-errors";  
  30. // 日志progress-statistics.log.txt的文件名  
  31. private static final String LOGNAME_PROGRESS_STATISTICS = "progress-statistics";  
  32. // runtime-errors.txt的文件名  
  33. private static final String LOGNAME_RUNTIME_ERRORS = "runtime-errors";  
  34. // 日志uri-errors.txt的文件名  
  35. private static final String LOGNAME_URI_ERRORS = "uri-errors";  
  36.   
  37. // 日志manifest-report的文件名前缀  
  38. public final static String MANIFEST_REPORT = "manifest";  
  39. //processors-report.txt的文件名前缀  
  40. public final static String PROCESSORS_REPORT = "processors";  
  41.   
  42. // crawl-manifest日志文件中中配置文件标签缩写   
  43. public static final char MANIFEST_CONFIG_FILE = 'C';  
  44. // crawl-manifest日志文件中中日志文件标签缩写   
  45. public static final char MANIFEST_LOG_FILE = 'L';  
  46. // crawl-manifest日志文件中中报告文件标签缩写   
  47. public static final char MANIFEST_REPORT_FILE = 'R';  
  48.   
  49. //报告文件名数组  
  50. protected final static String[] REPORTS = { PROCESSORS_REPORT,  
  51.         MANIFEST_REPORT };  
  52. //应急内存,当内存不够时Heritrix会释放这个内存去做一些紧急动作如数据备份  
  53. private static final int RESERVE_BLOCK_SIZE = 6 * 2 ^ 20// 6MB  
  54. private static final int RESERVE_BLOCKS = 1;  
  55.       
  56. //BDB数据库,Heritrix自己封装  
  57. private transient EnhancedEnvironment bdbEnvironment = null;  
  58. //用于Checkpoint备份,存储需要备份的数据  
  59. private transient Map<String, CachedBdbMap<?, ?>> bigmaps = null;  
  60. //备份器  
  61. private Checkpointer checkpointer;  
  62. //备份对象  
  63. private transient Checkpoint checkpointRecover = null;  
  64.   
  65. //备份目录  
  66. private transient File checkpointsDisk;  
  67. //整个Heritrix目录  
  68. private transient File disk;  
  69. //日志文件目录  
  70. private transient File logsDisk;   
  71. //scratch文件  
  72. private transient File scratchDisk;  
  73. //BDB数据库文件  
  74. private transient File stateDisk;  
  75.   
  76. //日志处理器跟文件处理器关联  
  77. transient private Map<Logger, FileHandler> fileHandlers;  
  78. //调度器  
  79. private transient Frontier frontier;  
  80.   
  81. // 日志处理器,关联local-errors.log  
  82. public transient Logger localErrors;  
  83. // 日志处理器,关联 progress-statistics.log  
  84. private transient Logger progressStats;  
  85. //日志处理器,关联报告文件  
  86. public transient Logger reports;  
  87. // 日志处理器,关联runtime-errors.log  
  88. public transient Logger runtimeErrors;  
  89. // 日志处理器,关联uri-Errors.log  
  90. public transient Logger uriErrors;  
  91. // 日志处理器,关联crawl.log  
  92. public transient Logger uriProcessing;   
  93.   
  94. //记录Hertrix创建的日志文件名  
  95. private StringBuffer manifest;  
  96.   
  97. //最大字节数,来源于配置文件  
  98. private long maxBytes; //   
  99. //抓取限制, 最大文档数,来源于配置文件  
  100. private long maxDocument;  
  101. // 抓取限制,最大时间,来源于配置文件  
  102. private long maxTime;   
  103. //管理order.xml  
  104. private transient CrawlOrder order;  
  105. //处理器链  
  106. private transient ProcessorChainList processorChains;  
  107.   
  108. //事件监听器,比如正在运行、停止  
  109. private transient List<CrawlStatusListener> registeredCrawlStatusListeners = Collections  
  110.         .synchronizedList(new ArrayList<CrawlStatusListener>());  
  111. //抓取状态监听器,这里监听哪些URl被忽略,哪些URL抓取失败等  
  112. private transient CrawlURIDispositionListener registeredCrawlURIDispositionListener;  
  113.   
  114. //抓取状态监听器数据  
  115. protected transient ArrayList<CrawlURIDispositionListener> registeredCrawlURIDispositionListeners;  
  116.   
  117. // 应急储备内存  
  118. private transient LinkedList<char[]> reserveMemory;  
  119.   
  120. //抓取范围管理  
  121. private transient CrawlScope scope;  
  122.   
  123. // CrawlServer和CrawlHost的缓存  
  124. private transient ServerCache serverCache;   
  125.   
  126. //配置文件,如order.xml  
  127. private transient SettingsHandler settingsHandler;  
  128. //Heritrix状态,表示已经存在  
  129. private transient String sExit;  
  130.   
  131. // 锁,控制同时只能一个线程运行使用本类  
  132. private transient ReentrantLock singleThreadLock = null;  
  133.   
  134. //是否是单线程模式  
  135. private volatile transient boolean singleThreadMode = false;  
  136. // 表示当前爬虫状态,新生的  
  137. transient private Object state = NASCENT;   
  138.   
  139. // 统计跟踪器  
  140. protected StatisticsTracking statistics = null;   
  141. //线程池  
  142. private transient ToePool toePool;  

 

同时属性中有三个地方需要补充下:

     1)"CHECKPOINTING".intern(); 为什么采用intern()方法?知道intern()方法的人都知道,intern在创建String对象时会先无内存里查看有没有该对象,有的话直接返回,没有则重新创建。而普通的new一般都是直接创建对象,如此在一定程序上可以节省开销

     2)transient LinkedList<char[]> reserveMemory;应急内存。Heritrix在初始化的时候会先占用一部分内存,这里是6M。当发生内存溢出的时候则释放这部分内存,然后做一些日志、报告方面的操作

     3)private transient ReentrantLock singleThreadLock,重入锁.大脑只能有一个,所以需要用这个来保证一个大脑的存在,而不是多个。这里为什么不用单例模式来取代,而采用这种方法?我这里没有用单例模式和这种方法进行实验比较,但直觉上告诉我,由于Heritrix是个多线程爬虫,并且可以同时有多个抓取Job,但同时只能有一个job运行。单例模式的synchronized不能保证当一个job发生线程中断时,其他job可以获得CrawlController的锁来运行他们的抓取,因为synchronized会一直锁住CrawlController对象.而使用ReentrantLock则可以做到这一点...我的想法,欢迎大家拍砖...

  上一篇博客主要介绍了CrawlController的各个属性,博客地址:http://guoyunsky.iteye.com/blog/650744 ,以及三个特殊的属性.这里就介绍它的相关方法,首先从初始化开始介绍,主要请看代码以及注释:

         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,你也可以通过这些方法调试一步步深入下去...


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值