druid 的基礎架構與應用

本文介紹了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部分的每一個維度都採用瞭如下三種數據結構作轉碼、存儲:

  1. A dictionary that maps values (which are always treated as strings) to integer IDs,
  2. For each distinct value in the column,a bitmap that indicates which rows contain that value,and
  3. A list of the column’s values,encoded using the dictionary in 1

舉個例子,源數據以下:

 

 

 

 

 

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引擎,從性能、穩定性上來講,都是很是不錯的。

相關文章
相關標籤/搜索