Apache Druid 的集羣設計與工做流程

導讀:本文將描述 Apache Druid 的基本集羣架構,說明架構中各進程的做用。並從數據寫入和數據查詢兩個角度來講明 Druid 架構的工做流程。html

關注公衆號 MageByte,設置星標點「在看」是咱們創造好文的動力。公衆號後臺回覆 「加羣」 進入技術交流羣獲更多技術成長。算法

Druid 是多進程架構,每種進程類型均可以獨立配置,獨立擴展。這樣能夠爲集羣提供最大的靈活度。這種設計還提供了強失效容忍:一個失效的組件不會當即影響另外的組件。sql

下面咱們來深刻了解 Druid 有哪些進程類型,每種進程又在整個集羣中扮演什麼角色。數據庫

進程和服務(Process and Servers)

Druid 有多種進程類型,以下:apache

  • Coordinator進程在集羣中負責管理數據可用。
  • Overlord進程控制數據攝入的資源負載分配。
  • Broker進程處理外部客戶端的查詢。
  • Router進程是可選的,它能夠路由請求到 Brokers,Coordinator,和 Overlord。
  • Historical進程存儲可查詢的數據。
  • MiddleManager進程負責數據攝入。

你能夠以任何方式來部署上面的進程。可是爲了易於運維,官方建議如下面三種服務類型來組織進程:Master、Query 和 Data。服務器

  • Master:運行 Coordinator 和 Overlord 進程,管理數據可用和數據寫入。
  • Query: 運行 Broker 和可選的 Router 進程,負責處理外部查詢請求。
  • Data:運行 Historical 和 MiddleManager 進程,負責執行數據寫入任務並存儲可查詢的數據。

外部依賴(External dependencies)

除了內置的進程類型,Druid 還有三個外部依賴項。網絡

Deep storage

共享文件存儲,只要配置成容許 Druid 訪問便可。在集羣部署中,一般使用分佈式存儲(如 S3 或 HDFS)或掛載網絡文件系統。在單機部署中,一般使用本地磁盤。Druid 使用 Deep Storage 存儲寫入集羣的數據。架構

Druid 僅將 Deep Storage 用做數據的備份,並做爲 Druid進程間在後臺的數據傳輸方式。要響應查詢,Historical 進程並不從 Deep Storage 上讀取數據,在任何查詢以前,先從本地磁盤查詢已經存在的數據。這意味着,Druid 在查詢時並不須要訪問 Deep Storage,這樣就能夠獲得最優的查詢延遲。這也意味着,在 Deep Storage 和 Historical 進程間你必須有足夠的磁盤空間來存儲你計劃加載的數據。app

Deep Storage 是 Druid 彈性、容錯設計的重要組成部分。若是 Druid 單機進程本地數據丟失,能夠從 Deep Storage 恢復數據。運維

Metadata storage

元數據存儲,存儲各類共享的系統元數據,如 segment 可用性信息和 task 信息。在集羣部署中,一般使用傳統的 RDBMS,如 PostgreSQL 或 MySQL。在單機部署中,一般使用本地存儲,如 Apache Derby 數據庫。

Zookeeper

用來進行內部服務發現,協調,和主選舉。

架構圖(Architecture diagram)

下圖能夠看出使用官方建議的 Master/Query/Data 服務部署方式,查詢和寫入數據是如何進行的:

druid-architecture

存儲設計(Storage design)

Datasources and segments

Druid 數據存儲在"datasources"中,它就像 RDBMS 中的 table。每個 datasources 經過時間分區,或經過其餘屬性進行分區。每個時間範圍稱之爲"chunk"(好比,一天一個,若是你的 datasource 使用 day 分區)。在 chunk 中,數據被分區進一個或多個"segments"中。每個 segment 是一個單獨的文件,一般包含數百萬行數據。一旦 segment 被存儲進 chunks,其組織方式將如如下時間線所示:

druid-timeline

一個 datasource 也許只有一個,也可能有數十萬甚至上百萬個 segment。每一個 segment 生命週期開始於 MiddleManager 建立時,剛被建立時,segment 是可變和未提交的。segment 構建過程包含如下幾步,旨在生成結構緊湊並支持快速查詢的數據文件。

  • 轉換成列格式
  • 使用 bitmap 建立索引
  • 使用各類算法壓縮數據
    • 爲 String 列作字典編碼,用最小化 id 存儲
    • 對 bitmap 索引作 bitmap 壓縮
    • 對全部列作類型感知壓縮

segment 定時提交和發佈。此時,數據被寫入 Deep Storage,而且再不可變,並從 MiddleManagers 進程遷移至 Historical 進程中。一個關於 segment 的 entry 將寫入 metadata storage。這個 entry 是關於 segment 的元數據的自描述信息,包含如 segment 的數據模式,大小,Deep Storage 地址等信息。這些信息讓 Coordinator 知道集羣中哪些數據是可用的。

索引和移交(Indexing and handoff)

indexing 是每一個 segment 建立的機制。handoff 是數據被髮布並開始能夠被 Historical 進程處理的機制。這機制在 indexing 側的工做順序以下:

  1. 啓動一個 indexing task 並構建一個新的 segment。在構建以前必須先肯定其標識。對於一個追加任務(如 kafka 任務,或 append 模式任務)能夠調用 Overlord 的"allocate"API 來將一個潛在的新分區加入到一個已經存在的 segment 中。對於一個覆寫任務(如 Hadoop 任務,或非 append 模式 index 任務) 將爲 interval 建立新版本號和新 segment。
  2. 若是 indexing 任務是實時任務(如 Kafka 任務),此時 segment 能夠當即被查詢。數據是可用的,但仍是未發佈狀態。
  3. 當 indexing 任務完成讀取 segment 數據時,它將數據推送到 Deep Storage 上,並經過向 metadata store 寫一個記錄來發布數據。
  4. 若是 indexing 任務是實時任務,此時,它將等待 Historical 進程加載這個 segment。若是 indexing 任務不是實時任務,就當即退出。

這機制在 Coordinator/Historical 側的工做以下:

  1. Coordinator 按期從 metadata storage 拉取已經發布的 segments(默認,每分鐘執行)。
  2. 當 Coordinate 發現已發佈但不可用的 segment 時,它將選擇一個 Historical 進程去加載 segment,並指示 Historical 該作什麼。
  3. Historical 加載 segment 併爲其提供服務。
  4. 此時,若是 indexing 任務還在等待數據移交,就能夠退出。

數據寫入(indexing)和移交(handoff):

Apache Druid 的集羣設計與工做流程

段標識(Segment identifiers)

Segment 標識由下面四部分組成:

  • Datasource 名稱。
  • 時間間隔(segment 包含的時間間隔,對應數據攝入時segmentGranularity指定參數)。
  • 版本號(一般是 ISO8601 時間戳,對應 segment 首次生成時的時間)。
  • 分區號(整數,在 datasource+interval+version 中惟一,不必定是連續的)。

例如,這是 datasource 爲clarity-cloud0,時間段爲2018-05-21T16:00:00.000Z/2018-05-21T17:00:00.000Z,版本號爲2018-05-21T15:56:09.909Z,分區號爲 1 的標識符:

clarity-cloud0_2018-05-21T16:00:00.000Z_2018-05-21T17:00:00.000Z_2018-05-21T15:56:09.909Z_1

分區號爲 0(塊中的第一個分區)的 segment 省略了分區號,如如下示例所示,它是與前一個分區在同一時間塊中的 segment,但分區號爲 0 而不是 1:

clarity-cloud0_2018-05-21T16:00:00.000Z_2018-05-21T17:00:00.000Z_2018-05-21T15:56:09.909Z

段版本控制(segment versioning)

你可能想知道上一節中描述的「版本號」是什麼。

Druid 支持批處理模式覆寫。在 Driud 中,若是你要作的只是追加數據,那麼每一個時間塊只有一個版本。可是,當你覆蓋數據時,在幕後發生的事情是使用相同的數據源,相同的時間間隔,但版本號更高的方式建立了一組新的 segment。這向 Druid 系統的其他部分發出信號,代表應從羣集中刪除較舊的版本,而應使用新版本替換它。

對於用戶而言,切換彷佛是瞬間發生的,由於 Druid 經過先加載新數據(但不容許對其進行查詢)來處理此問題,而後在全部新數據加載完畢後,當即將新查詢切換到新 segment。而後,它在幾分鐘後刪除舊 segment。

段(segment)生命週期

每一個 segment 的生命週期都涉及如下三個主要領域:

  1. 元數據存儲區:一旦構建完 segment,就將 segment 元數據(小的 JSON 數據,一般不超過幾個 KB)存儲在 元數據存儲區中。將 segmnet 的記錄插入元數據存儲的操做稱爲發佈。而後將元數據中的use布爾值設置成可用。由實時任務建立的 segment 將在發佈以前可用,由於它們僅在 segment 完成時才發佈,而且不接受任何其餘數據。
  2. 深度存儲:segment 數據構建完成後,並在將元數據發佈到元數據存儲以前,當即將 segment 數據文件推送到深度存儲。
  3. 查詢的可用性:segment 可用於在某些 Druid 數據服務器上進行查詢,例如實時任務或Historical進程。

你可使用 Druid SQL sys.segments檢查當前 segment 的狀態 。它包括如下標誌:

  • is_published:若是 segment 元數據已發佈到存儲的元數據中,used則爲 true,此值也爲 true。
  • is_available:若是該 segment 當前可用於實時任務或Historical查詢,則爲 True。
  • is_realtime:若是 segment 在實時任務上可用,則爲 true 。對於使用實時寫入的數據源,一般會先設置成true,而後隨着 segment 的發佈和移交而變成false
  • is_overshadowed:若是該 segment 已發佈(used設置爲 true)而且被其餘一些已發佈的 segment 徹底覆蓋,則爲 true。一般,這是一個過渡狀態,處於此狀態的 segment 很快就會將其used標誌自動設置爲 false。

查詢處理

查詢首先進入Broker進程,Broker將得出哪些 segment 具備與該查詢有關的數據(segment 列表始終按時間規劃,也能夠根據其餘屬性來規劃,這取決於數據源的分區方式),而後,Broker將肯定哪些 HistoricalMiddleManager 正在爲這些 segment 提供服務,並將重寫的子查詢發送給每一個進程。Historical / MiddleManager 進程將接受查詢,對其進行處理並返回結果。Broker接收結果並將它們合併在一塊兒以獲得最終答案,並將其返回給客戶端。

Broker會分析每一個請求,優化查詢,儘量的減小每一個查詢必須掃描的數據量。相比於 Broker 過濾器作的優化,每一個 segment 內的索引結構容許 Druid 在查看任何數據行以前先找出哪些行(若是有)與過濾器集匹配。一旦 Druid 知道哪些行與特定查詢匹配,它就只會訪問該查詢所需的特定列。在這些列中,Druid 能夠在行與行之間跳過,從而避免讀取與查詢過濾器不匹配的數據。

所以,Druid 使用三種不一樣的技術來優化查詢性能:

  1. 檢索每一個查詢需訪問的 segment。

  2. 在每一個 segment 中,使用索引來標識查詢的行。

  3. 在每一個 segment 中,僅讀取與特定查詢相關的行和列。

其餘系列文章連接:

時間序列數據庫(TSDB)初識與選擇

十分鐘瞭解 Apache Druid

想了解更多數據存儲,時間序列,Druid 的知識,可關注個人公衆號。點「在看」是咱們創造好文的動力

公衆號 MageByte

相關文章
相關標籤/搜索