本文介紹了druid的基礎架構以及工做過程,經過一個應用案例加深瞭解。node
durid簡介web
druid是一種高性能、列式存儲、分佈式數據存儲的時序數據分析引擎。能支持「PB」級數據的秒級查詢。相似的產品有kylin/clickhouse。druid典型的應用就是OLAP場景下的cube組合查詢分析。如數據鑽取(Drill-down)、上卷(Roll-up)、切片(Slice)、切塊(Dice)以及旋轉(Pivot)。後面的應用示例章節再詳細闡述。json
durid基礎架構緩存
先來了解一下durid主要節點:數據結構
一、broker node(代理節點)架構
Broker節點扮演着歷史節點和實時節點的查詢路由的角色。主要負責接收外部查詢,轉發查詢至各個segment數據所在的節點,並聚合結果返回。負載均衡
二、historical node(歷史節點)分佈式
historical主要負責歷史數據存儲和查詢,接收協調節點數據加載與刪除指令,從deepstoage中下載segment,完成數據加載或者刪除後在zk中進行通告。歷史節點遵循shared-nothing的架構,所以節點間沒有單點問題。節點間是相互獨立的而且提供的服務也是簡單的,它們只須要知道如何加載、刪除和處理不可變的segment。historical節點也能夠進行分組,組合成不一樣的historical tier。這會在集羣規模較大的時候體現出優點。如作數據的冷熱分離,按不一樣業務的數據分離(必定程度的資源隔離)。固然,historical 節點是整個集羣查詢性能的核心所在,由於historical會承擔絕大部分的segment查詢。性能
三、coordinator node(協調節點) ui
主要負責數據的管理和在歷史節點上的分佈。協調節點告訴歷史節點加載新數據、卸載過時數據、複製數據、和爲了負載均衡移動數據。能夠配置load數據及drop數據規則。
四、overlord node(index service 能夠理解爲任務管理節點)
功能描述:負責接收任務,管理任務。接收外部http請求(新建任務、查詢任務狀態、kill任務等),分配管理任務(當有新的任務請求,overload node會將任務分配給middleManager node去執行)。
五、middleManager node(能夠理解爲overlord節點的工做節點)
功能描述:能夠啓動n(可配置)個peon,接收overlord分配的task,再交給本身peon去執行。
查詢過程
見上圖藍色箭頭,Broker節點接收到查詢(Q1),再將查詢發送給歷史節點與實時節點(Q2,Q3),在上圖的模式中,實時節點是MM節點上啓動的task。該task會負責數據的攝入以及提供實時數據的查詢。
數據攝入過程
見上圖紅色箭頭,D1是client生產數據最終寫入kafka(這個過程可能在client與kafka的中間,還包含了多個環節,如數據傳輸與數據清洗),D2和D3過程是部署tranquility-kafka服務,消費kafka數據寫入對應的task,tranquility-kakfa啓動的時候會跟overlord節點通訊,由overlord節點分配任務給middleManager執行。D4是task 負責的segment段正常結束,而後將segment數據寫入deepstorage過程。(實時task運行時間是segmentGranularity+windowPeriod+intermediatePersistPeriod)。D5則是historical節點從deepstorage下載segment並在zk中聲明負責該segment段查詢的過程。
目前druid數據攝入過程還有一種更推薦的方式就是kafka index service(簡稱kis),有興趣的同窗能夠參考官方文檔,kis對kafka的版本有強要求。
druid總體架構雖然略爲複雜,可是總體穩定性很是不錯,幾乎不多出現集羣故障。拋開集羣硬件故障和數據自己問題,SLA基本能到4個9。coordinator,overlord兩個節點是主從模式,保證每一個角色起兩個實例便可。broker節點無狀態,能夠起多個實例,前面掛個域名便可(爲了保證緩存命中,最好配置ip hash)。historical節點無狀態,有必定冗餘便可。middleManager用做數據攝入節點,若task沒有配置副本,則節點宕機會引起丟數據的風險。固然,kis能夠避免該問題。
durid數據聚合、存儲核心思想
druid 數據存儲分爲三部分timestamp、dimensions、metrics。其中,timestamp、metrics部分是採用lz4直接壓縮。
可是dimensions部分須要支持過濾查詢以及分組查詢。因此dimensions部分的每一個維度都採用瞭如下三種數據結構作轉碼、存儲:
舉個例子,源數據以下:
name列來講
1. Dictionary that encodes column values
字典表的key都是惟一的,因此Map的key是unique的column value,Map的value從0開始不斷增長。 示例數據的name列只有兩個不一樣的值。因此張三編號爲0,李四編號爲1:
{ "張三": 0 "李四": 1 }
2. Column data
要保存的是每一行中這一列的值,值是ID而不是原始的值。由於有了上面的Map字典,因此有下面的對應關係:
[0,
1,
1,
0]
3. Bitmaps - one for each unique value of the column
BitMap的key是第一步Map的key(原始值), value則是真假的一個標識(是|否?等於|不等於?),取值只有0、1,以下:
value="張三": [1,0,0,1]
value=「李四": [0,1,1,0]
因此由上可知最壞的狀況多是隨着數據量的增長,bitmap的個數也成線性增加,爲數據量大小*列的個數。那麼在什麼狀況下會致使這種線性增加?這裏咱們引入了一個基數(cardinality)的概念。基數=unique(dim1,dim2.....),如若dim取值均爲各類爆炸性id或者隨機數,則druid的預聚合將徹底失去意義。因此在druid的應用場景中,基數約小,聚合效率越高。
講了dimensions怎麼存儲,那麼metrics又是怎麼聚合(roll-up)呢?這就要引入druid數據schema定義了。下一章結合應用一塊看一個示例。
應用示例與實踐經驗
假設有這樣一份數據,典型的商品銷售數據。
咱們構形成druid中的數據schema以下:
{ "dataSources" : [ { "spec" : { "dataSchema" : { "dataSource" : "test_datasource", "granularitySpec" : { "segmentGranularity" : "hour", "queryGranularity" : "minute", "type" : "uniform" }, "parser" : { "type" : "string", "parseSpec" : { "format" : "json", "timestampSpec" : { "column" : "time", "format" : "auto" }, "dimensionsSpec" : { "dimensions" : [ "productName", "city", "channel", 「action"] } } }, "metricsSpec" : [ { "name" : "count", "type" : "count" }, { "type" : "doubleSum", "fieldName" : "price", "name" : 「sale" } ] }, "tuningConfig" : { "type" : "realtime", "windowPeriod" : "PT10M", "intermediatePersistPeriod" : "PT10M", "maxRowsInMemory" : "100000" } }, "properties" : { "topicPattern" : "test_datasource", "task.partitions" : "2", "task.replicants" : "1" } } ], "properties" : { ... } }
前面重點說了dimensions,咱們再來看下metrics。在上面的例子中咱們只定義count和針對price的doubleSum,那麼這些指標就已經固定了後期的分析需求。咱們看到上面table中的一二行標紅部分,全部dim取值徹底相同,queryGranularity爲一分鐘。那麼在這2018-06-11 12:23:00這個點,這兩行數據就被聚合成一行,count=2,sale=0。以此類推。
而後咱們再來看看具體的分析需求,一個鑽取的例子。咱們首先查看商品A昨天的點擊量,select sum(count) from table where productName=‘A’ and action=‘click',再想看看地區=北京,渠道=web呢?是否是再加幾個where就搞定了?select sum(count) from table where productName=‘A’ and city=‘北京’ and channel=‘web' and action=‘click’; 而後就是切片和切塊,也很簡單,就是幾個group by。這些在druid中都能很是輕鬆的支持。
具體使用上的經驗總結:
1. reindex思想。通常咱們實時數據查詢粒度配置的會比較小,秒級或者分鐘級。那麼對於一天前,三天前,一個月前的數據呢?這時候通常關注的粒度將再也不那麼細,因此咱們通常會採起redinx的策略進行再聚合
2. 針對歷史數據,可能對於某些維度將不在關心,這時候咱們也能夠在reindex時,將無用的維度剔除掉,可能大大減小總體數據的基數。
3. 通常數據壓縮比例。這裏提供一個大概的參考值。數據總基數在10W如下,天天數據量約百億左右,druid中聚合後的索引數據與原始數據大小之比能夠到1:100,甚至1:1000。
4. druid適用於常規的olap場景,能很是輕鬆的支撐天天百億甚至千億級別的數據寫入。
5. 爆炸性維度數據,以及頻繁update數據的需求,不適用於druid的場景。
總結
本文主要對druid作了入門級的基礎介紹,能夠給你們作olap引擎技術選型時作一個參考。以及對druid的初學者作一個大體介紹。druid是一款很是優秀的olap引擎,從性能、穩定性上來講,都是很是不錯的。