爬虫平台Crawlab核心原理--分布式架构

背景

Crawlab自初版发布已经几个月了,其中经历了好几回迭代:版本从v0.1到了v0.3.0;后端语言从Python到了Golang;从最初使用Celery做为任务调度引擎,到本身开发分布式任务调度引擎;从只能运行自定义爬虫到能够运行可配置爬虫(虽然还没迁移到最新版本);从手动部署爬虫到自动部署爬虫;从本身搭建环境到Docker部署;从手动执行任务到定时任务;等等(详情见CHANGELOG)。在使用者们的反馈下,Crawlab爬虫平台也逐渐变得稳定和实用,可以真正帮助到有爬虫管理需求的用户。现在在Github上有近1k的star,相关社区(微信群、微信公众号)也创建起来,四分之一的用户表示已经将Crawlab应用于企业爬虫管理。能够看出,Crawlab是受开发者们关注和喜欢的。html

Github: github.com/tikazyq/cra…前端

为何须要爬虫管理平台

对于通常的爬虫爱好者来讲,写一个单机爬虫脚本已经足够,并且Scrapy这样的优秀爬虫框架可以让开发者轻松调试、编写一个简单的爬虫,须要定时任务就直接上Crontab,分分钟搞定。然而,通常的企业对爬虫的要求相对较高,其中主要涉及一个问题,也就是规模(Scale)。固然,这里的规模是指大型规模。爬虫的规模分为两种:一种是爬虫须要抓取大量的数据(Volume),例如全网抓取淘宝的商品;另外一种是爬虫须要涵盖大量的网站(Coverage),例如搜索引擎。node

不一样的规模须要有不一样的架构策略(以下图):git

  1. 当网站数量只有一个,抓取结果很少时,只须要单机便可,并不须要分布式爬虫;
  2. 但当须要抓取结果量级提高时,例如全网抓取淘宝,就须要用分布式爬虫了,这是由于单个机器的带宽和计算资源不足以作到全网抓取,并且为了应对反爬虫技术,还须要大量的代理IP;
  3. 同理,当须要抓取网站的数量增多时,例如你须要建立一个新闻搜索引擎,你一样须要多台机器来获取足够的带宽和计算资源;
  4. 对于同时要求Volume和Coverage的应用,不是通常的小企业或我的能作的,对无论是人力和机器的资源都很是高。

而爬虫管理平台就是针对状况(2)、(3)、(4)而存在的分布式管理平台,可以让用户轻松管理多个爬虫或多机运行的爬虫。github

Crawlab从诞生之初就解决了分布式爬虫问题,最先采用了Celery做为分布式任务调度引擎,以Redis做为消息队列,HTTP请求做为节点通讯媒介,简单地实现了分布式管理。但随着用户不断使用Crawlab,发现这样的方式并非很方便,用户须要指定节点的IP地址和API端口,并且还不能指定节点执行任务。由于各类问题,在最新版本v0.3.0用Golang重构后端的时候,就将Celery弃用了,转而本身开发分布式节点的监控和通讯应用,这样更加灵活和高效。本文是核心原理介绍,下面将着重介绍Crawlab的分布式架构原理(Golang版本)。算法

总体架构

Crawlab的总体架构以下图,由五大部分组成:数据库

  1. 主节点(Master Node):负责任务派发、API、部署爬虫等;
  2. 工做节点(Worker Node):负责执行爬虫任务;
  3. MongoDB数据库:存储节点、爬虫、任务等平常运行数据;
  4. Redis数据库:储存任务消息队列、节点心跳等信息。
  5. 前端客户端:Vue应用,负责前端交互和向后端请求数据。

以执行爬虫任务为例,它是Crawlab中常见的使用场景,咱们来看看它具体是如何工做的:json

  1. 前端向主节点发起请求,要求指定在某一工做节点执行任务;
  2. 主节点收到该请求,并将任务数据推送到Redis任务队列中;
  3. 工做节点持续监听Redis任务队列,并利用LPOP获取任务;
  4. 工做节点执行任务,并将结果写回到储存数据库;

以上就是执行爬虫任务的大体流程。固然,这还不是所有,咱们还须要考虑日志处理、并发执行、取消任务等细节问题。具体的处理信息,请查看相关文档源代码后端

总的来讲,能够将主节点看做是Crawlab总体架构的中控系统,理解为Crawlab的大脑;工做节点是实际干活的部分,是Crawlab的运动躯体;MongoDB和Redis是负责通讯交流的,能够看做Crawlab的血液和神经网络。这些模块一块儿构成了一个完整、自洽、相互协做的系统。安全

节点注册和监控

节点监控主要是经过Redis来完成的(以下图)。

工做节点会不断更新心跳信息在Redis上,利用HSET nodes <node_id> <msg>,心跳信息<msg>包含节点MAC地址,IP地址,当前时间戳。

主节点会周期性获取Redis上的工做节点心跳信息。若是有工做节点的时间戳在60秒以前,则考虑该节点为离线状态,会在Redis中删除该节点的信息,并在MongoDB中设置为"离线";若是时间戳在过去60秒以内,则保留该节点信息,在MongoDB中设置为"在线"。

该架构的优势

这样,就作到了一个监控节点是否在线的节点注册系统。这样架构的好处在于,节点之间根本不用像HTTP、RPC那样IP或端口,只须要知道Redis的地址就能够完成节点注册和监控。所以,也就减小了用户配置节点的操做,简化了使用流程。同时,因为隐藏了IP地址和端口,也更为安全。另外,相较于Celery版本的监控,咱们去除了Flower服务,不用在服务中单独起一个Flower服务的进程,减小了开销。

下图是Crawlab UI界面上的节点之间的关系图(拓扑图)。

该架构的缺点

相较于一些常见的分布式架构,例如Zookeeper,Crawlab还存在一些不完善的地方。

高可用性(High Availability)是Crawlab暂时尚未作得很好的。例如,当主节点宕机的时候,整个系统就会瘫痪,由于主节点是Crawlab的大脑中枢,负责不少功能。若是主节点宕机,前端就没法获取API数据,任务没法调度,固然也没法监控节点了。虽然Zookeeper没有将可用性(Availability)作得很是完善,但其投票选举机制保证了其必定程度的高可用。若是Crawlab要改善这一点的话,会在主节点宕机后,用必定的方式选举出另外一个主节点,保证高可用。

节点通讯

若是仔细看上面的总体架构图的话,你可能会注意到Crawlab中通讯有两种。一种是同步信息(Sync via Msg),另外一种是派发任务(Assign Tasks)。这两种通讯分别叫即时通讯延迟通讯。下面分别介绍。

即时通讯

即时通讯是指某节点A经过某种媒介向另外一节点B发送信息,取决因而否为双向通讯,节点B收到信息后可能会经过同一种媒介将信息回复给节点A。

Crawlab的即时通讯是经过Redis的PubSub来实现的(以下图)。

所谓PubSub,简单来讲是一个发布订阅模式。订阅者(Subscriber)会在Redis上订阅(Subscribe)一个通道,其余任何一个节点均可以做为发布者(Publisher)在该通道上发布(Publish)消息。

在Crawlab中,主节点会订阅nodes:master通道,其余节点若是须要向主节点发送消息,只须要向nodes:master发布消息就能够了。同理,各工做节点会各自订阅一个属于本身的通道nodes:<node_id>(node_id是MongoDB里的节点ID,是MongoDB ObjectId),若是须要给工做节点发送消息,只须要发布消息到该通道就能够了。

一个网络请求的简单过程以下:

  1. 客户端(前端应用)发送请求给主节点(API);
  2. 主节点经过Redis PubSub的<nodes:<node_id>通道发布消息给相应的工做节点;
  3. 工做节点收到消息以后,执行一些操做,并将相应的消息经过<nodes:master>通道发布给主节点;
  4. 主节点收到消息以后,将消息返回给客户端。

Crawlab的获取日志、获取系统信息、取消任务、告知节点获取爬虫文件都是经过即时通讯完成的。

而实现代码相对来讲有些复杂。下面是主节点的PubSub回调函数。

func MasterNodeCallback(channel string, msgStr string) {
	// 反序列化
	var msg NodeMessage
	if err := json.Unmarshal([]byte(msgStr), &msg); err != nil {
		log.Errorf(err.Error())
		debug.PrintStack()
		return
	}

	if msg.Type == constants.MsgTypeGetLog {
		// 获取日志
		fmt.Println(msg)
		time.Sleep(10 * time.Millisecond)
		ch := TaskLogChanMap.ChanBlocked(msg.TaskId)
		ch <- msg.Log
	} else if msg.Type == constants.MsgTypeGetSystemInfo {
		// 获取系统信息
		fmt.Println(msg)
		time.Sleep(10 * time.Millisecond)
		ch := SystemInfoChanMap.ChanBlocked(msg.NodeId)
		sysInfoBytes, _ := json.Marshal(&msg.SysInfo)
		ch <- string(sysInfoBytes)
	}
}
复制代码

这里实际上是用msg.Type来区分消息类别,若是要扩展的话须要写很多if/else。工做节点的回调函数也须要写相似的逻辑。

这个可能跟HTTP请求和RPC通讯相较来讲麻烦一些。不过,这其实和WebSocket很是像(对WebSocket不了解的同窗能够看看韦世东最近的文章《开发者必知必会的 WebSocket 协议》),都须要在客户端和服务端定义回调函数。一个改进方法是不用if/else来区分信息类别,转而用PubSub频道名称,监听多个频道。总之,具体实践中怎么选择,还须要考虑实际状况。

延迟通讯

延迟通讯对即时性要求不高,不须要节点或客户端对请求即时回复。一般来讲,延迟通讯的实现方式有队列、轮询等方式。这样的方式不要求即时性。延迟通讯主要是用做须要长时间的操做,例如发送邮件、数据处理、构建应用等等。

Crawlab中的延迟通讯主要包含任务队列以及轮询,都是经过Redis来实现的。任务队列是用做爬虫任务执行:主节点接收抓取请求后,将执行任务的消息推到任务队列中,工做节点不断轮询任务队列,获取任务并执行(以下图)。Crawlab爬虫任务执行的详情请参见相关文档源代码

爬虫任务执行

Crawlab的延迟通讯主要包括爬虫任务执行和爬虫部署。爬虫任务执行这里再也不赘述。下面简单介绍一下爬虫部署(流程以下图)。

爬虫部署

整个爬虫部署的生命周期:

  1. 主节点每5秒,会从爬虫的目录获取爬虫信息,而后更新到数据库(这个过程不涉及文件上传);
  2. 主节点每60秒,从数据库获取全部的爬虫信息,而后将爬虫打包成zip文件,并上传到MongoDB GridFS,而且在MongoDB的spiders表里写入file_id文件ID;
  3. 主节点经过Redis PubSub发布消息(file.upload事件,包含文件ID)给工做节点,通知工做节点获取爬虫文件;
  4. 工做节点接收到获取爬虫文件的消息,从MongoDB GridFS获取zip文件,并解压储存在本地。

这样,全部爬虫将被周期性的部署在工做节点上。

在后续的开发中,Crawlab将会加入邮件通知、短信通知、微信推送等功能。而这些都是属于延迟通讯的范畴,主要实现方法无外乎队列和轮询。

分布式实践 - 抓取上百个新闻网站

下面将介绍一个多机爬虫的实际应用场景,帮助你们深刻理解Crawlab的分布式原理。

首先,你可能须要足够的网络带宽,由于须要抓取的网站上百了,不是简简单单的单机爬虫,你须要多台机器。这里只是简单介绍下拓扑架构,并不会详细介绍大规模爬虫的去重、反爬、容错等逻辑。以下图,每个工做节点能够限制抓取一部分网站,总共M个网站平均分配给N个工做节点。在Crawlab中就是用指定节点的方式了,这个不难。另外,你也能够经过随机分配的方式来派发任务,每个工做节点统计上也会均匀分配到任务,这其实也就是一个负载均衡(Load Balancing)的过程。

固然,你可能好奇上百个新闻网站是否是须要写上百个爬虫。对于这个问题,没有确切的准确回答。答案应该是“看状况”。对于自动提取字段的算法不够自信的开发者来讲,能够选择Crawlab的可配置爬虫(看这篇文章《我是如何在3分钟内开发完一个爬虫的》),这样的开发成本相对来讲比较小。可是对于已经有技术实力的能够写出很好的通用提取规则的选手来讲,只写一个通用爬虫就足够了(简单的列表页提取规则参考《爬虫平台Crawlab核心原理--自动提取字段算法》),抓取多个网站等于抓取一个网站,不过仍是须要部署在多个机器上,以求最大的带宽和计算资源。固然,无论是哪种,都绕不开去重、反爬、错误监控,不过这些不在本文讨论范围,网上有不少教程能够多学习一下。

社区

若是您以为Crawlab对您的平常开发或公司有帮助,请加做者微信 tikazyq1 并注明"Crawlab",做者会将你拉入群。欢迎在Github上进行star,以及,若是遇到任何问题,请随时在Github上提issue。另外,欢迎您对Crawlab作开发贡献。

往期文章

本篇文章由ArtiPub自动发布, ArtiPub让您的文章随处可阅

相关文章
相关标签/搜索