说起爬虫大家一定不会陌生,随便在百度上搜以下,有一大堆的爬虫教程。不过,大多数的教程基本上就是介绍一下什么是数据爬虫。用一段简单的python脚本演示如何使用requests,BeautifulSoup,xpath和正则表达式爬取和提取数据。如果再深入一点,可能会讲到静态数据爬取或者动态页面的数据爬取,并且可能会介绍到selenium这个自动化测试框架。如果再要深入一点,可能会介绍Scrapy这个非常有名的Twisted的异步处理框架。这里我们就不介绍什么是Scrapy,什么是Twisted,什么是基于协程的异步处理了,有兴趣的朋友可以在百度上搜索自学以下。
以上提到的这些内容,都有一个特点——讲的都是单机的爬虫方案。对于简单爬取任务尚可应付,但是对于一个大型的爬取任务,或者一个长期的项目,单机爬虫完全是心有余而力不足的方案。在之前写过的一篇文章《爬虫系统构架演进-从脚本到服务的蜕变》,我详细分析了单机脚本爬虫的优势和劣势。并介绍了基于Scrapy Cluster的分布式爬虫方案。在另外一片文章《搭建上亿级舆情爬虫系统》中,我又详细介绍了Scrapy-Cluster开源项目中的一些缺陷。并且简单描述了一个真正的分布式数据爬虫系统需要的一些具体模块。本文将在前两篇文章的基础上,从另外一个视角讨论如何设计和搭建可伸缩的数据爬虫平台。
平台架构核心:可伸缩性
本文的套路这次有点变化,先说说平台的真正核心,即可伸缩性。什么是可伸缩性?这里可以分为垂直伸缩和水平伸缩。
垂直伸缩
可以简单理解为是主机服务器的配置升级。例如本来一台1核4G的服务器就可以跑几个静态脚本爬虫。但是如果需要使用selenium和Chrome,可能就需要把服务器升级到2核4G或者4核8G。但是垂直伸缩是有极限性的。目前云服务器的最高配置应该是16核32G。另外,随着服务器配置的提高,价格是成阶越型增长的。例如1台16核32G的服务器一定比4台4核8G的云服务器贵很多。另外,相对于一般服务来说,爬虫需要的服务器资源不仅仅是CPU和内存,更重要的是IP地址。
水平伸缩
一个平台的伸缩性一般指的就是水平伸缩。从1台服务器到10台服务器,从10台服务器到20台服务器。在不改变现有代码的情况下,如何能够快速的扩容服务器是衡量一个平台架构水平伸缩能力的唯一指标。而对于爬虫平台而言,水平伸缩的关键服务是爬虫服务。当然如果数据爬虫平台还有数据服务模块的话,需要进行水平伸缩的可能还包括数据库和数据搜索服务。
因此,设计数据爬虫平台的目的是实现各服务独立伸缩,从而每一个服务能够独立提高并发量。需要做到这一点,就需要将系统切分成多个独立的服务,服务与服务之前减少依赖,实现低耦合。
四种实现低耦合的设计思路
一个优秀的架构设计,首先要满足在调用量不断增长的情况下可以快速实现伸缩,而不需要改变现有的架构设计。与此同时,基于平台的任务和服务也不可能一成不变。也会随着用户的不断增涨扩展更多的业务和提供更多的服务。基于新的业务需求,平台的架构也应该是已扩展的。如果说,出来一个新需求就要修改现有架构设计,这显然是不合理的。因此,我们可以基于以下三种设计思路实现平台各服务间的低耦合,从而实现服务和规模上的灵活伸缩。
通过接口调用
顾名思义就是服务与服务之间,基于约定好的协议或者接口实现调用,并且每一个服务尽量保持单一职责。目前最普遍的方式是基于HTTP的RESTFUL接口调用。服务间的调用是通过事先的约定,包括调用接口名称,参数以及输出。通过RESTFUL调用实现服务间低耦合的好处是显而易见的。要保持单个服务的自由伸缩,就必须确保该服务调用必须是无状态的。例如,一个查看账户信息的调用,首先这个调用是登陆状态。但是无论是服务器A或者服务器B并不保存这个登陆状态。而是在调用过程中传递登陆状态或者从缓存中读取调用状态。
其实这一条就涵盖了六大设计原则中的两条:单一职责原则和接口隔离原则。其实架构设计低耦合的思路都是百变不离其宗的。
开闭原则
这也是六大设计原则之一。通俗来讲,就是对新业务、新需求、新扩展保持开放,而对于修改尽量保持关闭。要做到这一条,就需要重读第一条。只有实现了服务间通过约定实现接口调用,实现接口隔离,才能更好地支持新业务和新需求的扩展。另一方面,服务如果保持单一指责,在需求变更的情况先,能够把修改工作量降到最低。甚至为了保证系统的稳定性,可以通过提供新接口的方式实现需求变更,老接口维持现有的服务不变。
反转控制
反转控制IOC也是六大设计原则之一。但是这是更加抽象的一个设计思路。如果熟悉Java编程中的依赖注入(最常见的方式)可能会容易理解一些。因为很抽象,所以无法具体地用语言来解释这个原则。我用以下两个例子说明一下。
- 爬虫平台能够自由地增加新类型的爬虫组件
- Chrome浏览器可以自由地增加新类型的插件
当我们为平台开发一款新类型的爬虫,爬虫平台本身是不需要作任何修改的。每一款爬虫都是独立的,可随时接入系统,并且在没有对应任务的时候自动退出。平台只需要派发需要该爬虫爬取的任务,通过爬虫注册信息启动爬虫。任何一个独立的爬虫,无论其状态如何,都不会影响平台和其他爬虫。
Chrome浏览器的插件机制也是IOC比较经典的应用。首先Chrome为插件提供了各种接口,在开发插件过程中,始终需要遵守Chrome浏览器制定的接口规则。通过插件的配置文件,插件可以随时被浏览器加载、启动、停止、或者移除。浏览器不会依赖于某一个插件运行。
可以说反转控制也是以约定为基础的,单独的服务需要遵守平台架构设计的约定。要启动一个服务,平台并不需要为该服务定制化任何特别的工作。而是按照既定的规范加载和移除,并且不会影响平台的整体运行。
使用消息队列
消息队列能够有效地分离生产者和消费者,从而达到生产者把消息发送到消息队列(指定的队列或者指定的主题,基于系统使用什么消息队列)。而消费者则根据自己的业务需求消费特定队列中或者主题的消息。基于消息队列的特性,消息的消费端可以自由的伸缩,实现高并发,不受生产者的限制。这里唯一需要注意的是,消息队列的消息处理是异步的。因此在多个消费者消费同一类消息时,需要注意时序问题。
三种基本伸缩设计方案
当一个平台具备良好的伸缩性以后,我们可以从三个方面进行水平伸缩:
增加副本
简单来说就是为同一个服务增加服务器数量或者增加更多的容器。例如Web服务使用负载均衡将流量分发到不同的服务器上。由于服务本身是无状态的,任何一个副本都可以正常地为分配的调用提供服务。除了Web服务以外,增加副本的方案在数据库水平伸缩方面的应用也非常广泛。例如MYSQL的主从,MongoDB的副本集等。
功能分割
功能分割对应于上节中的第一个原则:服务与服务通过接口隔离。也就是服务的职责是单一的,独立的。服务与服务通过接口调用。如果遵守这一原则,功能分割就是自然地实现了。通过功能分割,我们可以更加细化地对于存在性能瓶颈的服务使用增加副本的方式进行水平伸缩。
数据分片
数据分片是三种伸缩方案中最难的最复杂的。简单理解就是以某一种规则均匀的保存到不同的数据库服务器上,从而减轻每一个服务器上的读写压力。但是数据分片的复杂之处在于,当指定好一个规则,并确定好服务器的数量以后,数据会按照规则存入不同的服务器。但是后期再次扩容服务器后,之前已经分配的数据就需要根据服务器的数量重新分配。
另一个数据分片后的问题是,跨分片的数据查询。如果一个查询涉及多个数据分区,那么实现起来就特别的低效和困难。因此在分片之前根据具体业务,根据实际情况选择合适的分片键和分片算法是非常必要的。
扫码联系船长
Thank you!!1