什么是Druid
Druid是一個(gè)高效的數(shù)據(jù)查詢系統(tǒng),主要解決的是對(duì)于大量的基于時(shí)序的數(shù)據(jù)進(jìn)行聚合查詢。數(shù)據(jù)可以實(shí)時(shí)攝入,進(jìn)入到Druid后立即可查,同時(shí)數(shù)據(jù)是幾乎是不可變。通常是基于時(shí)序的事實(shí)事件,事實(shí)發(fā)生后進(jìn)入Druid,外部系統(tǒng)就可以對(duì)該事實(shí)進(jìn)行查詢。
Druid系統(tǒng)架構(gòu)
Druid是一組系統(tǒng),按照職責(zé)分成不同的角色。目前存在五種節(jié)點(diǎn)類型:
- Historical: 歷史節(jié)點(diǎn)的職責(zé)主要是對(duì)歷史的數(shù)據(jù)進(jìn)行存儲(chǔ)和查詢,歷史節(jié)點(diǎn)從Deep Storage下載Segment,然后響應(yīng)Broker對(duì)于Segment的查詢將查詢結(jié)果返回給Broker節(jié)點(diǎn),它們通過(guò)Zookeeper來(lái)聲明自己存儲(chǔ)的節(jié)點(diǎn),同時(shí)也通過(guò)zookeeper來(lái)監(jiān)聽(tīng)加載或刪除Segment的信號(hào)。
- Coordinator:協(xié)調(diào)節(jié)點(diǎn)監(jiān)測(cè)一組歷史節(jié)點(diǎn)來(lái)保證數(shù)據(jù)的可用和冗余。協(xié)調(diào)節(jié)點(diǎn)讀取元數(shù)據(jù)存儲(chǔ)來(lái)確定哪些Segment需要load到集群中,通過(guò)zk來(lái)感知Historical節(jié)點(diǎn)的存在,通過(guò)在Zookeeper上創(chuàng)建entry來(lái)和Historical節(jié)點(diǎn)通信來(lái)告訴他們加載或者刪除Segment
- Broker:節(jié)點(diǎn)接收外部客戶端的查詢,并且將查詢路由到歷史節(jié)點(diǎn)和實(shí)時(shí)節(jié)點(diǎn)。當(dāng)Broker收到返回的結(jié)果的時(shí)候,它將結(jié)果merge起來(lái)然后返回給調(diào)用者。Broker通過(guò)Zook來(lái)感知實(shí)時(shí)節(jié)點(diǎn)和歷史節(jié)點(diǎn)的存在。
- Indexing Service: 索引服務(wù)是一些worker用來(lái)從實(shí)時(shí)獲取數(shù)據(jù)或者批量插入數(shù)據(jù)。
- Realtime:獲取實(shí)時(shí)數(shù)據(jù)
下面這張圖片展示了一次查詢數(shù)據(jù)在整個(gè)架構(gòu)中的流向

除了上述五個(gè)節(jié)點(diǎn),Druid還有三個(gè)外部依賴:
- Zookeeper集群
- 元數(shù)據(jù)存儲(chǔ)實(shí)例:Mysql
- Deep Storage:HDFS
Segments
Druid 把它的索引存儲(chǔ)到一個(gè)Segment文件中,Segment文件是通過(guò)時(shí)間來(lái)分割的。
Segment數(shù)據(jù)結(jié)構(gòu)
對(duì)于攝入到Druid的數(shù)據(jù)的列,主要分三種類型,時(shí)間列,指標(biāo)列和維度列。如下圖

對(duì)于時(shí)間列和指標(biāo)列處理比較簡(jiǎn)單,直接用LZ4壓縮存起來(lái)就ok,一旦查詢知道去找哪幾行,只需要將它們解壓,然后用相應(yīng)的操作符來(lái)操作它們就可以了。維度列就沒(méi)那么簡(jiǎn)單了,因?yàn)樗鼈冃枰贿^(guò)濾和聚合,因此每個(gè)維度需要下面三個(gè)數(shù)據(jù)結(jié)構(gòu)。
- 一個(gè)map,Key是維度的值,值是一個(gè)整型的id
- 一個(gè)存儲(chǔ)列的值得列表,用1中的map編碼的list
- 對(duì)于列中的每個(gè)值對(duì)應(yīng)一個(gè)bitmap,這個(gè)bitmap用來(lái)指示哪些行包含這個(gè)個(gè)值。
對(duì)于上圖的Page列,它的存儲(chǔ)是這樣的
1: 字典
{
"Justin BIeber": 0,
"Ke$ha": 1
}
2. 值的列表
[0,
0,
1,
1]
3. bitMap
value="Justin Bieber": [1, 1, 0, 0]
value="Ke$ha": [0, 0, 1, 1]
歷史節(jié)點(diǎn)
每個(gè)歷史節(jié)點(diǎn)維持一個(gè)和Zookeeper的長(zhǎng)連接監(jiān)測(cè)一組path來(lái)獲取新的Segment信息。歷史節(jié)點(diǎn)互相不進(jìn)行通信,他們依靠zk來(lái)等待協(xié)調(diào)節(jié)點(diǎn)來(lái)協(xié)調(diào)。
協(xié)調(diào)節(jié)點(diǎn)負(fù)責(zé)把新的Segment分發(fā)給歷史節(jié)點(diǎn),協(xié)調(diào)節(jié)點(diǎn)通過(guò)在zk的指定路徑下創(chuàng)建一個(gè)entry來(lái)向歷史節(jié)點(diǎn)做分發(fā)。
當(dāng)歷史節(jié)點(diǎn)發(fā)現(xiàn)一個(gè)新的entry出現(xiàn)在path中,它首先會(huì)檢查本地文件緩存看有有沒(méi)Segment信息,如果沒(méi)有Segment信息,歷史節(jié)點(diǎn)會(huì)從zk上下載新的Segment的元信息。Segment的元信息包括Segment存在Deep Storage的位置和如何解壓和處理Segment。一旦一個(gè)歷史節(jié)點(diǎn)完成對(duì)一個(gè)Segment的處理,這個(gè)歷史節(jié)點(diǎn)會(huì)在zk上的一個(gè)路徑聲明對(duì)這個(gè)Segment提供查詢服務(wù),此刻這個(gè)Segment就可以查詢了。
查詢節(jié)點(diǎn)
Broker節(jié)點(diǎn)負(fù)責(zé)將查詢路由到歷史節(jié)點(diǎn)和實(shí)時(shí)節(jié)點(diǎn),Broker節(jié)點(diǎn)通過(guò)zk來(lái)知道哪些Segment存在哪個(gè)節(jié)點(diǎn)上。Broker也會(huì)把查詢的結(jié)果進(jìn)行Merge
大多數(shù)Druid查詢包含一個(gè)區(qū)間對(duì)象,這個(gè)對(duì)象用來(lái)指定查詢所要查的區(qū)間段。Druid的Segment也通過(guò)時(shí)間段進(jìn)行分割散落在整個(gè)集群中。假設(shè)有一個(gè)簡(jiǎn)單的數(shù)據(jù)源,這個(gè)數(shù)據(jù)源有七個(gè)Segment,每個(gè)Segment包含一周中的某一天的數(shù)據(jù)。任何一個(gè)時(shí)間范圍超過(guò)一天的查詢都會(huì)落到不止一個(gè)Segment上。這些Segment可能分布在集群中不同的節(jié)點(diǎn)上。因此這種查詢就會(huì)涉及到多個(gè)節(jié)點(diǎn)。
為了確定發(fā)送到哪個(gè)節(jié)點(diǎn)上,Broker會(huì)從Historial和RealTime的節(jié)點(diǎn)來(lái)獲取他們提供查詢的Segment的信息,然后構(gòu)建一個(gè)時(shí)間軸,當(dāng)收到特定的時(shí)間區(qū)間的查詢時(shí),Broker通過(guò)時(shí)間軸來(lái)選擇節(jié)點(diǎn)。
Broker節(jié)點(diǎn)會(huì)維護(hù)一個(gè)LRU緩存,緩存存著每個(gè)Segment的結(jié)果,緩存可以是一個(gè)本地的緩存或者多個(gè)節(jié)點(diǎn)共用的外部的緩存如 memcached。當(dāng)Broker收到查詢時(shí)候,它首先將查詢映射成一堆Segment的查詢,其中的一個(gè)子集的結(jié)果可能已經(jīng)存在緩存中,他們可以直接從緩存中拉出來(lái),那些沒(méi)在緩存中的將被發(fā)送到相應(yīng)節(jié)點(diǎn)。
協(xié)調(diào)節(jié)點(diǎn)
協(xié)調(diào)節(jié)點(diǎn)負(fù)責(zé)Segment的管理和分發(fā),協(xié)調(diào)節(jié)點(diǎn)指揮歷史節(jié)點(diǎn)來(lái)加載或者刪除Segment,以及Segment的冗余和平衡Segment。協(xié)調(diào)節(jié)點(diǎn)會(huì)周期性的進(jìn)行掃描,每次掃描會(huì)根據(jù)集群當(dāng)前的狀態(tài)來(lái)決定進(jìn)一步的動(dòng)作。和歷史節(jié)點(diǎn)和Broker一樣,協(xié)調(diào)節(jié)點(diǎn)通過(guò)zk來(lái)獲取Segment信息,同時(shí)協(xié)調(diào)節(jié)點(diǎn)還通過(guò)數(shù)據(jù)庫(kù)來(lái)獲取可用的Segment信息和規(guī)則。在一個(gè)Segment提供查詢之前,可用的歷史節(jié)點(diǎn)會(huì)按照容量去排序,容量最小的具有最高的優(yōu)先級(jí),協(xié)調(diào)節(jié)點(diǎn)就會(huì)讓它去加載這個(gè)Segment然后提供服務(wù)。
- 清理Segment,Druid會(huì)將集群中的Segment和數(shù)據(jù)庫(kù)中的Segment進(jìn)行對(duì)比,如果集群有的的數(shù)據(jù)庫(kù)中沒(méi)有的會(huì)被清理掉。同事那些老的被新的替換的Segment也會(huì)被清理掉。
- Segment可用性, 歷史節(jié)點(diǎn)可能因?yàn)槟撤N原因不可用,協(xié)調(diào)節(jié)點(diǎn)會(huì)發(fā)現(xiàn)節(jié)點(diǎn)不可用了,會(huì)將這個(gè)節(jié)點(diǎn)上的Segment轉(zhuǎn)移到其他的節(jié)點(diǎn)。Segment不會(huì)立即被轉(zhuǎn)移,如果在配置的時(shí)間段內(nèi)節(jié)點(diǎn)恢復(fù)了,歷史節(jié)點(diǎn)會(huì)從本地緩存加載Segment。恢復(fù)服務(wù)
- Segment負(fù)載均衡,協(xié)調(diào)節(jié)點(diǎn)會(huì)找到Segment最多的節(jié)點(diǎn)和Segment最少的節(jié)點(diǎn),當(dāng)他們的比例超過(guò)一個(gè)設(shè)定的值的時(shí)候,協(xié)調(diào)節(jié)點(diǎn)會(huì)從Segment最多的節(jié)點(diǎn)轉(zhuǎn)移到Segment最少的節(jié)點(diǎn)。
索引服務(wù)
索引服務(wù)是一個(gè)高可用的,分布式的服務(wù)來(lái)運(yùn)行索引相關(guān)的Task。索引服務(wù)會(huì)創(chuàng)建或者銷毀Segment。索引服務(wù)是一個(gè)Master/Slave架構(gòu)。索引服務(wù)是三個(gè)組件的集合
- peon組件用來(lái)跑索引任務(wù)。
- Middle Manager組件用來(lái)管理peons
- Overlord向MiddleManager分發(fā)任務(wù)。
索引服務(wù)

Overlord節(jié)點(diǎn)負(fù)責(zé)接受任務(wù),協(xié)調(diào)任務(wù)分發(fā),創(chuàng)建鎖,和返回狀態(tài)給調(diào)用者。Overlord節(jié)點(diǎn)可以以本地模式或者遠(yuǎn)程模式運(yùn)行。本地模式會(huì)直接創(chuàng)建Peon,遠(yuǎn)程模式會(huì)通過(guò)Middle Manager創(chuàng)建任務(wù)。
實(shí)時(shí)節(jié)點(diǎn)
實(shí)時(shí)節(jié)點(diǎn)提供實(shí)時(shí)索引服務(wù),通過(guò)實(shí)時(shí)節(jié)點(diǎn)索引的數(shù)據(jù)立即可查。實(shí)時(shí)節(jié)點(diǎn)會(huì)周期性的構(gòu)建Segment,并且把這些Segment推到歷史節(jié)點(diǎn)并修改元數(shù)據(jù)。
