1 簡(jiǎn)介
Druid是針對(duì)時(shí)間序列數(shù)據(jù)提供低延時(shí)的數(shù)據(jù)寫入以及快速交互式查詢的分布式OLAP數(shù)據(jù)庫(kù)。
分布式OLAP數(shù)據(jù)庫(kù):
(1)ES-明細(xì)數(shù)據(jù)檢索(OLAP聚合分析支持不好)
(2)Kylin-預(yù)計(jì)算+kv存儲(chǔ)(預(yù)計(jì)算無法做到低延時(shí))
(3)Presto-可直接讀HDFS文件的查詢引擎
注意:如果需要使用到j(luò)oin,一般來說會(huì)在之前進(jìn)行一步流處理,將數(shù)據(jù)處理成一個(gè)寬表,再進(jìn)行Druid端計(jì)算。
Druid的核心架構(gòu),結(jié)合了數(shù)據(jù)倉(cāng)庫(kù),時(shí)間序列數(shù)據(jù)庫(kù),日志搜索系統(tǒng)。其中主要的特點(diǎn)有:
<1> 列式存儲(chǔ)格式
Druid使用列式存儲(chǔ),在特定查詢中只需要指定需要的列,這樣能夠給速度帶來很大的提升;另外,每列都針對(duì)其特定數(shù)據(jù)類型進(jìn)行了優(yōu)化存儲(chǔ),支持更快瀏覽和聚合。
<2> 可擴(kuò)展的分布式系統(tǒng)
<3> 大量并行計(jì)算
<4> 支持實(shí)時(shí)或者批量的數(shù)據(jù)攝入
<5> ‘自愈’,自動(dòng)平衡,容易維護(hù)
<6> 云原生的容錯(cuò)架構(gòu),不會(huì)丟失數(shù)據(jù)
一旦Druid攝入了數(shù)據(jù),就會(huì)產(chǎn)生副本存儲(chǔ)起來
<7> 支持索引快速過濾
Druid使用CONCISE或Roaring壓縮的bitmap索引來創(chuàng)建索引,以支持快速過濾和跨多列搜索。
<8> 基于時(shí)間的分區(qū)
Druid首先按時(shí)間對(duì)數(shù)據(jù)進(jìn)行分區(qū),然后可以根據(jù)其他字段進(jìn)行分區(qū)。 這意味著基于時(shí)間的查詢將僅訪問與查詢時(shí)間范圍匹配的分區(qū)。
<9> 近似算法
Druid包括用于近似計(jì)數(shù)區(qū)別,近似排名以及近似直方圖和分位數(shù)計(jì)算的算法。 這些算法提供有限的內(nèi)存使用量,通常比精確計(jì)算要快得多。 對(duì)于精度比速度更重要的情況,Druid還提供了精確的計(jì)數(shù)區(qū)別和精確的排名。
<10> 攝取時(shí)自動(dòng)匯總
Druid可選地(需要預(yù)先配置)在攝取時(shí)支持?jǐn)?shù)據(jù)匯總,這種匯總會(huì)部分地預(yù)先聚合您的數(shù)據(jù),并可以節(jié)省大量成本并提高性能。
2 基本概念
1.數(shù)據(jù)
按照列的不同類型,可以將數(shù)據(jù)分為以下三類:
(1) 時(shí)間序列(Timestamp):
Druid既是內(nèi)存數(shù)據(jù)庫(kù),又是時(shí)間序列數(shù)據(jù)庫(kù),Druid中所有查詢以及索引過程都和時(shí)間維度息息相關(guān)。Druid底層使用絕對(duì)毫秒數(shù)保存時(shí)間戳,默認(rèn)使用ISO-8601格式展示時(shí)間(形如:yyyy-MM-ddThh:mm:sss.SSSZ,其中“Z”代表零時(shí)區(qū),中國(guó)所在的東八區(qū)可表示為+08:00)。
(2)維度列(Dimensions),Druid的維度概念和OLAP中一致,一條記錄中的字符類型(String)數(shù)據(jù)可看作是維度列,維度列被用于過濾篩選(filter)、分組(group)數(shù)據(jù)。如圖3.1中page、Username、Gender、City這四列。
(3)度量列(Metrics),Druid的度量概念也與OLAP中一致,一條記錄中的數(shù)值(Numeric)類型數(shù)據(jù)可看作是度量列,度量列被用于聚合(aggregation)和計(jì)算(computation)操作。如圖3.1中的Characters Added、Characters Removed這兩列。
2.roll up
Druid會(huì)在攝入數(shù)據(jù)的時(shí)候,對(duì)原始數(shù)據(jù)進(jìn)行聚合操作。該過程稱為上卷(roll up)。Roll up可以看做是在選擇的列上進(jìn)行的第一級(jí)聚合操作,以減少sgements的數(shù)量。
以下面數(shù)據(jù)為例:
{"timestamp":"2018-01-01T01:01:35Z","srcIP":"1.1.1.1", "dstIP":"2.2.2.2","packets":20,"bytes":9024}
{"timestamp":"2018-01-01T01:01:51Z","srcIP":"1.1.1.1", "dstIP":"2.2.2.2","packets":255,"bytes":21133}
{"timestamp":"2018-01-01T01:01:59Z","srcIP":"1.1.1.1", "dstIP":"2.2.2.2","packets":11,"bytes":5780}
{"timestamp":"2018-01-01T01:02:14Z","srcIP":"1.1.1.1", "dstIP":"2.2.2.2","packets":38,"bytes":6289}
{"timestamp":"2018-01-01T01:02:29Z","srcIP":"1.1.1.1", "dstIP":"2.2.2.2","packets":377,"bytes":359971}
{"timestamp":"2018-01-01T01:03:29Z","srcIP":"1.1.1.1", "dstIP":"2.2.2.2","packets":49,"bytes":10204}
{"timestamp":"2018-01-02T21:33:14Z","srcIP":"7.7.7.7", "dstIP":"8.8.8.8","packets":38,"bytes":6289}
{"timestamp":"2018-01-02T21:33:45Z","srcIP":"7.7.7.7", "dstIP":"8.8.8.8","packets":123,"bytes":93999}
{"timestamp":"2018-01-02T21:35:45Z","srcIP":"7.7.7.7", "dstIP":"8.8.8.8","packets":12,"bytes":2818}
rollup-index.json
{
"type" : "index",
"spec" : {
"dataSchema" : {
"dataSource" : "rollup-tutorial",
"parser" : {
"type" : "string",
"parseSpec" : {
"format" : "json",
"dimensionsSpec" : {
"dimensions" : [
"srcIP",
"dstIP"
]
},
"timestampSpec": {
"column": "timestamp",
"format": "iso"
}
}
},
"metricsSpec" : [
{ "type" : "count", "name" : "count" },
{ "type" : "longSum", "name" : "packets", "fieldName" : "packets" },
{ "type" : "longSum", "name" : "bytes", "fieldName" : "bytes" }
],
"granularitySpec" : {
"type" : "uniform",
"segmentGranularity" : "week",
"queryGranularity" : "minute",
"intervals" : ["2018-01-01/2018-01-03"],
"rollup" : true
}
},
"ioConfig" : {
"type" : "index",
"firehose" : {
"type" : "local",
"baseDir" : "quickstart/tutorial",
"filter" : "rollup-data.json"
},
"appendToExisting" : false
},
"tuningConfig" : {
"type" : "index",
"maxRowsPerSegment" : 5000000,
"maxRowsInMemory" : 25000
}
}
}
其中維度列是srcIP和dstIP,時(shí)間序列時(shí)timestamp,度量列有count,packets和bytes。
其中"queryGranularity" : "minute"
發(fā)現(xiàn)01:01內(nèi)的數(shù)據(jù)被聚合了,按照時(shí)間序列和維度列,即{timestamp,srcIP,dstIP}進(jìn)行第一步聚合,對(duì)應(yīng)的指標(biāo)列進(jìn)行sum操作。
總結(jié),roll-up對(duì)什么范圍內(nèi)的數(shù)據(jù)進(jìn)行第一步的聚合,是由"queryGranularity" : "minute"來決定的。
queryGranularity:默認(rèn)為None,允許查詢的時(shí)間粒度,單位與segmentGranularity相同,如果為None那么允許以任意時(shí)間粒度進(jìn)行查詢。
注意:當(dāng)設(shè)定roll-up為true時(shí),會(huì)帶來信息量的丟失,因?yàn)閞oll-up的粒度會(huì)變成最小的數(shù)據(jù)可視化粒度,即毫秒級(jí)別的原始數(shù)據(jù),如果按照分鐘粒度進(jìn)行roll-up,那么入庫(kù)之后我們能夠查看數(shù)據(jù)的最小粒度即為分鐘級(jí)別。
在界面中,點(diǎn)擊如下按鈕可以顯示上面的json
3.Sharding和Segment
Druid是時(shí)間序列數(shù)據(jù)庫(kù),也存在分片(Sharding)的概念。Druid對(duì)原始數(shù)據(jù)按照時(shí)間維度進(jìn)行分片,每一個(gè)分片稱為段(Segment)。
Segment是Druid中最基本的數(shù)據(jù)存儲(chǔ)單元,采用列式(columnar)存儲(chǔ)某一個(gè)時(shí)間間隔(interval)內(nèi)某一個(gè)數(shù)據(jù)源(dataSource)的部分?jǐn)?shù)據(jù)所對(duì)應(yīng)的所有維度值、度量值、時(shí)間維度以及索引。
注意:
Segment是按照time進(jìn)行分區(qū),默認(rèn)的,一個(gè)Segment文件是每隔一個(gè)time interval創(chuàng)建的,而這個(gè)time interval是在granularitySpec中的segmentGranularity來配置的。
為了能夠讓Druid在查詢下良好運(yùn)行,segment文件的大小推薦在300mb~700mb中。
如果大小超過這個(gè)區(qū)間,考慮改變這個(gè)time interval的單位(換成更小的單位)或者重新對(duì)數(shù)據(jù)分區(qū),在TunningConfig中增加partitionsSpec,調(diào)整targetPartitionSize(此參數(shù)的最佳起點(diǎn)是500萬(wàn)行)。
Druid將不同時(shí)間范圍內(nèi)的數(shù)據(jù)存儲(chǔ)在不同的Segment數(shù)據(jù)快中,這就是所謂的數(shù)據(jù)橫向切割,這種設(shè)計(jì)為Druid帶來的顯而易見的優(yōu)點(diǎn):按時(shí)間范圍查詢數(shù)據(jù)時(shí),僅需要訪問對(duì)應(yīng)時(shí)間段內(nèi)的這些Segment數(shù)據(jù)塊,而不需要進(jìn)行全表數(shù)據(jù)范圍查詢,大大提高效率。
將行式數(shù)據(jù)轉(zhuǎn)換為列式存儲(chǔ)結(jié)構(gòu):按需加載,提高查詢速度。有3種類型的數(shù)據(jù)結(jié)構(gòu):
(1)timestamp列,long數(shù)組
(2)指標(biāo)列,int數(shù)組或者float數(shù)組
(3)維度列,支持過濾和分組。使用壓縮的BitMap索引
3.1 Segment數(shù)據(jù)結(jié)構(gòu)
Timestamp列和Metric列比較簡(jiǎn)單,他們?cè)趯?shí)現(xiàn)中被存儲(chǔ)為通過lz4壓縮的整型或浮點(diǎn)數(shù)組。當(dāng)一個(gè)查詢需要訪問某列數(shù)據(jù)時(shí),只需要解壓縮這些列,然后讀取出這些列的數(shù)據(jù),然后執(zhí)行預(yù)定義的聚合操作。對(duì)于查詢過程中不需要的列,Druid會(huì)直接跳過對(duì)這些列數(shù)據(jù)的訪問。
維度列Dimension列由于要實(shí)現(xiàn)filter和group by,實(shí)現(xiàn)有所不同,dimension列包含以下三種數(shù)據(jù)結(jié)構(gòu):
(1)一個(gè)字典:將值從字符串(所有的dimension列的數(shù)值都被當(dāng)做字符串處理)映射到整數(shù)id
(2)一個(gè)值的列表(正排數(shù)據(jù)):把列中的數(shù)值通過字典編碼以后,將數(shù)字id存儲(chǔ)在列表里面
(3)一個(gè)bitmap倒排索引:對(duì)于列里面的每個(gè)不同的值,都對(duì)應(yīng)存儲(chǔ)為一個(gè)bitmap,用來表示哪一行數(shù)據(jù)包含該值
3.2 為什么我們需要這三種數(shù)據(jù)結(jié)構(gòu)呢?
1.通過字典將字符串映射成數(shù)字id,數(shù)字id通常比字符串更小,因此可以被更緊湊的存儲(chǔ)。
2.第三點(diǎn)中的bitmap,通常被稱為倒排索引,用于支持快速的過濾操作(bitmap對(duì)AND和OR操作的速度非常快)。
3.最后,第二點(diǎn)中的值列表將用于支持group by和topN查詢,而普通的查詢只需要根據(jù)filter出來的行去來聚合相應(yīng)的metirc就可以了,而不需要訪問上述2中的值列表。
下面以上面表格中的page列數(shù)據(jù)為例,來演示一下這三種數(shù)據(jù)結(jié)構(gòu),如下所示:
1.編碼了列值的字典:
{
'Justin Bieber': 0,
'Ke$ha': 1
}
2.列值數(shù)據(jù)(正排):
[0, 0, 1, 1](第一個(gè)和第二個(gè)0代表第一,二行數(shù)據(jù)編碼,即上述字典中的'Justin BieBer',第三個(gè)和第四個(gè)代表第三四行數(shù)據(jù),即上述字典中的'Ke$ha')
3. Bitmaps:字典中的每個(gè)值對(duì)應(yīng)一個(gè)bitmap
value='Justin Bieber': [1, 1, 0, 0]
value='Ke$ha': [0, 0, 1, 1]
注意:bitmap跟其他兩種數(shù)據(jù)結(jié)構(gòu)不同,其他兩種數(shù)據(jù)結(jié)構(gòu)都是跟隨數(shù)據(jù)量的增長(zhǎng)而線性增長(zhǎng)的(最差情況下),而 bitmap的大小=數(shù)據(jù)的總行數(shù) * 列中值的種類數(shù) (也就是字典的size)。這就意味著如果值得的種類很多,那么bitmap中為1的數(shù)量將會(huì)非常稀疏,這種情況下bitmap有可能被大幅壓縮。Druid針對(duì)這種情況,采用了特殊的壓縮算法,比如roaring bitmap壓縮算法。
倒排索引相關(guān)文檔:http://hbasefly.com/2018/06/19/timeseries-database-8/?jqdajo=dz6ta
疑問:
公司的Druid設(shè)置的按小時(shí)進(jìn)行分區(qū),但是每個(gè)小時(shí)時(shí)間段生成的Segment文件數(shù)目卻不是一份,另外發(fā)現(xiàn)有些文件的size為0,是因?yàn)檫@部分?jǐn)?shù)據(jù)還沒有push到磁盤上。