之前写的一个爬虫下载部分使用HttpClient,效率相当不敢恭维,最近打算使用NIO实现该部分,记录一下设计及实现过程中遇到的的问题。
一.基本思路
以下为可能会犯的错误
1.连接到同一服务器的SocketChannel数量过多
看到很多人用SocketChannel模拟Http请求实现方式几乎都是为每一个域名下的URL注册一个SocketChannel,这种方式有一个弊端,不论从横向(多域名)还是纵向(同一域名),SocketChannel的总量不好控制,这就使我们很难在带宽与下载效率上进行权衡。
2.频繁的打开、关闭SocketChannel
我们很期望从本机到远程服务器能够建立几个相对稳定的长连接,以减少频繁打开、关闭Socket连接的资源消耗,但是还有另外一个问题:我们需要礼貌爬取,即使你选择"疯狂"爬取,也要考虑远程服务器强制关闭Socket连接的问题。权衡一下,我更倾向于使Socket可以保持一定时间的连接(可以叫做半长连接),打开一定时间的Socket连接在完成最后一次下载任务后就要关闭。
综合以上两个因素,我选择为同一域名的URL注册一定数量的(可配置,以适应下载服务器的性能与带宽的变动)Socket连接,以该值为阀值并辅以Socket连接服务时长对Socket连接数量进行横向与纵向的控制。
二.设计
主要结构如下图所示:
其中IURLRegister与IFetchedURLHandler分别供爬行模块与文件持久化模块调用。
三.主要接口及实现类代码
package com.crazygeeker.search.fetcher;
/**
* A service designed for download remote file.
* <p>Provide basic operations such as <tt>start</tt><tt>shutdown</tt></p>
* @author JohnWang
* @version 1.0
* @date 2013-11-11
*/
public interface IFetcher {
void start();
void shutdown();
}
package com.crazygeeker.search.fetcher;
import com.crazygeeker.search.common.model.URLBean;
/**
* register the url waiting for downloading
*
* @author JohnWang
* @version 1.0
* @date 2013-11-11
*/
public interface IURLBeanRegister {
/**
*
* @param urlBean
* @return
*/
boolean register(URLBean urlBean);
}
package com.crazygeeker.search.fetcher;
import com.crazygeeker.search.common.model.URLBean;
/**
* An interface for data upload
* @author JohnWang
* @version 1.0
* @date 2013-11-11
*/
public interface IFetchedURLHandler {
URLBean getURLBean();
}
package com.crazygeeker.search.fetcher;
import java.io.IOException;
import java.nio.channels.Selector;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import com.crazygeeker.search.common.model.URLBean;
import com.crazygeeker.search.common.utils.LoggerUtils;
/**
*
* @author JohnWang
* @version 1.0
* @date 2013-11-11
*/
public abstract class AbstractNIOFetcher implements IFetcher {
protected Selector selector;
/**
*
*/
protected AtomicBoolean isRunning = new AtomicBoolean(false);
/**
*
*/
protected BlockingQueue<URLBean> pendingQueue = new PriorityBlockingQueue<URLBean>();
/**
*
*/
protected BlockingQueue<URLBean> fetchedQueue = new LinkedBlockingQueue<URLBean>();
/**
*
*/
public void start() {
try {
selector = Selector.open();
} catch (IOException e) {
e.printStackTrace();
LoggerUtils.fetcherLogger.error("error.cannot_open_selector",e);
}
isRunning.set(true);
LoggerUtils.fetcherLogger.info("selector has bean opened");
service();
}
protected abstract void service();
/**
*
*/
public void shutdown(){
try {
selector.close();
} catch (IOException e) {
e.printStackTrace();
LoggerUtils.fetcherLogger.error("error.cannot_close_selector",e);
}
LoggerUtils.fetcherLogger.info("selector has bean closed");
}
}