前面每爬取一個任務都對應一個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
我的博客:一灰的我的博客
公衆號獲取更多: