爬蟲平臺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作開發貢獻。

往期文章

相關文章
相關標籤/搜索