尝试WebMagic+Dubbo搭建爬虫Cluster(更新完毕)

2 篇文章 0 订阅
1 篇文章 0 订阅

目录

  • 现在还没解决的问题
  • 已经解决的问题
  • 大致思路的演化
  • 2017年11月11日开发记录
  • 2017年11月16日开发记录
  • 2017年11月17日开发记录
  • 2017年11月18日开发记录
  • 2017年11月19日开发记录
  • 2017年11月20日开发记录
  • 2017年11月21日开发记录
  • 2017年11月22日开发记录
  • 2017年11月24日开发记录
  • 面试该项目时得到的一些改进的建议

现在还没解决的问题

以下是还没解决的问题,其中有技术问题,还有优化问题。有思路的朋友可以给些建议。

  • 关于“先在子机累积song,累积到1000条,然后拼接sql批量插入。并且在spider.run()结束后,还准备了一个flush方法,把缓冲区中剩余的song但是不满1000条的数据批量插入数据库。”的问题二:由以前的经验得知,这个程序运行的时间越长,很有可能出现程序进入停滞状态(可能是死锁),也不继续往下爬取,也不停止,导致放在spider.run()后的代码执行不了。

  • slave子机在运行到main()方法的结束"}“后(经过调试得知的)还不退出程序,debug的时候到了”}"后,光标就消失了,再按F8就不管用了,原因还不得知。

已经解决的问题

  1. HTTPClient包在webMagic和dubbo中都有,发生冲突

    • 解决方案:用maven的去掉dubbo的HTTPClient和httpcore就好了
  2. task接口没有序列化,导致dubbo不能传输

    • 解决方案:一开始的解决方案是光传输Request,不传输task。后来是自定义一个SerializableTask类实现task接口,使得dubbo传输的是它,在提供者和消费者各自进行装配和拆卸(详情请见2017年11月16日和17日的开发记录)。
  3. 关于“先在子机累积song,累积到1000条,然后拼接sql批量插入。并且在spider.run()结束后,还准备了一个flush方法,把缓冲区中剩余的song但是不满1000条的数据批量插入数据库。”的问题一:如果这一千条批量插入的时候,有一条出现错误(比如某个字段超了),由于这是一个事务,那么MySQL将会回滚,一条都插不进去,导致了程序进入停滞状态(一条新的song都插入不进去)。

    • 解决方案:MySQL数据库方面,设置大文本字段为text;代码方面(为了提高性能,用SpringJDBC),先写SQL = insert into song(name,lyric,singer…除了id所有的字段),然后append()1000条记录,每条记录中如果有空值,则用’_'或者0代替。这样就实现了insertSelective的效果。但是这样还是解决不了根本问题,还是可能插入失败回滚并报错,这样只能减少错误出现。
  4. 插入的数据有重复的。

    • 原因:经过分析后是并发问题,因为我的逻辑是批量插入有一个bufferList,当bufferList没满500时,往bufferList里插入,满了500,就往数据库批量导入。这里强调几点,bufferList是多个线程共同维护的,换句话说,多个线程爬取到的song数据都往bufferList里放。而错误的原因是,当bufferList满了500时,当其中一个线程正在批量导入的时候,另一个线程也正好判断了bufferList.size() > 500,于是也进行了插入,这导致了同一个bufferList插入了多遍。
    • 解决方案:加synchronized代码块。
  5. 我的代码:

    	if (bufferList.size() < 500) {
                bufferList.add(song);
            } else {
                synchronized (this) {
                    try {
    //                    为了性能,用SpringJDBC
                        Date date = new Date();
                        batchInsertSelective(bufferList);
                        Date date1 = new Date();
                        System.out.println("[INFO] 线程\t" + Thread.currentThread().getId() + "\t本次插入所花时间:\t" + (date1.getTime() - date.getTime()));
                        bufferList.clear();
                        objectCount += bufferList.size();
                    } catch (Exception e) {
                        e.printStackTrace();
                        logger.error(e.getMessage());
                    }
                }
    
            }
    
    • 打印的时候是这样的:
    [INFO] 线程	57	本次插入所花时间:	1106
    [INFO] 线程	89	本次插入所花时间:	0
    [INFO] 线程	36	本次插入所花时间:	0
    [INFO] 线程	45	本次插入所花时间:	0
    [INFO] 线程	76	本次插入所花时间:	0
    [INFO] 线程	42	本次插入所花时间:	0
    [INFO] 线程	58	本次插入所花时间:	0
    [INFO] 线程	86	本次插入所花时间:	0
    [INFO] 线程	67	本次插入所花时间:	0
    [INFO] 线程	81	本次插入所花时间:	0
    [INFO] 线程	64	本次插入所花时间:	0
    

    不过没影响数据库,但这是为啥?

  • 原因:这是因为当一个线程获得锁进入synchronized代码块后,随后有多个线程到了else,等待释放锁,当当前线程释放了synchronized锁后,等待的线程挨个拿到锁后又把这个代码块执行了一遍,但因为bufferList是空的所以执行的很快。这是一个缺陷。

大致思路的演化

  • 从官方的原始架构谈起…这是webMagic官方的架构图。整个爬虫与外界的Internet交互数据。
  • 整个爬虫分为四部分:
    • Scheduler:管理URL,主要就是推入URL,去重,读出URL
    • Downloader:从scheduler读取URL,然后基于HTTPClient下载页面,封装成Page后传给PageProcessor处理
    • PageProcessor:处理Downloader下载封装好的Page对象,抽取Items交给PipeLine处理结果数据;抽取有效URL封装成Request添加到Scheduler的URL管理队列中。
    • Pipeline:结果集处理,存数据库,存文件…
  • 因为webMagic想做爬虫集群的话主要是在scheduler模块上做文章,所以经过上面的分析可以得出与scheduler有关系的两个模块是Downloader(读取URL),PageProcessor(写入URL)。
  • 经过在网络上查询资料后知道,爬虫集群的大致思路是:有三类机器,主机器,从机器,数据库机器。
  • 主机:维护URL队列,从机请求主机,主机poll一条Request(URL)返回给从机。
  • 从机:有若干台机器,每台机器担负下载,处理页面,利用pipeLine给数据库机器填入数据,相当于单个爬虫。
  • 数据库机器:存数据。
  • 从上面的分析,思路相信已经很明了了。所以,我最初的思路是主机相当于scheduler模块,从机上有Downloader,PageProcessor,PipeLine,数据库机器上有MySQL。
    架构图 v1.0如下:
    在这里插入图片描述
  • 那么爬虫从机和主机的scheduler之间的Request(URL)怎么传递呢?请教别人后了解到,阿里的dubbo可以满足这方面的需求。
  • 研究了一下dubbo的入门用法后,发现直接在 爬虫从机.downloader和 主机.scheduler,爬虫从机.pageProcessor 和 主机.scheduler之间传递参数有些麻烦。所以在v1.0的基础上改了一下就有了下面的架构图 v2.0:

在这里插入图片描述

  • 修改的地方是在爬虫从机自定义scheduler,然后scheduler中用到的一切方法(其实也就是poll和push)全部用dubbo远程调用主机中的scheduler中的方法(相当于打电话中的“转接”功能)。
  • 举例说明(只是为了说明思路,并不是实际代码):
    • 主机——服务提供者(Provider)
    • 二者的公共接口:
	public interface HostSchedulerService {
	
	    void pushWhenNoDuplicate(Request request, Task task);//push进一个Request
	
	    Request poll(Task task);//读取一个Request,需要同步
	
	}
  • 服务的具体实现类:
	public class HostSchedulerServiceTest extends DuplicateRemovedScheduler
        implements HostSchedulerService {

    private BlockingQueue<Request> queue = new LinkedBlockingQueue();

    @Override
    public void pushWhenNoDuplicate(Request request, Task task) {
        System.out.println("[INFO] pushWhenNoDuplicate " + request.getUrl());
        this.queue.add(request);
    }

    @Override
    public synchronized Request poll(Task task) {
        Request request = (Request)this.queue.poll();
        System.out.println("[INFO] poll " + request.getUrl());
        return request;
    }
	}
	//这是改的webMagic默认的scheduler——QueueScheduler
  • 在spring-dubbo.xml中暴露服务:

    <!-- 具体的实现bean -->
    <bean id="hostSchedulerService" class="cn.spiderCluster.service.impl.HostSchedulerServiceTest" />
    <!-- 声明需要暴露的服务接口 -->
    <dubbo:service interface="cn.spiderCluster.service.HostSchedulerService" ref="hostSchedulerService" />
    
  • 然后启动Provider服务…

  • 从机——服务消费者(Consumer)

  • 从参数来看,主从机消费的对象是Request,Task,因为涉及到dubbo传递的类必须是序列化的,所以现在也是因为这个问题而经常报错。 ps:加色部分可以先跳过,看完从机介绍后再回来看,要不然你会觉得我在自言自语的很傻{所以我考虑着是不是光传String类型的URL就可以了呢(不过Request还有Extras等属性),毕竟本质上还是URL的传递,在这个基础上又有一个思路就是,主机上维护的服务是一个URL队列的管理程序,对远程机器通过dubbo提供poll和push功能,消费的对象是URL+Extras+其他重要参数(比如URL的优先级)组成的可序列化对象。并且我还期望爬虫从机可以实现“热插拔”。脑洞超多超大。} 从机中除了Downloader需要webMagic原生的外,其余三个模块都要自己写。PageProcessor,PipeLine没啥说的,主要说一下Scheduler。

  • Spring-dubbo.xml中定义远程服务:

    <!-- 生成远程服务代理,可以像使用本地bean一样使用demoService -->
    <dubbo:reference id="hostSchedulerService"
                     interface="cn.spiderCluster.service.HostSchedulerService" />
    
  • 公共接口见上面。

    @Component
    public class SlaveScheduler extends DuplicateRemovedScheduler implements  DuplicateRemover {
    
        @Autowired
    
        HostSchedulerService hostSchedulerService;//注入服务实现类
    
        public SlaveScheduler() {
        }
    
        @Override
        public Request poll(Task task) {
            return hostSchedulerService.poll(task);
        }
    
        @Override
        public void pushWhenNoDuplicate(Request request, Task task) {
            hostSchedulerService.pushWhenNoDuplicate(request,task);
            return;
        }
    }
    
    • 相当于把Request,Task通过dubbo“转接”给了主机的scheduler,将远程服务的处理结果返回给Downloader和PageProcessor。

2017年11月11日

  • 出现的一个问题:maven HTTPClient jar包冲突
Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/http/config/Lookup
	at org.apache.http.impl.client.HttpClients.createDefault(HttpClients.java:58)
	at cn.spiderCluster.slave.utils.PageUtil.<clinit>(PageUtil.java:24)
	at cn.spiderCluster.slave.pageProcessor.KwPageProcessor.<init>(KwPageProcessor.java:34)
	at cn.spiderCluster.slave.Main.main(Main.java:29)
Caused by: java.lang.ClassNotFoundException: org.apache.http.config.Lookup
	at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	... 4 more
  • 解决方案:去除dubbo中HTTPClient包和httpcore包即可。另外,说一个跟本问题无关的建议——把dubbo中的Spring也去掉(版本太老)。
	<dependency>
      <groupId>us.codecraft</groupId>
      <artifactId>webmagic-core</artifactId>
      <version>${webMagic.version}</version>
    </dependency>
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>dubbo</artifactId>
      <version>${dubbox.version}</version>
      <exclusions>
        <exclusion>
          <groupId>org.springframework</groupId>
          <artifactId>spring</artifactId>
        </exclusion>
        <exclusion>
          <groupId>org.apache.httpcomponents</groupId>
          <artifactId>httpclient</artifactId>
        </exclusion>
        <exclusion>
          <groupId>org.apache.httpcomponents</groupId>
          <artifactId>httpcore</artifactId>
        </exclusion>
      </exclusions>
    </dependency>

这样的话,HTTPClient就用webMagic依赖过来的包就可以了。

2017年11月16日

  • 由于Request和task都需要发送,然而Request实现了Serializable接口,task没有继承(task是个接口),所以发生错误的原因一般就是task没有实现接口引起的,阅读QueueScheduler(Spider默认的Scheduler)源码后,发现内部使用了BlockingQueue实现的,没有使用到传过来的参数——task,所以只需要在host和slave之间传递Request即可,成功。
  • QueueScheduler.java
@ThreadSafe
public class QueueScheduler extends DuplicateRemovedScheduler implements MonitorableScheduler {
    private BlockingQueue<Request> queue = new LinkedBlockingQueue();

    public QueueScheduler() {
    }

    public void pushWhenNoDuplicate(Request request, Task task) {
        this.queue.add(request);
    }

    public synchronized Request poll(Task task) {
        return (Request)this.queue.poll();
    }

    public int getLeftRequestsCount(Task task) {
        return this.queue.size();
    }

    public int getTotalRequestsCount(Task task) {
        return this.getDuplicateRemover().getTotalRequestsCount(task);
    }
}

  • Host的服务实现类:
/**
 * Created by Ww on 2017/11/11.
 */
public class HostSchedulerService2Memory implements HostSchedulerService {
    private BlockingQueue<Request> queue = new LinkedBlockingQueue();

    @Override
    public void pushWhenNoDuplicate(Request request) {
        this.queue.add(request);
    }
    @Override
    public Request poll() {
        Request request = (Request)this.queue.poll();
        return (Request)this.queue.poll();
    }
}
  • Slave的自定义Scheduler():
@Override
public Request poll(Task task) {
	return hostSchedulerService.poll();
}

@Override
public void pushWhenNoDuplicate(Request request, Task task) {
	hostSchedulerService.pushWhenNoDuplicate(request);
	return;
}
  • 关于LikedBlockingQueue:
    • LinkedBlockingQueue是一个单向链表实现的阻塞队列,先进先出的顺序。支持多线程并发操作。
    • 相比于数组实现的ArrayBlockingQueue的有界,LinkedBlockingQueue可认为是无界队列。多用于任务队列。
    • 定义:LinkedBlockingQueue继承AbstractQueue,实现了BlockingQueue,Serializable接口。内部使用单向链表存储数据。
    • 链接:原文链接

2017年11月17日

  • 昨天把搭了一台centos 6.5的机器准备做爬虫子机用,安装的时候各种问题(增霸卡,BIOS设置等等等等等),今天又把网络给调通了。哎,实验室电脑资源有限且质量一般。

  • 今天主要是整理了一下slave和host,现在总结一下出现的问题和解决方案。

  • 先说Host:

    • “解决task没有继承Serializable接口”,以下所有都是在这个思路的基础上开始的。
    • 因为我的自定义Scheduler是在QueueScheduler的基础上改的(还有一个是改的FileCacheScheduler),所以研究了QueueScheduler后发现,task用到的就光是getUUID(),所以我就自定义了一个SerializableTask继承Task接口。
    • SerializableTask.java
    public class SerializableTask implements Task, Serializable {
        private String uuid;
    
        public void setUuid(String uuid) {
            this.uuid = uuid;
        }
        @Override
        public String getUUID() {
            return null;
        }
    
        @Override
        public Site getSite() {
            return null;
        }
    }
    
    • 所以,dubbo传递Task接口时传递的是SerializableTask类。
    • 因为需求中需要一开始先加入许多(大约有5000)条起始URL,所以根据需求定制了一个KwScheduler如下:
    public class KwScheduler extends HostSchedulerService_Queue implements KwSchedulerService {
    
        private static AtomicBoolean isAddAllPage = new AtomicBoolean(false);
    
    
        @Override
        public void pushAllPage(List<String> requestList,Task task) {
            for (String r:
                    requestList) {
                pushWhenNoDuplicate(new Request(r),task);
            }
            isAddAllPage.set(true);
        }
    
        @Override
        public boolean isAddAllPage() {
            return isAddAllPage.get();
        }
    }
    
    • 从子机上传来List urlList,在这边处理并填入queue,并设置为isAddAllPage为true,使得其他子机不用重复这个昂贵的操作。另一个增加的方法isAddAllPage()返回是否完成了批量导入初始化URL。
  • 再说slave:

    • Slave中自定义的scheduler中,当Spider传参Task task的时候,通过
    private SerializableTask setTaskUUID(Task task) {
    	SerializableTask task1 = new SerializableTask();
    	task1.setUuid(task.getUUID());
    	return task1;
    }
    

    new一个新的SerializableTask,只把UUID填进去,因为主机的KwScheduler没有用到site。

    • 关于“先在子机累积song,累积到1000条,然后拼接sql(用mybatis的foreach)批量插入。并且在spider.run()结束后,还准备了一个flush方法,把缓冲区中剩余的song但是不满1000条的数据批量插入数据库。”思路的问题:
    • 这是为了提升数据库性能的一个优化逻辑,这样确实可以提升性能。但是,有几个问题还没解决了。
      • 如果这一千条批量插入的时候,有一条出现错误(比如某个字段超了),由于这是一个事务,那么MySQL将会回滚,一条都插不进去,导致了程序进入停滞状态(一条新的song都插入不进去)。
      • 由以前的经验得知,这个程序运行的时间越长,很有可能出现程序进入停滞状态(可能是死锁),也不继续往下爬取,也不停止,导致放在spider.run()后的代码执行不了。
  • Main中的修改就是每台爬虫子机在main()中,一开始先判断service.isAddAllPage(),如果没有那就装好urlList后用service.pushAllPage(urlList,task)。

2017年11月18日

今天出现的问题:
  • 多台爬虫子机爬取的时候可能报异常TimeoutException: Waiting server-side response timeout。

    • slave1异常节选:
    TimeoutException: Waiting server-side response timeout. start time: 2017-11-18 10:13:29.968, end time: 2017-11-18 10:13:30.969, client elapsed: 0 ms, server elapsed: 1001 ms, timeout: 1000 ms
    
    • slave2异常节选:
    Waiting server-side response timeout. start time: 2017-11-18 10:13:29.979, end time: 2017-11-18 10:13:30.980, client elapsed: 0 ms, server elapsed: 1001 ms, timeout: 1000 ms
    
    • 简单分析后,我觉得是在2017-11-18 10:13:30.969——2017-11-18 10:13:30.980之间这段时间,dubbo接收到两个poll请求,先接受slave1的,因为poll是BlockingQueue.poll,是线程安全的,换句话说,是会阻塞,host处理slave1超过了1000ms,在这期间阻塞,导致也不能处理slave2的请求,导致了两者都超时。
    • 解决思路:
      • 保证主机资源够用。
      • 建立slave重试机制。
      • 想办法slave报异常后可以自动跳过异常(现在是报了这个异常,程序不会继续运行,但是也不会退出)。
      • 优化dubbo配置,提升重试次数,提升超时时间。
      • 报异常的时候通知程序维护者,让程序维护者重启程序。
      • 当发生这种情况时用jvisualvm和mat看一下jvm的状态。
  • 发现网络上有mybatis批量插入和jdbc批量插入的对比,结论是jdbc+Spring的事务管理最快,用JdbcTemplate。所以准备在pipeLine里加上JdbcTemplate的批量插入(当然要处理多线程问题了)。

2017年11月19日

  • 实现了SpringJDBC的批量插入,相关并发问题也已经解决。
  • 出现的问题:
    • 插入的数据有重复的。

      • 原因:经过分析后是并发问题,因为我的逻辑是批量插入有一个bufferList,当bufferList没满500时,往bufferList里插入,满了500,就往数据库批量导入。这里强调几点,bufferList是多个线程共同维护的,换句话说,多个线程爬取到的song数据都往bufferList里放。而错误的原因是,当bufferList满了500时,当其中一个线程正在批量导入的时候,另一个线程也正好判断了bufferList.size() > 500,于是也进行了插入,这导致了同一个bufferList插入了多遍。
      • 解决方案:加synchronized代码块。
    • 我的代码:

    	if (bufferList.size() < 500) {
                bufferList.add(song);
            } else {
                synchronized (this) {
                    try {
    //                    为了性能,用SpringJDBC
                        Date date = new Date();
                        batchInsertSelective(bufferList);
                        Date date1 = new Date();
                        System.out.println("[INFO] 线程\t" + Thread.currentThread().getId() + "\t本次插入所花时间:\t" + (date1.getTime() - date.getTime()));
                        bufferList.clear();
                        objectCount += bufferList.size();
                    } catch (Exception e) {
                        e.printStackTrace();
                        logger.error(e.getMessage());
                    }
                }
    
            }
    
    • 打印的时候是这样的:
    [INFO] 线程	57	本次插入所花时间:	1106
    [INFO] 线程	89	本次插入所花时间:	0
    [INFO] 线程	36	本次插入所花时间:	0
    [INFO] 线程	45	本次插入所花时间:	0
    [INFO] 线程	76	本次插入所花时间:	0
    [INFO] 线程	42	本次插入所花时间:	0
    [INFO] 线程	58	本次插入所花时间:	0
    [INFO] 线程	86	本次插入所花时间:	0
    [INFO] 线程	67	本次插入所花时间:	0
    [INFO] 线程	81	本次插入所花时间:	0
    [INFO] 线程	64	本次插入所花时间:	0
    
    不过没影响数据库,但这是为啥?
  • 原因:这是因为当一个线程获得锁进入synchronized代码块后,随后有多个线程到了else,等待释放锁,当当前线程释放了synchronized锁后,等待的线程挨个拿到锁后又把这个代码块执行了一遍,但因为bufferList是空的所以执行的很快。这是一个缺陷。

2017年11月20日

  • 实现了SpiderMonitor(WebMagic自带的基于JMX的监视功能)。
  • 加了日志。关于日志很好的一篇文章:点击我传送
  • 优化了synchronized代码块位置。
  • 增加了Constant类和spider.properties——让爬虫可以配置的信息更多(现在有initURL指定初始URL,thread指定线程数,log4j指定日志文件路径…)。
  • 还应该解决一下objectCount光存最后插入的数据的size()的问题。

2017年11月21日

  • 关于打包,打包失败:

    • 先是打的包没有依赖
    • 装上依赖后有没有配置文件
    • 通过360压缩将配置文件放到classpath下后,又提示“通配符的匹配很全面, 但无法找到元素 ‘dubbo:application’ 的声明。”的错误。
    • 额。明天在研究一下。
  • 关于“运行完main方法还不停止”,请教别人后,得知程序运行完main方法还不停止,可能是还有活跃的线程。

  • 关于“爬虫开多少线程合适”

    • 爬虫在处理过程中,会涉及到cpu和IO时间。其中IO等待时,cpu被动放弃执行,其他线程就可以利用这段时间片进行操作。
    • 爬虫是种IO密集的程序,特别适合多线程。
    • 一篇如何计算tomcat线程池大小?的文章。

2017年11月22日

  • 关于“程序运行完main后还不停止”,经过研究及上网查询资料后得知,应该是dubbo的某些线程,这里涉及到“dubbo的优雅退出”,“Runtime.getRuntime().addShutdownHook(new Thread())”。以下是探索过程记录:

    • 关于“shutdown hook” 的文章。ShutdownHook - 优雅地停止服务。从其中可以得知shutdown hook当我调用System.exit(0)会被调用,完成平滑退出,具体好处参见文章“二、java进程平滑退出的意义”
    • 关于“dubbo中优雅停机”中根据阿里一位工程师的Dubbo应用与异常记录提到,Dubbo是通过JDK的ShutdownHook来完成优雅停机的,所以如果用户使用"kill -9 PID"等强制关闭指令,是不会执行优雅停机的,只有通过"kill PID"或者是“System.exit(0)”或者是“其他方法”时,才会执行。
    • 关于“Runtime.getRuntime().addShutdownHook(new Thread())在什么时候才会被调用”java.lang.Runtime.addShutdownHook(Thread hook) 方法注册一个新的虚拟机关闭挂钩。 Java虚拟机的关闭响应于两种事件:
      • 该程序正常退出,当最后一个非守护线程退出或退出(等同于System.exit)方法被调用时,或关闭钩子是一个简单的初始化但尚未启动的线程。
      • 当虚拟机开始其关闭序列将启动所有已注册的关闭钩子在一些未指定的顺序,让他们同时运行。当所有的钩子都做完了会然后运行所有未调用的终结,如果最后确定的按出口已启用。最后,虚拟机将暂停。需要注意的是守护线程将继续关闭序列期间运行,将作为非守护线程,如果关机是通过调用exit方法启动。
  • 在研究上一个问题的时候,看到一个词“配置分离”,这是那位大神的博客

什么叫做配置分离呢?就是使用maven打包时,借助assemble插件,打一个tar.gz的压缩包。里面有三个目录。bin目录,用来存放启动与停止的脚本,lib目录,用来存放相关依赖的jar包,注意,这里每个jar包都是单独的,而不是一个大的jar包。conf目录,用来存放配置文件,包括dubbo.property,applicatiom.xml等文件。

2017年11月24日开发记录

  • 在命令行中用 java -jar xxx.jar 运行时,出现dubbo不能运行远程方法(用到的第一个方法就不行)的问题。
    • Fail to decode request due to: RpcInvocation xxxxxxxx…有个dubbo的版本号是2.8.4,但我在pom.xml中早就改成了2.5.3了呀。
  • 解决方案,把pom.xml中dubbo的版本号改成2.8.4就好了。

面试该项目时得到的一些改进的建议

在准备面试和面试的这段时间(2018.03.05-2018.04.25)内,关于这个项目接收到了来自面试官(七牛)和实验室同学的一些改进的建议,特做此记录,采用Q-A的形式。

  • Q:可不可以把中心scheduler和子爬虫之间的交互改为异步的,从而实现假如中心scheduler宕机,子爬虫不会受到影响而宕机的效果。
  • A:这是面试官提的问题,一开始我想到的方案仅仅是针对他后面提出的需求,增加中心scheduler子机,实现读写分离,宕机后进行备份的切换。但并没有实现异步,然后面试官的建议是,在中心scheduler和子爬虫间加上一个阻塞队列,实现生产者消费者模式,这就实现了异步,而且可以提高性能。
  • Q:如何设计URL去重操作。
  • A:我回答的是利用HashMap的key不重复的性质,将URL offer进去,然后面试官说这样就不能知道某个URL是否被消费过。然后我改进,将URL和一个标记位包装一个pojo作为key offer进去,然后面试官说这样的性能太差,因为每次都需要遍历HashMap,遍历的过程中可能会遍历到标记位为“已消费”的URL,而这样的遍历恰好是没有意义的。面试官给出的答案,将“未消费”的和“已消费”的分开来放,当从PageProcessor来了一个新的URL的时候,先去“已消费”的集合中遍历看有没有,如果没有的话,将该URL offer进“未消费”的队列中去,如果有的话,则丢弃,还可以记录次数,记录爬取时间,进而还可以实现URL的持久化,实现增量爬取。
  • Q:关于Constant.getConfig()实现“不用重启就能修改系统配置”
  • A:解释一下,在这个小系统中我用了一个Constant类去维护系统配置文件(spider.properties),Constant有一个HashMap的类变量做缓存。以前Constant类去加载本地的.properties文件读取配置的,现在改成从数据库的一张key-value表中获取了,管理员去修改数据库,然后配置一个refresh操作,清空缓存,重新从数据库中读取,实现了“不用重启就能修改系统配置”。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值