Druid大数据之存储和查询
Druid数据格式
- 时间列(Timesatmp):表明每行数据的时间值,默认使用UTC时间格式并且精确到毫秒级别。这个列是数据聚合与范围查询的重要维度。
- 维度列(Dimension):维度来自于OLAP的概念,用来标识数据行的各个类别信息。
- 指标列(Metrics):指标对应于OLAP概念中的Fact,是用于计算和聚合的列。指标列通常是一些数字,计算操作通常包括Count,Sum,Mean等。
从上表可知维度列是:publisher,advertiser,gender,country。指标列是:click,price。
预聚合roll up
无论是实时数据消费还是批量的离线数据处理,Druid基于DataSource结构存储数据时间即可选择对任何指标列进行预聚合(roll up)。
- 同维度列的值做聚合:所有维度列的值都相同时,比对于所有组合维度“publisher advertise gender country”维度值同为“ultratrimfast.com google.com Male USA”或同为“bieberfever.com google.com Male USA”的行。
- 对指定时间粒度内的值做聚合:符合参数queryGranularity指定的范围,比如时间列同为一分钟内所有的行,聚合操作相当于数据表所有列做了Group by操作。比如“group by timestamp, publisher,advertiser,gender,country::impressions=Count(1),clicks=SUM(click),revenue=SUM(price)”。下表即为定义粒度为HOUR聚合之后的数据源情况。
这种预聚合的方式可以很显著的减少数据的存储(可减少100倍)。 Druid也是通过这种方式来减少数据的存储。 这种减少存储的方式也会带来副作用,比如我们没有办法再查询到每条数据具体的明细。换句话说,数据聚合的粒度是我们能查询数据的最小粒度。
数据分片
第一级分片-segment
DataSource只是一个逻辑概念,而Segment是数据的实际物理存储格式。Druid正是通过Segment实现对数据横向切割操作。从数据时间分布的角度来看,通过参数segmentGranularity的设置,Druid将不同的时间范围内的数据存储在不同的segment块中,这便是所谓的横向切割。这样访问Druid的数据仅需要访问对应时间的segment数据块,而不需要进行全表的数据范围查询。
同时在segment中也面向列进行数据压缩存储,这便是所谓的数据纵向切割。而且在Segment中使用了Bitmap进行了数据访问进行了优化。
第二级分片-shard
支持两种类型的分区策略:“散列”、“单维度”。在大多数情况下,建议使用散列分区,可以提高索引性能和创造大小更均匀的数据段。基于哈希散列首先选择第一级分片的segments,然后根据segments每一行的所有维度的hash来划分。
"partitionsSpec": {
"type”: “hashed",
"targetPartitionSize: 5000000
}
numShards:可以直接指定shard个数。当配置这个参数时,还可以配置partitionDimensions来指定维度,而不用全部维度。
单维度划分
首先自动选择一个维度划分,然后分离该维度成连续的范围。每一部分将包含所有行维度的值范围。
“partitionsSpec”: {
“type”: “dimension”,
“targetPartitionSize”: 5000000
}
可以通过partitionDimension指定维度进行划分,不用自动选择的维度。
shard大小推荐
根据官方文档,shard大小推荐为300MB-700MB,需要调整两级分片的参数segmentGranularity和partitioningSpec。shard相当于是druid数据最小存储单位,分得太大的话,可能在各个历史节点分散不开。分得合适的话,查询的时候在历史节点比较分散,可以充分利用每个历史节点的cpu。
数据查询
Druid的查询是使用REST风格的HTTP请求查询服务节点(Broker、Historical、Realtime),这些服务节点暴露REST查询接口,客户端发送Json对象请求查询接口。一般情况下,查询服务接口发布在Broker节点,POST请求查询如下所示:
Druid查询类型
Druid在不同场景下,有很多的查询类型。对于各种类型的查询类型的配置可以json属性文件设置。Druid查询类型,概括一下为3大类:
- 聚合查询 - 时间序列查询(Timeseries)、排名查询(TopN)、分组查询(GroupBy)
- 元数据查询 - 时间范围(Time Boundary) 、段元数据(Segment Metadata)、数据源(Datasource)
- Search查询 - Search以聚合查询为主,与其它查询类型比较相对简单,使用上相对比较少,暂不介绍。
如何对查询进行选择呢?
在可能的情况下,我们建议使用的时间序列和TopN查询代替分组查询,分组查询是Druid最灵活的的查询,但是性能最差。时间序列查询是明显快于GROUPBY查询,因为聚合不需要分组尺寸。对于分组和排序在一个单一的维度,TopN查询更优于GROUPBY。
Druid Json查询属性
Druid json查询比较重要的几个属性是:queryType、dataSource、granularity、filter、aggregator等。
- 查询类型(queryType):对应聚合查询下的3种类型值:timeseries、topN、groupBy
- 数据源(dataSource):数据源,类似数据库中表的概念,对应数据导入时Json配置属性dataSource值。
- 聚合粒度(granularity): 粒度决定如何得到数据块在跨时间维度,在配置查询聚合粒度里有三种配置方法:
- 简单聚合粒度 - 支持字符串值有:all、none、second、minute、five_minute,ten_minute,fifteen_minute、thirty_minute、hour、day、week、month、quarter、year,all - 将所有块变成一块, none - 不使用块数据(它实际上是使用最小索引的粒度,none意味着为毫秒级的粒度);按时间序列化查询时不建议使用none,因为所有的毫秒不存在,系统也将尝试生成0值,这往往是很多。
- 时间段聚合粒度 - Druid指定一精确的持续时间(毫秒)和时间戳返回UTC(世界标准时间)。
- 常用时间段聚合粒度 - 与时间段聚合粒度差不多,但是常用时间指平时我们常用时间段,如年、月、周、小时等。下面对3种聚合粒度配置举例说明。
简单聚合粒度
查询粒度比数据采集时配置的粒度小,则不合理,也无意义,因较小粒度(相比)者无索引数据;如查询粒度小于采集时配置的查询粒度时,则Druid的查询结果与采集数据配置的查询粒度结果一样。
假设我们存储在Druid的数据使用毫秒粒度获取,数据如下:
{"timestamp": "2017-08-31T01:02:33Z", "name": "yangxuan", "classes" : "chinese"}
{"timestamp": "2017-09-01T01:02:33Z", "name": "liuboyu", "language" : "english"}
{"timestamp": "2017-09-02T23:32:45Z", "name": "luojiangyu", "language" : "english"}
{"timestamp": "2017-09-03T03:32:45Z", "name": "DDD", "language" : "english"}
以"小时" 粒度提交一个groupby查询,查询配置如下:
{
"queryType":"groupBy",
"dataSource":"dataSource",
"granularity":"hour",
"dimensions":[
"language"
],
"aggregations":[
{
"type":"count",
"name":"count"
}
],
"intervals":[
"2000-01-01T00:00Z/3000-01-01T00:00Z"
]
}
按小时粒度进行的groupby查询结果中timestamp值精确到小时间,比小时粒度更小粒度值自动补填零,以此类推按天查询,则小时及小粒度补零。timestamp值为UTC查询结果如下:
[ {
"version" : "v1",
"timestamp" : "2017-08-31T01:00:00.000Z",
"event" : {
"count" : 1,
"language" : "chinese"
}
}, {
"version" : "v1",
"timestamp" : "2017-09-01T01:00:00.000Z",
"event" : {
"count" : 1,
"language" : "english"
}
}, {
"version" : "v1",
"timestamp" : "2017-09-02T23:00:00.000Z",
"event" : {
"count" : 1,
"language" : "english"
}
}, {
"version" : "v1",
"timestamp" : "2017-09-03T03:00:00.000Z",
"event" : {
"count" : 1,
"language" : "english"
}
} ]
如果指定查询粒度为none,则返回结果与数据导入时设置粒度(queryGranularity属性值)结果一样,此处的导入粒度为毫秒,结果如下:
[ {
"version" : "v1",
"timestamp" : "2017-08-31T01:02:33.000Z",
"event" : {
"count" : 1,
"language" : "chinese"
}
}, {
"version" : "v1",
"timestamp" : "2017-09-01T01:02:33.000Z",
"event" : {
"count" : 1,
"language" : "english"
}
}, {
"version" : "v1",
"timestamp" : "2017-09-02T23:32:45.000Z",
"event" : {
"count" : 1,
"language" : "english"
}
}, {
"version" : "v1",
"timestamp" : "2017-09-03T03:32:45.000Z",
"event" : {
"count" : 1,
"language" : "english"
}
} ]
时间段聚合粒度
指定一个精确时间持续时长(毫秒表示),返回UTC时间;支持可选项属性origin,不指定时默认开始时间(1970-01-01T00:00:00Z)
/**持续时间段2小时,从1970-01-01T00:00:00Z开始*/
{"type": "duration", "duration": 7200000}
/**持续时间1小时,从origin开始*/
{"type": "duration", "duration": 3600000, "origin": "2012-01-01T00:30:00Z"}
过滤(Filters)
等价于sql 查询的where。也是支持and,or,in,not等。
"filter": { "type": "selector", "dimension": <dimension_string>, "value": <dimension_value_string> }
聚合(Aggregations)
聚合类型如下:Count aggregator、Sum aggregators、Min / Max aggregators、Approximate Aggregations、Miscellaneous Aggregations
/**Druid进行Count查询的数据量并不一定等于数据采集时导入的数据量,因为Druid在采集数据并导入时已经对数据进行了聚合*/
{ "type" : "count", "name" : <output_name> }
/**longSumaggregator:计算值为有符号位64位整数*/
{ "type" : "longSum", "name" : <output_name>, "fieldName" : <metric_name> }
/**doubleSum aggregator:与longSum类似,计算值为64位浮点型*/
{ "type" : "doubleSum", "name" : <output_name>, "fieldName" : <metric_name> }
/** doubleMin aggregator */
{ "type" : "doubleMin", "name" : <output_name>, "fieldName" : <metric_name> }
/**doubleMax aggregator*/
{ "type" : "doubleMax", "name" : <output_name>, "fieldName" : <metric_name> }
/**longMin aggregator*/
{ "type" : "longMin", "name" : <output_name>, "fieldName" : <metric_name> }
/** longMax aggregator*/
{ "type" : "longMax", "name" : <output_name>, "fieldName" : <metric_name> }
类似聚合(Approximate Aggregations)
基数聚合(Cardinality aggregator)
计算Druid多种维度基数,Cardinality aggregator使用HyperLogLog评估基数,这种聚合比带有索引的
hyperUnique聚合慢;一般我们强力推荐使用hyperUniqueaggregator而不是Cardinality aggregator,格式如下:
{
"type": "cardinality",
"name": "<output_name>",
"fieldNames": [ <dimension1>, <dimension2>, ... ],
"byRow": <false | true> # (optional, defaults to false)
}
维度值聚合-当设置属性byRow为false(默认值)时,通过合并所有给定的维度列来计算值集合。单维度等价于:
SELECT COUNT(DISTINCT(dimension)) FROM <datasource>
对于多维度,等价如下:
SELECT COUNT(DISTINCT(value)) FROM (
SELECT dim_1 as value FROM <datasource>
UNION
SELECT dim_2 as value FROM <datasource>
UNION
SELECT dim_3 as value FROM <datasource>
行聚合-当设置属性byRow为true时,根所不同维度的值合并来计算行值,等价如下:
SELECT COUNT(*) FROM ( SELECT DIM1, DIM2, DIM3 FROM <datasource> GROUP BY DIM1, DIM2, DIM3 )
HyperUnique aggregator
“hyperunique”在创建索引时聚合的维度值使用HyperLogLog计算估计,更多资料请参考官网:
{ "type" : "hyperUnique", "name" : <output_name>, "fieldName" : <metric_name> }
后聚合(post-aggregators)
后聚合是对Druid进行聚合后的值进行聚全,如果查询中包括一个后聚合,那么确保所有聚合满足后聚合要求;后聚合有以下几种类型:
- Arithmetic post-aggregators
- Field accessor post-aggregator
- Constant post-aggregator
- JavaScript post-aggregator
- HyperUnique Cardinality post-aggregator
Arithmetic post-aggregators
算术后聚合应用已提供的函数从左到右获取字段,这些字段可聚合或后聚合;支持+, -, *, /, and quotient。
算术后聚合语法如下:
postAggregation : {
"type" : "arithmetic",
"name" : <output_name>,
"fn" : <arithmetic_function>,
"fields": [<post_aggregator>, <post_aggregator>, ...],
"ordering" : <null (default), or "numericFirst">
}
时间序列查询(Timeseries)
这些类型的查询以时间序列查询对象和返回一个JSON数组对象,每个对象表示时间序列查询的值,时间序列查询请求的Json的7个主要属性如下:
排名查询(TopN query)
TopN查询根据规范返回给定维度的有序的结果集,从概念上来讲,TopN查询被认为单维度、有序的类似分组查询。在某些情况下,TopN查询比分组查询(groupby query)快。TopN查询结果返回Json数组对象。TopN在每个节点将顶上K个结果排名,在Druid默认情况下最大值为1000。在实践中,如果你要求前1000个项顺序排名,那么从第1-999个项的顺序正确性是100%,其后项的结果顺序没有保证。你可以通过增加threshold值来保证顺序准确。
更多推荐
所有评论(0)