先上一张架构图
spiderman2的事件传递的主要通过queue通道也就是图中的DQ,EQ,RQ;这三个是阻塞队列。Worker来消费队列,Manager来处理任务流向哪个队列
public static void main(String[] args) {
final String xml = "xiaomibbs.xml";
final Config conf = new XMLConfBuilder(xml).build();// 解析xml配置文件通过XMLBuilder构建CONF对象
new Spiderman(conf).go();//启动,别忘记看控制台信息哦,结束之后会有统计信息的
}
创建一个Spiderman
public Spiderman(Config config) {//将配置文件初始化到上下文
this.context = new Context(config);
final Properties params = context.getParams();
this.scheduler = Executors.newSingleThreadScheduledExecutor();
this.managers = context.getManagers();
this.managers.forEach(m -> {
m.addListener(() -> {
counter.plus();
});
});
this.threads = Executors.newFixedThreadPool(managers.size());
duration = K.convertToMillis(params.getString("duration", "0")).longValue();
counter = new Counter(managers.size(), duration);
}
开始执行抓取
public Spiderman go() {
logger.debug("开始行动...");
// 启动各个工头,启动所有的downloadWorker,extractWorker,resultWorker,开始等待阻塞队列的数据
this.managers.forEach(m -> threads.execute(m));
// 调度, 固定一段时间清除种子和一些中间过程任务,重新将种子放入任务队列
final InitialSeeds initSeeds = new InitialSeeds();
final String cron = context.getParams().getString("scheduler.cron");
if (K.isNotBlank(cron)) {
// quartz
} else {
final long period = K.convertToMillis(context.getParams().getString("scheduler.period", "0")).longValue();
if (period > 0) {
this.scheduler.scheduleAtFixedRate(initSeeds, 5000, period, TimeUnit.MILLISECONDS);
} else {
//开始处理种子请求,将种子请求初始化成DownloadTask对象并塞到DQ
initSeeds.execute();
}
}
Thread thread = new Thread(() -> {
// 阻塞等待计数器归0
try {
this.counter.await();
} finally {
if (this.counter.isTimeout()) {
// 若是超时退出,先关闭manager
logger.warn("运行时间["+this.counter.getCost()+"]已经达到或超过设置的最大运行时间[duration="+this.duration+"],将强行停止行动");
this.stop();
} else {
logger.warn("当前采集的结果数["+this.counter.get()+"]已经达到或超过设置的最大数量[worker.result.limit="+this.counter.getLimit()+"],将强行停止行动");
}
this._stop();
}
});
thread.start();
return this;
}