Druid.io系列3:Druid叢集節點

大樹葉發表於2018-04-30

1 Historical Node

Historical Node的職責單一,就是負責載入Druid中非實時視窗內且滿足載入規則的所有歷史資料的Segment。每一個Historical Node只與Zookeeper保持同步,不與其他型別節點或者其他Historical Node進行通訊。

根據上節知曉,Coordinator Nodes會定期(預設為1分鐘)去同步元資訊庫,感知新生成的Segment,將待載入的Segment資訊儲存在Zookeeper中線上的Historical Nodes的load queue目錄下,當Historical Node感知到需要載入新的Segment時,首先會去本地磁碟目錄下查詢該Segment是否已下載,如果沒有,則會從Zookeeper中下載待載入Segment的元資訊,此元資訊包括Segment儲存在何處、如何解壓以及如何如理該Segment。Historical Node使用記憶體檔案對映方式將index.zip中的XXXXX.smoosh檔案載入到記憶體中,並在Zookeeper中本節點的served segments目錄下宣告該Segment已被載入,從而該Segment可以被查詢。對於重新上線的Historical Node,在完成啟動後,也會掃描本地儲存路徑,將所有掃描到的Segment載入如記憶體,使其能夠被查詢。

2 Broker Node

Broker Node是整個叢集查詢的入口,作為查詢路由角色,Broker Node感知Zookeeper上儲存的叢集內所有已釋出的Segment的元資訊,即每個Segment儲存在哪些儲存節點上,Broker Node為Zookeeper中每個dataSource建立一個timeline,timeline按照時間順序描述了每個Segment的存放位置。我們知道,每個查詢請求都會包含dataSource以及interval資訊,Broker Node根據這兩項資訊去查詢timeline中所有滿足條件的Segment所對應的儲存節點,並將查詢請求發往對應的節點。

對於每個節點返回的資料,Broker Node預設使用LRU快取策略;對於叢集中存在多個Broker Node的情況,Druid使用memcached共享快取。對於Historical Node返回的結果,Broker Node認為是“可信的”,會快取下來,而Real-Time Node返回的實時視窗內的資料,Broker Node認為是可變的,“不可信的”,故不會快取。所以對每個查詢請求,Broker Node都會先查詢本地快取,如果不存在才會去查詢timeline,再向相應節點傳送查詢請求。

3 Coordinator Node

Coordinator Node主要負責Druid叢集中Segment的管理與釋出,包括載入新Segment、丟棄不符合規則的Segment、管理Segment副本以及Segment負載均衡等。如果叢集中存在多個Coordinator Node,則通過選舉演算法產生Leader,其他Follower作為備份。

Coordinator會定期(預設一分鐘)同步Zookeeper中整個叢集的資料拓撲圖、元資訊庫中所有有效的Segment資訊以及規則庫,從而決定下一步應該做什麼。對於有效且未分配的Segment,Coordinator Node首先按照Historical Node的容量進行倒序排序,即最少容量擁有最高優先順序,新的Segment會優先分配到高優先順序的Historical Node上。由3.3.4.1節可知,Coordinator Node不會直接與Historical Node打交道,而是在Zookeeper中Historical Node對應的load queue目錄下建立待載入Segment的臨時資訊,等待Historical Node去載入該Segment。

Coordinator在每次啟動後都會對比Zookeeper中儲存的當前資料拓撲圖以及元資訊庫中儲存的資料資訊,所有在叢集中已被載入的、卻在元資訊庫中標記為失效或者不存在的Segment會被Coordinator Node記錄在remove list中,其中也包括我們在3.3.3節中所述的同一Segment對應的新舊version,舊version的Segments同樣也會被放入到remove list中,最終被邏輯丟棄。

對於離線的Historical Node,Coordinator Node會預設該Historical Node上所有的Segment已失效,從而通知叢集內的其他Historical Node去載入該Segment。但是,在生產環境中,我們會遇到機器臨時下線,Historical Node在很短時間內恢復服務的情況,那麼如此“簡單粗暴”的策略勢必會加重整個叢集內的網路負載。對於這種場景,Coordinator會為叢集內所有已丟棄的Segment儲存一個生存時間(lifetime),這個生存時間表示Coordinator Node在該Segment被標記為丟棄後,允許不被重新分配最長等待時間,如果該Historical Node在該時間內重新上線,則Segment會被重新置為有效,如果超過該時間則會按照載入規則重新分配到其他Historical Node上。

考慮一種最極端的情況,如果叢集內所有的Coordinator Node都停止服務,整個叢集對外依然有效,不過新Segment不會被載入,過期的Segment也不會被丟棄,即整個叢集內的資料拓撲會一直保持不變,直到新的Coordinator Node服務上線。

4 Indexing Service

Indexing Service是負責“生產”Segment的高可用、分散式、Master/Slave架構服務。主要由三類元件構成:負責執行索引任務(indexing task)的Peon,負責控制Peon的MiddleManager,負責任務分發給MiddleManager的Overlord;三者的關係可以解釋為:Overlord是MiddleManager的Master,而MiddleManager又是Peon的Master。其中,Overlord和MiddleManager可以分散式部署,但是Peon和MiddleManager預設在同一臺機器上。圖3.5給出了Indexing Service的整體架構。

Overlord
Overlord負責接受任務、協調任務的分配、建立任務鎖以及收集、返回任務執行狀態給呼叫者。當叢集中有多個Overlord時,則通過選舉演算法產生Leader,其他Follower作為備份。

Overlord可以執行在local(預設)和remote兩種模式下,如果執行在local模式下,則Overlord也負責Peon的建立與執行工作,當執行在remote模式下時,Overlord和MiddleManager各司其職,根據圖3.6所示,Overlord接受實時/批量資料流產生的索引任務,將任務資訊註冊到Zookeeper的/task目錄下所有線上的MiddleManager對應的目錄中,由MiddleManager去感知產生的新任務,同時每個索引任務的狀態又會由Peon定期同步到Zookeeper中/Status目錄,供Overlord感知當前所有索引任務的執行狀況。

Overlord對外提供視覺化介面,通過訪問https://:/console.html,我們可以觀察到叢集內目前正在執行的所有索引任務、可用的Peon以及近期Peon完成的所有成功或者失敗的索引任務。

MiddleManager
MiddleManager負責接收Overlord分配的索引任務,同時建立新的程式用於啟動Peon來執行索引任務,每一個MiddleManager可以執行多個Peon例項。

在執行MiddleManager例項的機器上,我們可以在${ java.io.tmpdir}目錄下觀察到以XXX_index_XXX開頭的目錄,每一個目錄都對應一個Peon例項;同時restore.json檔案中儲存著當前所有執行著的索引任務資訊,一方面用於記錄任務狀態,另一方面如果MiddleManager崩潰,可以利用該檔案重啟索引任務。

Peon
Peon是Indexing Service的最小工作單元,也是索引任務的具體執行者,所有當前正在執行的Peon任務都可以通過Overlord提供的web視覺化介面進行訪問。

這裡寫圖片描述

5 Real-Time Node

實時節點主要負責實時資料攝入,以及生成Segment檔案,有兩種資料處理模式,一種為Stream Push,另一種為Stream Pull。

  • Stream Pull

實時節點通過Firehose來消費實時資料,Firehose是Druid中的消費實時資料模型,可以有不同的實現,Druid自帶了一個基於Kafka High Level API實現的對於Kafka的資料消費(druid-kafka-eight Firehose)。除了Firehose,實時節點上還有一個重要的角色叫Plumber,主要負責按照指定的週期,對資料檔案進行合併。

如果Druid以Stream Pull方式自主地從外部資料來源拉取資料從而生成Indexing Service Tasks,我們則需要建立Real-Time Node。Real-Time Node主要包含兩大“工廠”:一個是連線流式資料來源、負責資料接入的Firehose(中文翻譯為水管,很形象地描述了該元件的職責);另一個是負責Segment釋出與轉移的Plumber(中文翻譯為搬運工,同樣也十分形象地描述了該元件的職責)。在Druid原始碼中,這兩個元件都是抽象工廠方法,使用者可以根據自己的需求建立不同型別的Firehose或者Plumber。Firehose和Plumber給我的感覺,更類似於Kafka_0.9.0版本後釋出的Kafka Connect框架,Firehose類似於Kafka Connect Source,定義了資料的入口,但並不關心接入資料來源的型別;而Plumber類似於Kafka Connect Sink,定義了資料的出口,也不關心最終輸出到哪裡。

下面就講講druid-kafka-eight的原理,結構和一些問題。

當使用druid-kafka-eight從Kafka進行資料消費時,該Firehose可以讓實時節點具有很好的可擴充套件性。當啟動多個實時節點時,將使用Kafka Consumer Group的方式從Kafka進行資料獲取,通過Zookeeper來維護每個節點的offset情況,無論是增加節點,還是刪除節點,通過High API都可以保證Kafka的資料至少被Druid的叢集消費一次。Kafka Consumer Group的詳細說明,請參考:http://blog.csdn.net/eric_sunah/article/details/44243077

通過druid-kafka-eight實現的高可用機制,可用下圖進行表示: 
這裡寫圖片描述

通過druid-kafka-eight保證的高可用,仔細分析可以發現會存在生成的segment檔案不能被傳到Deepstorage的缺陷,解決該問題可以通過兩個辦法

  1. 重啟實時節點
  2. 使用Tranquility+Index Service的方式對Kafka的資料進行精確的消費與備份。由於Tranquility可以通過Push的方式將制定的資料推到Druid叢集,一次它可以對同一個Partition資料建立多個副本,當某個資料消費任務失敗時,系統可以準確的使用另外一個相同任務所建立的Segment資料塊。
 更多的資訊在這裡:http://blog.csdn.net/eric_sunah/article/details/44243077

  • Stream Push
      如果採用Stream Push策略,我們需要建立一個“copy service”,負責從資料來源中拉取資料並生成Indexing Service Tasks,從而將資料“推入”到Druid中,我們在druid_0.9.1版本之前一直使用的是這種模式,不過這種模式需要外部服務Tranquility,Tranquility元件可以連線多種流式資料來源,比如Spark-Streaming、Storm以及Kafka等,所以也產生了Tranquility-Storm、Tranquility-Kafka等外部元件。Tranquility-Kafka的原理與使用將在3.4節中進行詳細介紹。

6 外部擴充

Druid叢集依賴一些外部元件,與其說依賴,不如說正是由於Druid開放的架構,所以使用者可以根據自己的需求,使用不同的外部元件。

Deep Storage
Druid目前支援使用本地磁碟(單機模式)、NFS掛載磁碟、HDFS、Amazon S3等儲存方式儲存Segments以及索引任務日誌。

Zookeeper
Druid使用Zookeeper作為分散式叢集內部的通訊元件,各類節點通過Curator Framework將例項與服務註冊到Zookeeper上,同時將叢集內需要共享的資訊也儲存在Zookeeper目錄下,從而簡化叢集內部自動連線管理、leader選舉、分散式鎖、path快取以及分散式佇列等複雜邏輯。

Metadata Storage
Druid叢集元資訊使用MySQL 或者PostgreSQL儲存,單機版使用derby。在Druid_0.9.1.1版本中,元資訊庫druid主要包含十張表,均以“druid_”開頭,如圖3.7所示。
這裡寫圖片描述
這裡寫圖片描述
這裡寫圖片描述
這裡寫圖片描述
這裡寫圖片描述
這裡寫圖片描述
這裡寫圖片描述
這裡寫圖片描述
這裡寫圖片描述

7 載入資料

對於載入外部資料,Druid支援兩種模式:實時流(real-time ingestion)和批量匯入(batch ingestion)。

Real-Time Ingestion
實時流過程可以採用Apache Storm、Apache Spark Streaming等流式處理框架產生資料,再經過pipeline工具,比如Apache Kafka、ActiveMQ、RabbitMQ等訊息匯流排類元件,使用Stream Pull 或者Stream Push模式生成Indexing Service Tasks,最終儲存在Druid中。

Batch Ingestion
批量匯入模式可以採用結構化資訊作為資料來源,比如JSON、Avro、Parquet格式的文字,Druid內部使用Map/Reduce批處理框架匯入資料。

8 高可用性

Druid高可用性可以總結以下幾點:

Historical Node
如3.3.4.1節中所述,如果某個Historical Node離線時長超過一定閾值,Coordinator Node會將該節點上已載入的Segments重新分配到其他線上的Historical Nodes上,保證滿足載入規則的所有Segments不丟失且可查詢。

Coordinator Node
叢集可配置多個Coordinator Node例項,工作模式為主從同步,採用選舉演算法產生Leader,其他Follower作為備份。當Leader當機時,其他Follower能夠迅速failover。
即使當所有Coordinator Node均失效,整個叢集對外依然有效,不過新Segments不會被載入,過期的Segments也不會被丟棄,即整個叢集內的資料拓撲會一直保持不變,直到新的Coordinator Node服務上線。

Broker Node
Broker Node與Coordinator Node在HA部署方面一致。

Indexing Service
Druid可以為同一個Segment配置多個Indexing Service Tasks副本保證資料完整性。

Real-Time
Real-Time過程的資料完整性主要由接入的實時流語義(semantics)決定。我們在0.9.1.1版本前使用Tranquility-Kafka元件接入實時資料,由於存在時間視窗,即在時間視窗內的資料會被提交給Firehose,時間視窗外的資料則會被丟棄;如果Tranquility-Kafka臨時下線,會導致Kafka中資料“過期”從而被丟棄,無法保證資料完整性,同時這種“copy service”的使用模式不僅佔用大量CPU與記憶體,又不滿足原子操作,所以在0.9.1.1版本後,我們使用Druid的新特性Kafka Indexing Service,Druid內部使用Kafka高階Consumer API保證exactly-once semantics,盡最大可能保證資料完整性。不過我們在使用中,依然發現有資料丟失問題。

Metadata Storage
如果Metadata Storage失效,Coordinator則無法感知新Segment的生成,整個叢集中資料拓撲亦不會改變,不過不會影響老資料的訪問。

Zookeeper
如果Zookeeper失效,整個叢集的資料拓撲不會改變,由於Broker Node快取的存在,所以在快取中的資料依然可以被查詢。

9 資料分層

Druid訪問控制策略採用資料分層(tier),有以下兩種用途:

將不同的Historical Node劃分為不同的group,從而控制叢集內不同許可權(priority)使用者在查詢時訪問不同group。

通過劃分tier,讓Historical Node載入不同時間範圍的資料。例如tier_1載入2016年Q1資料,tier_2載入2016年Q2資料,tier_3載入2016年Q3資料等;那麼根據使用者不同的查詢需求,將請求發往對應tier的Historical Node,不僅可以控制使用者訪問請求,同時也可以減少響應請求的Historical Node數量,從而加速查詢。

https://blog.csdn.net/eric_sunah/article/details/78563634

相關文章