前面每爬取一个任务都对应一个Job任务,试想一下,当咱们爬取网页愈来愈多,速度愈来愈快时,就会出现频繁的Job对象的建立和销毁,所以本片将考虑如何实现对象的复用,减小频繁的gchtml
咱们的目标是设计一个对象池,用于建立Job任务,基本要求是知足下面几点:java
ObjectFactory
对象池在初始化对象时,借用对象工厂类来建立,实现解耦git
public interface ObjectFactory<T> { T create(); }
IPollCell
由于每一个对象都拥有本身的做用域,内部包含一些成员变量,若是对象重用时,这些成员变量的值,可能会形成影响,所以咱们定义 IPoolCell
接口,其中声明一个方法,用于重置全部的变量信息github
public interface IPoolCell { /** * 清空全部状态 */ void clear(); }
SimplePool
package com.quick.hui.crawler.core.pool; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicInteger; /** * Created by yihui on 2017/8/6. */ @Slf4j public class SimplePool<T extends IPoolCell> { private static SimplePool instance; public static void initInstance(SimplePool simplePool) { instance = simplePool; } public static SimplePool getInstance() { return instance; } private int size; private BlockingQueue<T> queue; private String name; private ObjectFactory objectFactory; private AtomicInteger objCreateCount = new AtomicInteger(0); public SimplePool(int size, ObjectFactory objectFactory) { this(size, "default-pool", objectFactory); } public SimplePool(int size, String name, ObjectFactory objectFactory) { this.size = size; this.name = name; this.objectFactory = objectFactory; queue = new LinkedBlockingQueue<>(size); } /** * 获取对象,若队列中存在, 则直接返回;若不存在,则新建立一个返回 * @return */ public T get() { T obj = queue.poll(); if (obj != null) { return obj; } obj = (T) objectFactory.create(); int num = objCreateCount.addAndGet(1); if (log.isDebugEnabled()) { if (objCreateCount.get() >= size) { log.debug("objectPoll fulled! create a new object! total create num: {}, poll size: {}", num, queue.size()); } else { // fixme 因为并发问题,这个队列的大小实际上与添加对象时的大小不必定相同 log.debug("objectPoll not fulled!, init object, now poll size: {}", queue.size()); } } return obj; } /** * 将对象扔回到队列中 * * @param obj */ public void release(T obj) { obj.clear(); // 非阻塞方式的扔进队列 boolean ans = queue.offer(obj); if (log.isDebugEnabled()) { log.debug("return obj to pool status: {}, now size: {}", ans, queue.size()); } } public void clear() { queue.clear(); } }
上面的方法中,主要看get和release方法,简单说明并发
get 方法app
release 方法异步
既然要使用对象池,那么咱们的IJob对象须要实现 IPoolCell接口了ide
将实现放在 DefaultAbstractCrawlJob
类中测试
@Override public void clear() { this.depth = 0; this.crawlMeta = null; this.fetchQueue = null; this.crawlResult = null; }
上面只是实现了一个最简单的最基础的对象池,接下来就是适配咱们的爬虫系统了fetch
以前的建立Job任务是在 com.quick.hui.crawler.core.fetcher.Fetcher#start
中直接根据传入的class对象来建立对象,所以,第一步就是着手改Fetcher类
建立方法修改,新增对象池对象初始化:Fetcher.java
public <T extends DefaultAbstractCrawlJob> Fetcher(Class<T> jobClz) { this(0, jobClz); } public <T extends DefaultAbstractCrawlJob> Fetcher(int maxDepth, Class<T> jobClz) { this(maxDepth, () -> { try { return jobClz.newInstance(); } catch (Exception e) { log.error("create job error! e: {}", e); return null; } }); } public <T extends DefaultAbstractCrawlJob> Fetcher(int maxDepth, ObjectFactory<T> jobFactory) { this.maxDepth = maxDepth; fetchQueue = FetchQueue.DEFAULT_INSTANCE; threadConf = ThreadConf.DEFAULT_CONF; initExecutor(); SimplePool simplePool = new SimplePool<>(ConfigWrapper.getInstance().getConfig().getFetchQueueSize(), jobFactory); SimplePool.initInstance(simplePool); }
说明
为何将建立的对象池座位 SimplePool的静态变量 ?
由于每一个任务都是异步执行,在任务执行完以后扔回队列,这个过程不在 Fetcher对象中执行,为了共享对象池,采用了这种猥琐的方法
在建立 Fetcher 对象时,已经初始化好对象池,所以start方法不须要接收参数,直接改成
public <T extends DefaultAbstractCrawlJob> void start() throws Exception { .... DefaultAbstractCrawlJob job = (DefaultAbstractCrawlJob) SimplePool.getInstance().get(); job.setDepth(this.maxDepth); job.setCrawlMeta(crawlMeta); job.setFetchQueue(fetchQueue); executor.execute(job); ...
测试代码与以前有一点区别,即 Fetcher 在建立时选择具体的Job对象类型,其余的没啥区别
public static class QueueCrawlerJob extends DefaultAbstractCrawlJob { public void beforeRun() { // 设置返回的网页编码 super.setResponseCode("gbk"); } @Override protected void visit(CrawlResult crawlResult) { // System.out.println(Thread.currentThread().getName() + "___" + crawlMeta.getCurrentDepth() + "___" + crawlResult.getUrl()); } } @Test public void testCrawel() throws Exception { Fetcher fetcher = new Fetcher(2, QueueCrawlerJob.class); String url = "http://chengyu.911cha.com/zishu_4.html"; CrawlMeta crawlMeta = new CrawlMeta(); crawlMeta.setUrl(url); crawlMeta.addPositiveRegex("http://chengyu.911cha.com/zishu_4_p[0-9]+\\.html$"); fetcher.addFeed(crawlMeta); fetcher.start(); }
上面只是实现了一个最基本简单的对象池,有很多能够改进的地方
以上坑留待后续有空进行修改
项目地址: https://github.com/liuyueyi/quick-crawler
对象池对应的tag: v0.008
我的博客:一灰的我的博客
公众号获取更多: