Druid是一個專門針對事件數據的OLAP查詢而設計的開源數據存儲系統。此頁面旨在為讀者介紹關于Druid存儲數據的高級概述,以及Druid集群的架構。
數據
我們從一個在線廣告的示例數據集開始討論:
timestamp publisher advertiser gender country click price
2011-01-01T01:01:35Z bieberfever.com google.com Male USA 0 0.65
2011-01-01T01:03:63Z bieberfever.com google.com Male USA 0 0.62
2011-01-01T01:04:51Z bieberfever.com google.com Male USA 1 0.45
2011-01-01T01:00:00Z ultratrimfast.com google.com Female UK 0 0.87
2011-01-01T02:00:00Z ultratrimfast.com google.com Female UK 0 0.99
2011-01-01T02:00:00Z ultratrimfast.com google.com Female UK 1 1.53
熟悉OLAP的同學應該對這些概念肯定不會陌生,Druid也把數據集分為三個部分:
- Timestamp column(時間字段):將時間字段單獨處理,是因為Druid的所有查詢都是圍繞時間軸進行的。
- Dimension columns(維度字段): 維度字段是數據的屬性,一般被用來做數據的過濾。在示例數據集中有四個維度:publisher、advertiser、gender和 country。它們各自代表了我們用來分割數據的軸。
- Metric columns(度量字段):度量字段是用來做數據的聚合計算的。在示例中, click和price是倆個度量。度量是可以衡量的數據,一般可以做count、sum等操作。在OLAP術語中也叫measures。
Roll-up
在海量數據處理中,一般對于單條的細分數據并不感興趣,因為存在數萬億計這樣的事件。然而這類數據的統計匯總是很有用的,Druid通過roll-up過程的處理,使數據在攝取加載階段就做了匯總處理。Roll-up(匯總)是在維度過濾之前就做的第一級聚合操作。相等于如下偽代碼:
GROUP BY timestamp, publisher, advertiser, gender, country
:: impressions = COUNT(1), clicks = SUM(click), revenue = SUM(price)
原始數據壓縮聚合之后就是這個樣子:
timestamp publisher advertiser gender country impressions clicks revenue
2011-01-01T01:00:00Z ultratrimfast.com google.com Male USA 1800 25 15.70
2011-01-01T01:00:00Z bieberfever.com google.com Male USA 2912 42 29.18
2011-01-01T02:00:00Z ultratrimfast.com google.com Male UK 1953 17 17.31
2011-01-01T02:00:00Z bieberfever.com google.com Male UK 3194 170 34.01
可以看到,通過roll-up匯總數據后可以大大減少需要存儲的數據量(高達100倍)。Druid在接收數據的時候匯總數據,以最小化需要存儲的原始數據量。但是這種存儲減少是有代價的,當我們roll up數據后,就沒辦法再查詢詳細數據了。換句話說roll-up后的粒度就是你能夠探索數據的最小粒度,事件被分配到這個粒度。因此,Druid攝取規范將此粒度定義為數據的queryGranularity, 支持的最低queryGranularity是毫秒。
Sharding the Data
Druid的數據分片稱為 segments,druid總是最先通過時間來分片,上面例子中我們聚合后的數據,可以按小時分為倆分片:
Segment sampleData_2011-01-01T01:00:00:00Z_2011-01-01T02:00:00:00Z_v1_0包含
2011-01-01T01:00:00Z ultratrimfast.com google.com Male USA 1800 25 15.70
2011-01-01T01:00:00Z bieberfever.com google.com Male USA 2912 42 29.18
Segment sampleData_2011-01-01T02:00:00:00Z_2011-01-01T03:00:00:00Z_v1_0包含
2011-01-01T02:00:00Z ultratrimfast.com google.com Male UK 1953 17 17.31
2011-01-01T02:00:00Z bieberfever.com google.com Male UK 3194 170 34.01
segments是一個包含數據的獨立容器,內部數據以時間分割。segments為聚合的列做索引,數據依賴索引,按列方式存儲。 所以druid得查詢就轉為了如何掃描segments了。
segment 由datasource(數據源)、interval(間隔)、version(版本號)和一個可選的分區號唯一的確定。 例如上面例子中,我們的segment的名字就是這種格式dataSource_interval_version_partitionNumber。
Indexing the Data
Druid能夠取得這樣的查詢速度,主要取決于數據的存儲方式。借鑒google等搜索引擎的思路,Druid生成不可變的數據快照,存儲在針對分析查詢高度優化的數據結構中。
Druid是列式存儲也就意味著每一個列都是單獨存儲的。Druid查詢時只會使用到與查詢相關的列,而且Druid很好的支持了在查詢時只掃描其需要的。不同的列可以采用不同的壓縮方法,也能夠建立與它們相關的不同索引。
Druid在每一個分片級別(segment)建立索引。
Loading the Data
Druid有實時和批量兩種方式進行數據攝取。Druid中實時數據攝取方式是盡力而為。Druid暫時實時攝取暫時無法支持正好一次(Exactly once),當然在后續版本計劃中會盡量去支持。通過批量創建能夠準確映射到攝取數據的段,批量攝取提供了正好一次的保證。使用Druid的通用方式是用實時管道獲取實時數據,用批量管道處理副本數據。
Querying the Data
Druid的本地查詢語言是JSON通過HTTP,雖然社區在眾多的語言中提供了查詢庫,包括SQL查詢貢獻庫。
Druid設計用于單表操作,目前不支持join操作。因為加載到Druid中的數據需要規范化,許多產品準備在數據ETL(Extract-Transform-Load)階段進行join。
The Druid Cluster
Druid集群是由不同類型節點組成的,每個節點各司其職:
- Historical Nodes 歷史節點通常構成Druid集群的骨干,歷史節點在本地下載不可變的段,并對這些段進行查詢。這些節點有一個無共享的體系結構,并知道如何加載數據段、丟棄數據段,并對數據段進行查詢。
- Broker Nodes Broker節點是客戶端和應用從Druid查詢獲取數據的地方。Broker節點負責分配查詢并且收集和合并結果,Broker節點知道哪些段放在哪里。
- Coordinator Nodes Coordinator節點負責管理集群中歷史節點的段,協調器節點告訴歷史節點加載新的段、刪除舊的段并且移動段來達到負載均衡。
- Real-time Processing Druid中的實時處理可以使用獨立的實時節點或使用索引服務來完成。實時邏輯在這兩個服務之間是共同的。實時處理包括接收數據、索引數據(創建數據段)和將數據段傳輸到歷史節點。通過實時處理邏輯接收數據,數據就是可查詢的。切換過程也是無損的,數據在整個過程中保持可查詢。
外部依賴
Druid對集群操作有幾個外部依賴關系。
- Zookeeper Druid依賴zookeeper進行集群內部通信。
- Metadata Storage Druid依賴元數據存儲來存儲段和配置的元數據。創建段的服務向元數據存儲寫入新條目,并且協調器節點監視元數據存儲以知道何時需要加載新數據或需要丟棄舊數據。元數據存儲不包含在查詢路徑中。MySQL和PostgreSQL是生產系統中常用的元數據存儲數據庫。當在單機系統上運行所有Druid節點,可以使用Derby 數據庫。
- Deep Storage 深度存儲用作段的永久備份。創建段的服務將段上傳到深度存儲,歷史節點從深度節點上下載段。深度存儲也不包含在查詢路徑里。S3和HDFS是流行的深度存儲方式。
高可用性
Druid設計避免了單點故障。不同類型的節點故障并不會影響其他類型節點的服務。為了運行高可用的Druid集群,需要每個節點類型至少2個節點。