程序的基本业务如下:
- 从一个待爬集合中取一个待爬URL
- 取回这个URL代表的Web页面
- 对页面进行解析,找出其中的链接URL
- 如果找到的URL不在已爬集合中,就把它放到待爬集合中去
我关心的这些URL都属于同一个网站,所以从一个URL开始(如首页),如果待爬队列中过了一段时间没有新任务,整个网站就爬完了。
由于网络访问的延迟,所以采用多线程是很自然的考虑。但是多线程的设计并不能伸缩到多台机器的集群上。“做什么(爬网页)”和“怎么做(多线程)”混在了一起,未能实现关注点分离。
如果我们决定使用多线程,可以。但要确保这种设计决定没有散布在程序的各个角落。不幸的是,我原来的程序没有做到。由于要使用多线程,我在程序的各个角落使用了synchronize关键字,还使用了线程安全的集合类。
之所以要使用synchronize关键字和线程安全的集合类(手段),是因为我要保证操作的原子性(目的)。实际上,根据实现的设计决定不同,就会采用不同的手段。例如,我们可以通过数据库持久事务来实现操作的原子性。
按照Google MapReduce的设计思路,我们应该设计一个任务主控组件,它负责分发任务和结果合并。这个主控组件管理着待爬集合、正在爬集合和已爬集合。同时它也管理着执行机器的集合。它组装出一个个的Runnable或Task,发送到执行机器上(如果只有一台机器,那就本机了),并监督它们是否正常执行。可以向一台执行机器同时发送多个任务,在执行机器上实现多线程。
这样,通过一个中间层,一个子任务的执行和总任务的管理之间的耦合解除了。
这个架构可以移到GAE上去。写一个RESTful的Web service,负责执行一个子任务。任务主控组件向GAE上的这个应用发起一堆请求,利用GAE的强大计算力、带宽以及伸缩性。也许一万个页面只要几秒钟就搞定了。
你说什么?这个任务主控组件不好弄?本身就需要很好的带宽?把它也弄到GAE上去!
网络就是计算机。这一天到来已经很久了。
没有评论:
发表评论