設計
德魯伊具有多進程、分佈式的架構,並且在設計爲雲友好且易操作的系統。每種Druid進程類型(指Druid框架之中的各種組件)都可以獨立配置和水平擴展,從而爲您的集羣提供最大的靈活性。這種設計還提高了容錯能力:一個組件的故障不會立即影響其他組件。
進程和服務器
Druid有幾種進程類型,簡述如下:
- Coordinator 協調器進程管理羣集上的數據可用性。
- Overlord 統治進程控制數據攝入工作負載的分配。
- Broker 代理進程處理來自外部客戶端的查詢。
- Router 路由進程是可選進程,可以將請求路由到代理,協調器和霸主。
- Historical 歷史進程存儲可查詢的數據。
- MiddleManager 中間管理進程負責攝取數據。
Druid進程可以按照您喜歡的任何方式進行部署,但是爲了便於部署,我們建議將其組織爲三種服務器類型:主服務器,查詢服務器和數據服務器。
- Master:運行協調器和統治流程,管理數據可用性和接收。
- Query:運行代理和可選的路由器進程,處理來自外部客戶端的查詢。
- Data:運行Historical和MiddleManager進程,執行提取工作負載並存儲所有可查詢的數據。
有關進程和服務器架構的更多詳細信息,請參閱Druid進程和服務器。
外部依賴
除了內置的進程類型外,Druid還具有三個外部依賴項。這些外部依賴項能夠利用現有的基礎結構。
Deep storage 深度儲存
每個Druid服務器均可訪問共享文件存儲。在集羣部署中,這通常將是分佈式對象存儲(例如S3或HDFS)或通過網絡掛載的文件系統。在單服務器部署中,這通常是本地磁盤。Druid使用deep storage來存儲已攝取到系統中的所有數據。
Druid僅將deep storage用作數據的備份,並作爲在Druid進程之間在後臺傳輸數據的方式(注:多個Druid實例可以通過Deep Storage 共享數據)。處理查詢請求的時候,歷史進程不會從深度存儲中讀取數據,而是會在提供任何查詢之前從其本地磁盤讀取預攝入的segments 。這意味着Druid不需要在查詢期間訪問deep storage,從而幫助它提供最短的查詢延遲。這也意味着在deep storage和 Historical進程中,必須有足夠的磁盤空間用於加載你計劃攝取的數據。
深度存儲是Druid的彈性,容錯設計的重要組成部分。即使每臺數據服務器都丟失並重新配置,Druid也可以從深度存儲中引導。
有關更多詳細信息,請參閱深度存儲頁面。
Metadata storage 元數據存儲
Metadata storage區包含各種共享的系統元數據,例如segment 可用性信息和task信息。在集羣部署中,這通常將是傳統的RDBMS,例如PostgreSQL或MySQL。在單服務器部署中,它通常將是本地存儲的Apache Derby數據庫。
有關更多詳細信息,請參見“ 元數據存儲”頁面。
ZooKeeper
用於內部服務發現,協調和leader選舉。
有關更多詳細信息,請參見ZooKeeper頁面。
Architecture diagram 架構圖
下圖顯示了使用建議的Master / Query / Data服務器組織的查詢和數據在整個體系結構中的流動方式:
存儲設計
Datasources 數據源和segments段
Druid數據存儲在“Datasources”中,類似於傳統RDBMS中的表。每個數據源都按時間分區,並且可以選擇按其他屬性進一步分區。每個時間範圍都稱爲“chunk”(例如,如果您的數據源按天劃分,則爲一天)。在一個chunk內,數據被劃分爲一個或多個 “segments”。每個segments都是單個文件,通常包含多達幾百萬行的數據。由於segments是按時間塊組織的,因此將細分視爲生活在時間線上的以下內容有時會有所幫助:
一個Datasources 可能具有從幾個段到數十萬甚至數百萬個segments。每個segments都是從在MiddleManager上創建開始的,在那個時候,segments是可變的且未提交的。爲了生成緊湊且支持快速查詢的數據文件,segments構建過程包括以下步驟,:
- 轉換爲列格式
- 使用位圖索引編制索引
- 使用各種算法進行壓縮
- 字符串列的ID存儲最小化的字典編碼
- 位圖索引的位圖壓縮
- 所有列的類型感知壓縮
segments會定期提交和發佈。發佈後,它們被寫入Deep storage ,變得不可變,並從MiddleManagers遷移到Historical處理。有關該segment的Entry也將寫入到元數據存儲中。Entry是有關該segment的元數據的自描述位,包括諸如segment的模式,其大小以及其在Deep storage上的位置之類的信息。這些條目是Coordinator用來了解集羣上應該有哪些數據的內容。
有關segments文件格式的詳細信息,請參閱段文件。
有關在Druid中對數據建模的詳細信息,請參見模式設計。
Indexing 索引 和handoff移交
索引 是創建新段的機制,移交是它們被髮布並開始由Historical進程提供服務的機制。該機制在索引端的工作方式如下:
- 一個索引任務開始運行,並建立一個新的segments。它必須在開始構建段之前確定該段的標識符。對於要追加的任務(例如Kafka任務,或追加(append)模式中的索引任務),可以通過在Overlord上調用“allocate” API來完成,以潛在地將新分區(partition )添加到現有的段集中。對於要覆蓋(overwriting )的任務(例如Hadoop任務或不在追加模式下的索引任務),這可以通過鎖定間隔(注:覆蓋的時間段)並創建新的版本號和新的段集來完成。
- 如果索引任務是實時任務(例如Kafka任務),則此時可以立即查詢該段。它可用,但尚未發佈。
- 索引任務完成對段的數據讀取後,會將其推入深度存儲,然後通過將記錄寫入元數據存儲來發布它。
- 如果索引任務是實時任務,則此時它等待“Historical”進程加載該段。如果索引任務不是實時任務,它將立即退出。
在Coordinator / Historical端,看起來像這樣:
- Coordinator 定期(默認情況下,每1分鐘)輪詢元數據存儲區以查找新發布的段。
- 當Coordinator 找到已發佈和使用但不可用的段時,它會選擇一個“Historical”進程並指示“Historical”來加載該段
- 歷史記錄會加載段並開始處理。
- 此時,如果索引任務正在等待切換,它將退出。
Segment identifiers 段標識符
段均具有包含以下部分的四部分標識符:
- 數據源名稱。
- 時間間隔(對於包含分段的時間塊;這對應
segmentGranularity
於攝取時指定的時間間隔)。 - 版本號(通常爲ISO8601時間戳,與首次啓動段集的時間相對應)。
- 分區號(整數,在數據源+間隔+版本內是唯一的;可能不一定是連續的)。
例如,這是datasource clarity-cloud0
,time chunk 2018-05-21T16:00:00.000Z/2018-05-21T17:00:00.000Z
,version 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(塊中的第一個分區)的段省略了分區號,如以下示例所示,它是與上一個分區在同一時間塊中的段,但分區號爲0而不是1:
clarity-cloud0_2018-05-21T16:00:00.000Z_2018-05-21T17:00:00.000Z_2018-05-21T15:56:09.909Z
段版本控制
您可能想知道上一節中描述的“版本號”是做什麼的。或者,您可能不是,在這種情況下您可以跳過本部分!
段版本控制是爲了支持批處理模式覆蓋。在Druid中,如果您要做的只是追加(append)數據,那麼每個時間塊只有一個版本。但是,當您覆蓋數據時,在幕後發生的事情是使用相同的數據源,相同的時間間隔,但版本號更高的方式創建了一組新的段。這向Druid系統的其餘部分發出信號,表明應從集羣中刪除較舊的版本,而應使用新版本替換它。
對於用戶而言,切換似乎是瞬間發生的,因爲Druid通過首先加載新數據(但不允許對其進行查詢)來處理此問題,然後在所有新數據加載完畢後,立即切換所有新查詢以使用這些新段。然後,它會在幾分鐘後刪除舊段。
段生命週期
每個部分的生命週期都涉及以下三個主要領域:
- 元數據存儲區:一旦構建完段,就將段元數據(一個小的JSON有效負載(payload ),通常不超過幾個KB)存儲在 元數據存儲區中。將段的記錄插入元數據存儲的操作稱爲發佈。這些元數據記錄具有一個名爲
used
的布爾標誌,用於控制該段是否可查詢。由實時任務創建的段將在發佈之前可用(available
),因爲它們僅在段完成時才發佈,並且不接受任何其他數據行。 - 深度存儲:分段數據構建完成後,會將分段數據文件推送到深度存儲。這在將元數據發佈到元數據存儲之前立即發生。
- 查詢的可用性:段可用於在某些Druid數據服務器上進行查詢,例如實時任務或歷史進程。
您可以使用Druid SQL sys.segments
表檢查當前活動段的狀態 。它包括以下標誌:
is_published
:如果段元數據已發佈到存儲的元數據中,used
爲true。is_available
:如果該段當前可用於實時任務或歷史流程查詢,則爲True。is_realtime
:如果分段僅在實時任務上可用,則爲true 。對於使用實時提取的數據源,通常會先開始true
,然後false
隨着段的發佈和移交而變成。is_overshadowed
:如果該段已發佈(used
設置爲true)並且被其他已發佈的段完全遮蓋,則爲true。通常,這是一個過渡狀態,處於此狀態的段很快就會將其used
標誌自動設置爲false。
查詢處理
查詢首先進入Broker,Broker將在其中識別哪些段具有與該查詢有關的數據。段列表始終按時間篩選,也可以根據其他屬性來篩選,這取決於數據源的分區方式。然後,Broker將確定哪些Historical 和 MiddleManager爲這些段提供服務,並將重寫的子查詢發送給每個進程。Historical / MiddleManager進程將接受查詢,對其進行處理並返回結果。Broker接收結果並將它們合併在一起以得到最終響應,並將其返回給原始調用方。
Broker篩選是Druid限制每個查詢必須掃描的數據量的一種重要方法,但這不是唯一的方法。對於比Broker可以使用的過濾器更細粒度的過濾器,每個段內的索引結構允許Druid在查看任何數據行之前先找出哪些行(如果有)與過濾器集匹配。一旦Druid知道哪些行與特定查詢匹配,它就只會訪問該查詢所需的特定列。在這些列中,Druid可以在行與行之間跳過,從而避免讀取與查詢過濾器不匹配的數據。
因此,Druid使用三種不同的技術來最大化查詢性能:
- 篩選每個查詢所需要訪問的段。
- 在每個段中,使用索引來標識必須訪問的行。
- 在每個段中,僅讀取與特定查詢相關的特定行和列。