目录
1.介绍
2.Druid总体设计
2.1 Druid总体架构
3.数据导入
3.1 基于DataSource与Segment的数据结构
3.1.1 DataSource结构
3.1.2 Segment结构
3.2 数据格式定义
3.2.1 DataSchema详解
3.3 流式数据源 (实时导入)
3.4 静态数据源(离线数据导入)
4. 查询
4.1 查询组件
4.2 查询实例
4.2.1 元数据信息查询
4.2.2 TopN
4.2.3 GroupBy
4.2.3 Select
5.Druid扩展
5.2 完善的社区
0. 前言
OLTP 与 OLAP
OLTP:OLTP是传统的关系型数据库的主要应用,主要是基本的、日常的事务处理,例如银行交易。通俗的讲,就是对数据的增删改查等操作。
其中,OLAP可以分为 ROLAP, MOLAP 和 HOLAP
Druid 是一个为在大数据集之上做实时统计分析而设计的开源数据存储。这个系统集合了一个面向列存储的层,一个分布式、shared-nothing的架构,和一个高级的索引结构,来达成在秒级以内对十亿行级别的表进行任意的探索分析。在这篇论文里面,我们会描述Druid的架构,和怎样支持快速聚合、灵活的过滤、和低延迟数据导入的一些细节。
Druid总体架构图
本身包含了五种节点 : Realtime、Historical、Coordinator、Broker、Indexer
2.1.1 历史节点(Historical Node)
历史节点主要负责加载已经生成好的数据文件以及提供数据查询。
历史节点启动时,会先检查自身的本地缓存中已存在的Segment数据文件,然后从DeepStorage中下载数据自己但不在本地的Segment数据文件,后加载到内存后再提供查询。
2.1.2 实时节点(Realtime Node)
主要负责及时摄入实时数据,以及生成Segment数据文件;Realtime节点只响应broker节点的查询请求,返回查询结果到broker节点。旧数据会按指定周期将数据build成segments移到Historical节点。一般会使用外部依赖kafka来提高实时摄取数据的可用性。如果不需要实时摄取数据到集群中,可以舍弃Real-time nodes,只定时地批量从deep storage摄取数据即可;
2.1.2.1 Segment数据文件的制造与传播
2.13 协调节点(Coordinator Node)
协调节点主要负责历史节点的数据负载均衡,以及通过规则管理数据源的生命周期。
监控historical节点组,可以认为是Druid中的master,其通过Zookeeper管理Historical和Real-time nodes,且通过Mysql中的metadata管理Segments,以确保数据可用、可复制。它们通过从MySQL读取数据段的元数据信息,来决定哪些数据段应该在集群中被加载,使用Zookeeper来确定哪个historical节点存在,并且创建Zookeeper条目告诉historical节点加载和删除新数据段。
2.1.4 查询节点(Broker Node)
对外提供数据查询服务,并同时从实施节点、历史节点查询数据,合并后返给调用方
2.1.4.1 查询中枢点
Druid集群直接对外提供查询的节点只有查询节点,而查询节点会将从实时节点与历史节点查询到的数据进行合并并返回。
2.1.4.2 查询节点中的缓存应用
当请求多次相似查询时,可直接利用之前的缓存中的数据作为部分或全部进行返回,而不用去访问库中的数据,可以大大提高查询效率,如下图;
Druid提供了两类截止作为Cache:
- 外部Cache,如memcached
- 本地Cache,比如查询节点或历史节点的内存作为Cache
2.1.4.3 查询节点高可用
一般Druid集群只需要一个查询节点,但了防止出现单点down机等问题,可增加一个或多个查询节点到集群中,多个查询节点可使用Nginx来完成负载,并达到高可用的效果,如下图;
2.1.5 Indexer
索引服务用于数据导入,batch data和streaming data都可以通过给indexing services发请求来导入数据。Indexing service可以通过api的方式灵活的操作Segment文件,如合并,删除Segment等
3个外部依赖 :Mysql、Deep storage、Zookeeper
1) Mysql:存储关于Druid中的metadata,规则数据,配置数据等,主要包含以下几张表:”druid_config”(通常是空的), “druid_rules”(协作节点使用的一些规则信息,比如哪个segment从哪个node去load)和“druid_segments”(存储 每个segment的metadata信息);
2 )Deep storage:存储segments,Druid目前已经支持本地磁盘,NFS挂载磁盘,HDFS,S3等。Deep Storage的数据有2个来源,一个是批数据摄入, 另一个来自Realtime节点;
3) ZooKeeper:被Druid用于管理当前cluster的状态,为集群服务发现和维持当前的数据拓扑而服务;例如:被Druid用于管理当前cluster的状态,比如记录哪些segments从实时节点移到了历史节点;
Druid中的数据表(称为数据源)是一个时间序列事件数据的集合,并分割到一组segment中,而每一个segment通常是0.5-1千万行。在形式上,我们定义一个segment为跨越一段时间的数据行的集合。Segment是Druid里面的基本存储单元,复制和分布都是在segment基础之上进行的。
Druid总是需要一个时间戳的列来作为简化数据分布策略、数据保持策略、与第一级查询剪支(first-level query pruning)的方法。Druid分隔它的数据源到明确定义的时间间隔中,通常是一个小时或者一天,或者进一步的根据其他列的值来进行分隔,以达到期望的segment大小。segment分隔的时间粒度是一个数据大小和时间范围的函数。一个超过一年的数据集最好按天分隔,而一个超过一天的数据集则最好按小时分隔。
Segment是由一个数据源标识符、数据的时间范围、和一个新segment创建时自增的版本字符串来组合起来作为唯一标识符。版本字符串表明了segment的新旧程度,高版本号的segment的数据比低版本号的segment的数据要新。这些segment的元数据用于系统的并发控制,读操作总是读取特定时间范围内有最新版本标识符的那些segment。
Druid的segment存储在一个面向列的存储中。由于Druid是适用于聚合计算事件数据流(所有的数据进入到Druid中都必须有一个时间戳),使用列式来存储聚合信息比使用行存储更好这个是 有据可查 的。列式存储可以有更好的CPU利用率,只需加载和扫描那些它真正需要的数据。而基于行的存储,在一个聚合计算中相关行中所有列都必须被扫描,这些附加的扫描时间会引起性能恶化。
实时节点数据块生成过程
与传统关系型数据库相比,Druid的DataSource可以算是table,DataSource的结构包括以下几个方面
DataSource是一个逻辑概念,Segment确实数据的实际物理存储格式,segment 的组织方式是通过时间戳跟粒度来定义的.Druid通granularitySpec过Segment实现了对数据的横纵切割操作,从数据按时间分布的角度来看,通过参数segmentGranularity设置,Druid将不同时间范围内的数据存储在不同的Segment数据块中,这边是所谓的数据横向切割。带来的优点:按时间范围查询数据时,仅需访问对应时间段内的这些Segment数据块,而不需要进项全表数据查询。下图是Segment按时间范围存储的结构;同时在Segment中也面向列进行数据压缩存储,这就是数据纵向切割;(Segment中使用Bitmap等技术对数据的访问进行了优化,没有详细了解bitmap)
|
|
|
如上的配置说明了接收【09-13】号这5天的数据,然后按天来划分segment,所以总共只有5个segment。
实时数据首先会被直接加载到实时节点内存中的堆结构换从去,当条件满足时,缓存区里的数据会被写到硬盘上行程一个数据块(Segment),同事实时节点又会立即将新生成的数据块加载到内存中的非堆区,因此无论是堆缓存区还是非堆区里的数据,都能被查询节点(Broker Node)查询;
同时实时节点会周期性的将磁盘上同一时间段内生成的数据块合并成一个大的Segment,这个过程在实时节点中的操作叫做Segment Merge,合并后的大Segment会被实时节点上传到数据文件存储(DeepStorage)中,上传到DeepStorage后,协调节点(Coordination Node)会指定一个历史节点(Historical Node)去文件存储库将刚刚上传的Segment下载到本地磁盘,该历史节点加载成功后,会通过协调节点(Coordination Node)在集群中声明该Segment可以提供查询了,当实时节点收到这条声明后会立即向集群声明实时节点不再提供该Segment的查询,而对于全局数据来说,查询节点(Broker Node)会同时从实时节点与历史节点分别查询,对结果整合后返回用户。
首先通过Indexing Service提交Indexing作业,将输入数据转换为Segments文件。然后需要一个计算单元来处理每个Segment的数据分析,这个计算单元就是Druid中的 历史节点(Historical Node),
Historical Node是Druid最主要的计算节点,用于处理对Segments的查询。在能够服务Segment之前, Historical Node需要首先将Segment从HDFS、S3、本地文件等下载到本地磁盘,然后将Segment数据文件mmap到进程的地址空间。
Historical Node采用了Shared-Nothing架构,状态信息记录在Zookeeper中,可以很容易地进行伸缩。 Historical Node在Zookeeper中宣布自己和所服务的Segments,也通过Zookeeper接收加载/丢弃Segment的命令。
最后,由于Segment的不可变性,可以通过复制Segment到多个 Historical Node来实现容错和负载均衡,这也体现了druid的扩展性和高可用。
Filter(过滤器)
类型 | 说明 | 举例 |
---|---|---|
Select Filter | 类似SQL中的 where key = valus | "filter":{"type": "selector" , "dimension" : "city_name" , "value": "北京" } |
Regex Filte | 正则表达式筛选维度(任何java支持的正则,druid都支持) | "filter":{"type": "regex" , "dimension" : "city_code" , "pattern": "^[a-z]+(?$" } |
Logical Expression Filter | Logical Expression包含 and 、 or、 not 三种过滤器,每种都支持嵌套,可构造出丰富的查询。 | "filter":{"type": "and" , "fields":[ "filter":{"type": "or" , "fields":[ "filter":{"type": "not" , "field": |
Search Filter | 通过字符串匹配过滤维度 | |
In Filter | 类似SQL中的IN | "filter":{"type": "in" , "dimension" : "source" , "values": ["ios","pc","android"] } |
Bound Filter | 比较过滤器,包含 ">、<、="三种算子【Bound Filter还支持字符串比较,基于字典排序,默认是字符串比较 】 | 21 <= age <= 31 "filter":{"type": "bound" , "dimension":"age" ,"lower":"21","upper": "31", "alphaNumeric":true } //因默认是字符串比较,数字比较时需指定 alphaNumeric为ture 21 < age < 31 "filter":{"type": "bound" , "dimension":"age" ,"lower":"21", "lowerStrict":"true", "upper": "31", "upperStrict":"true" ,"alphaNumeric":true } // boubd 默认符号 为"<="或">=" ,使用lowerStrict、upperStrict 标识大于、小于,不包含等于 |
JavaScript Filter | Druid支持自己写javascript(未深入了解) |
Aggregator (聚合器)
可在数据摄入阶段就指定,也可以在查询时指定,聚合器类型如下:
类型 | 说明 | 举例 |
---|---|---|
Count | 计算Druid数据行数 | 1.查询聚合后的数据量{"type": "count", "name": "总行数" } 2.查询摄入的原始数据条数{"type": "longSum", "name": "总数" ,"fieldName":"count"} |
Sum | 计算加和,按数据类型可分两类 1.64位有符号整型; 2.64位浮点型 |
{"type": "longSum", "name": "整型总数" , "fieldName": "metric_name_1"} {"type": "doubleSum", "name": "浮点型总数" , "fieldName" : "metric_name_2"} |
Min/Max | 计算最大最小值,按数据类型可分四类 1.doubleMin Aggregator 2.doubleMax Aggregator 3.longMin Aggregator 4.longMax Aggregator |
{"type": "longMax", "name": "整型最大值" , "fieldName": "metric_name_1"} {"type": "longMin", "name": "整型最小值" , "fieldName": "metric_name_1"} {"type": "doubleMin", "name": "浮点型最小值" , "fieldName": "metric_name_2"} {"type": "doubleMax", "name": "浮点型最大值" , "fieldName": "metric_name_2"} |
Cardinality | 计算指定维度的基数 | |
HyperUnique | 计算指定维度的基数,性能比Cardinality好很多 | |
Filtered | 对聚合指定filter规则(只对满足filter的维度进行聚合,效率更高) | {"type":"filtered" ,"filter":{"type":"selector","dimentsion":"xxxx","value":"value"}, "aggregator": |
JavaScript | Druid支持自己写javascript(未深入了解) |
Post-Aggregator
可对Aggregator进行二次加工并输出,如果使用postaggregator,必须包含aggregator为前提。
类型 | 说明 | 举例 |
---|---|---|
Arithmetic | 支持对Aggregator的结果和其他Arithmetic的结果进行“加、减、乘、除、余数”计算 | "postAggretation":{"type":"arithmetic","name":"计算结果","fn":计算方法,"fields":[ |
Field Accessor | 返回指定的Aggregator的值 | |
Constant | 可以将Aggregator的结果转换为百分比 | {"type":"constant","name":"增长率","value":92} |
Search Query
类型 | 说明 | 举例 |
---|---|---|
contains | 筛选维度值相匹配的,可使用case_sensitive设置是否区分大小写 | {"type": "contains", "case_sensitive": true, "value":"北京"} |
insensitive_contains | 指定的维度是否包含给定的字符串,不区分大小写,等价于contains中case_sensitive设置为false | {"type": "insensitive_contains", "value":"北京"} |
fragment | 指定维度任意部分是否包含 | {"type": "fragment", "case_sensitive": true, "values": ["北京","成都,"上海"]} |
Interval(查询指定的时间区间)
Interval中的时间格式是ISO-8601格式,中国所在时区为东8区,查询时需要在时间中加入“+8:00”,如:"intervals":["2018-01-29T00:00:00+08:00/2018-01-30T00:00:00+08:00"]
Interval的区间为前闭后开:start <= time < end ,查询结果的准确性需要注意.
Context(查询时的辅助参数配置等,入超时时间设置、是否使用查询缓存等,都是非必填项,如果不指定,则用默认参数)
字段名 | 默认值 | des说明cription |
---|---|---|
timeout | 0 |
查询超时时间,单位毫秒. |
priority | 0 |
查询优先级 |
queryId | 自动生成 | 生成唯一查询的id标示 |
useCache | true |
此次查询是否利用查询缓存,如手动指定,则会覆盖查询节点或历史节点配置的值 |
populateCache | true |
此次查询的结果是否缓存,如果手动指定,则会覆盖查询节点或历史节点配置的值 |
bySegment | false |
为true是,将在返回结果中显示关联的Segment |
finalize | true |
是否返回Aggregator的最终结果 |
chunkPeriod | 0 (off) |
指定是否将茶时间跨度的查询切分为多个短时间跨度进行查询,需要配置druid.processiong.numThreads的值 |
minTopNThreshold | 1000 |
配置每个Segment返回的TopN的数量用于合并,从而得到最终的topN |
maxResults |
500000 | 结果groupBy查询可以处理的最大数目。 在查询节点和历史节点配置项中的 druid.query.groupBy.maxResults 可以更改所使用的默认值。 查询时该字段的值只能低于配置项的值 |
maxIntermediateRows |
50000 | 配置GroupBy查询处理单个分段时的最大行数,默认值在查询节点和历史节点的配置druid.query.groupBy.maxIntermediateRows中指定,查询时该字段的值只能低于配置项的值 |
groupByIsSingleThreaded |
false | 费否使用单线程执行GroupBy,默认值在历史节点配置项 druid.query.groupBy.singleThreaded中 指定 |
和我需求契合度非常高,返回指定维度和排序字段的top-n条。
与sql中的group by类似,支持对多个维度进行分组,也支持对维度进行排序,并支持limit行数限制。同时支持having操作,与topN相比,性能比前者差很多!如果对时间维度进行聚合,建议使用时间序列或topN。
常用参数如下:
与sql中的slect操作类似,支持按指定过滤器和时间段查看指定维度和指标,并可通过descending字段设置排序,支持分页拉取。但不支持aggregations和postAggregations。
5.1 完善的监控与告警
https://imply.io/get-started 支持简单的报表制作,sql查询
https://github.com/implydata