Druid 大数据分析

目录

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:OLAP是数据仓库系统的主要应用,支持复杂的分析操作,侧重决策支持,并且提供直观易懂的查询结果。通俗的讲,就是对数据按不同维度的聚合,维度的下钻,上卷等。

其中,OLAP可以分为 ROLAPMOLAP 和 HOLAP

  • ROLAP: 使用关系型数据库或者扩展的关系型数据库来管理数据仓库数据,而OLAP中间件支持其余的功能。ROLAP包含了每个后端关系型数据库的优化,聚合,维度操作逻辑的实现,附件的工具以及服务等。所以ROLAP比MOLAP有更好的可伸缩性。 比较典型的ROLAP有mondrian, Presto(facebook)。 目前阿里的DRDS也可以看作是ROLAP的框架

 

  • MOLAP: 通过基于数据立方体的多位存储引擎,支持数据的多位视图。即通过将多维视图直接映射到数据立方体上,使用数据立方体能够将预计算的汇总数据快速索引。比较典型的MOLAP框架有kylin(apache), Lylin(ebay)、pinot(linkedin)和druid。MOLAP是空间换时间,即把所有的分析情况都物化为物理表或者视图,查询的时候直接从相应的物化表中获取数据, 而ROLAP则通过按维度分库,分表等方式,实现单一维度下的快速查询,通过分布式框架,并行完成分析任务,来实现数据的分析功能。MOLAP 实现较简单,但当分析的维度很多时,数据量呈指数增长,而ROLAP在技术实现上要求更高,但扩展性也较好。

 

  • HOLAP: 混合OLAP结合ROLAP和MOLAP,得益于ROLAP较大的可伸缩性和MOLAP的快速查询。

 

1.介绍

Druid 是一个为在大数据集之上做实时统计分析而设计的开源数据存储。这个系统集合了一个面向列存储的层,一个分布式、shared-nothing的架构,和一个高级的索引结构,来达成在秒级以内对十亿行级别的表进行任意的探索分析。在这篇论文里面,我们会描述Druid的架构,和怎样支持快速聚合、灵活的过滤、和低延迟数据导入的一些细节。

  • 交互式查询:为了前端可视化工具可以有很好的用户体验,查询必须秒级响应
  • 低数据延迟:产生的数据必须很快进入系统并被用户查询到
  • 高并发:系统需要支持1000+的并发用户,并提供隔离机制支持多租户模式
  • 高可用:服务故障会导致公司业务遭受重大损失,因此不可用时间应当降到最低

2.Druid总体设计

2.1 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数据文件的制造与传播

  1. 实时节点生成Segment数据文件,并上传到DeepStorage中。
  2. Segment数据文件的相关元数据信息保存到MetaStore中(如mysql,derby等)。
  3. 协调节点会从MetaSotre中获取到Segment数据文件的相关元信息后,将按配置的规则分配到符合条件的历史节点中。
  4. 历史节点收到协调节点的通知后,会从DeepStorage中拉取该Segment数据文件,并通过zookeeper向集群声明可以提供查询了。
  5. 实时节点会丢弃该Segment数据文件,并通过zookeeper向集群声明不在提供该Sgment的查询服务。


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从实时节点移到了历史节点;

 

 

3.数据导入

3.1 基于DataSource与Segment的数据结构

 

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利用率,只需加载和扫描那些它真正需要的数据。而基于行的存储,在一个聚合计算中相关行中所有列都必须被扫描,这些附加的扫描时间会引起性能恶化。

 

实时节点数据块生成过程

3.1.1 DataSource结构

与传统关系型数据库相比,Druid的DataSource可以算是table,DataSource的结构包括以下几个方面

  • 时间列:表明每行数据的时间值,默认只用UTC时间格式且精确到毫秒。这个列是数据聚合与范围查询的重要维度
  • 维度列:用来表示数据行的各个类别信息
  • 指标列:用于聚合和计算的列

3.1.2 Segment结构

DataSource是一个逻辑概念,Segment确实数据的实际物理存储格式,segment 的组织方式是通过时间戳跟粒度来定义的.Druid通granularitySpec过Segment实现了对数据的横纵切割操作,从数据按时间分布的角度来看,通过参数segmentGranularity设置,Druid将不同时间范围内的数据存储在不同的Segment数据块中,这边是所谓的数据横向切割。带来的优点:按时间范围查询数据时,仅需访问对应时间段内的这些Segment数据块,而不需要进项全表数据查询。下图是Segment按时间范围存储的结构;同时在Segment中也面向列进行数据压缩存储,这就是数据纵向切割;(Segment中使用Bitmap等技术对数据的访问进行了优化,没有详细了解bitmap)

3.2 数据格式定义

{
  "dataSchema" : {...},       #JSON对象,指明数据源格式、数据解析、维度等信息
  "ioConfig" : {...},         #JSON对象,指明数据如何在Druid中存储
  "tuningConfig" : {...}      #JSON对象,指明存储优化配置(非必填)
}

3.2.1 DataSchema详解

{
 "datasource":"...",            #string类型,数据源名称
 "parser": {...},               #JSON对象,包含了如何即系数据的相关内容
 "metricsSpec": [...],          #list 包含了所有的指标信息
 "granularitySpec": {...}       #JSON对象,指明数据的存储和查询力度
}

 

"granularitySpec" : {
  "type" : "uniform",                     	 //type : 用来指定粒度的类型使用 uniform, arbitrary(尝试创建大小相等的段).
  "segmentGranularity" : "day",           	 //segmentGranularity : 用来确定每个segment包含的时间戳范围
  "queryGranularity" : "none",               //控制注入数据的粒度。 最小的queryGranularity 是 millisecond(毫秒级)
  "rollup" : false,
  "intervals" : ["2018-01-09/2018-01-13"]    //intervals : 用来确定总的要获取的文件时间戳的范围
}

 

如上的配置说明了接收【09-13】号这5天的数据,然后按天来划分segment,所以总共只有5个segment。

3.3 流式数据源 (实时导入)

 实时数据首先会被直接加载到实时节点内存中的堆结构换从去,当条件满足时,缓存区里的数据会被写到硬盘上行程一个数据块(Segment),同事实时节点又会立即将新生成的数据块加载到内存中的非堆区,因此无论是堆缓存区还是非堆区里的数据,都能被查询节点(Broker Node)查询;

       同时实时节点会周期性的将磁盘上同一时间段内生成的数据块合并成一个大的Segment,这个过程在实时节点中的操作叫做Segment Merge,合并后的大Segment会被实时节点上传到数据文件存储(DeepStorage)中,上传到DeepStorage后,协调节点(Coordination Node)会指定一个历史节点(Historical Node)去文件存储库将刚刚上传的Segment下载到本地磁盘,该历史节点加载成功后,会通过协调节点(Coordination Node)在集群中声明该Segment可以提供查询了,当实时节点收到这条声明后会立即向集群声明实时节点不再提供该Segment的查询,而对于全局数据来说,查询节点(Broker Node)会同时从实时节点与历史节点分别查询,对结果整合后返回用户。

                                                                                                                                    

3.4 静态数据源(离线数据导入)

首先通过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的扩展性和高可用。

4. 查询

4.1 查询组件

 

  • 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]+(?<!bc)$" } 
    Logical Expression FilterLogical Expression包含 and 、 or、 not 三种过滤器,每种都支持嵌套,可构造出丰富的查询。

     "filter":{"type": "and" , "fields":[<filter>,<filter>... ] } 

    "filter":{"type": "or" , "fields":[<filter>,<filter>... ]  } 

     "filter":{"type": "not" , "field":<filter> } 

    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 FilterDruid支持自己写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":<aggregation>}
    JavaScriptDruid支持自己写javascript(未深入了解)  

     

  • Post-Aggregator

    可对Aggregator进行二次加工并输出,如果使用postaggregator,必须包含aggregator为前提。

    类型说明举例
    Arithmetic支持对Aggregator的结果和其他Arithmetic的结果进行“加、减、乘、除、余数”计算"postAggretation":{"type":"arithmetic","name":"计算结果","fn":计算方法,"fields":[<post_aggregator>,<post_aggregator>,...],"ordering":""} //ordering 排序
    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
    timeout0 查询超时时间,单位毫秒.
    priority0查询优先级
    queryId自动生成生成唯一查询的id标示
    useCachetrue此次查询是否利用查询缓存,如手动指定,则会覆盖查询节点或历史节点配置的值
    populateCachetrue此次查询的结果是否缓存,如果手动指定,则会覆盖查询节点或历史节点配置的值
    bySegmentfalse为true是,将在返回结果中显示关联的Segment
    finalizetrue是否返回Aggregator的最终结果
    chunkPeriod0 (off)指定是否将茶时间跨度的查询切分为多个短时间跨度进行查询,需要配置druid.processiong.numThreads的值
    minTopNThreshold1000配置每个Segment返回的TopN的数量用于合并,从而得到最终的topN
    maxResults500000结果groupBy查询可以处理的最大数目。 在查询节点和历史节点配置项中的 druid.query.groupBy.maxResults 可以更改所使用的默认值。 查询时该字段的值只能低于配置项的值
    maxIntermediateRows50000配置GroupBy查询处理单个分段时的最大行数,默认值在查询节点和历史节点的配置druid.query.groupBy.maxIntermediateRows中指定,查询时该字段的值只能低于配置项的值
    groupByIsSingleThreadedfalse费否使用单线程执行GroupBy,默认值在历史节点配置项 druid.query.groupBy.singleThreaded中指定

     

 

4.2 查询实例

4.2.1 元数据信息查询

 

  • timeBoundary:  查询datasource的最早和最晚的时间点
     
  • segmentMetadata:查询Segment的元信息,例如有那列,哪些指标,聚合力度等
     
  • dataSourceMetadata: 查询datasource的最近一次摄入数据的时间
     

4.2.2 TopN

     和我需求契合度非常高,返回指定维度和排序字段的top-n条。

4.2.3 GroupBy

     与sql中的group by类似,支持对多个维度进行分组,也支持对维度进行排序,并支持limit行数限制。同时支持having操作,与topN相比,性能比前者差很多!如果对时间维度进行聚合,建议使用时间序列或topN。

    常用参数如下:

4.2.3 Select

   与sql中的slect操作类似,支持按指定过滤器和时间段查看指定维度和指标,并可通过descending字段设置排序,支持分页拉取。但不支持aggregations和postAggregations

 

5.Druid扩展

5.1 完善的监控与告警

  • 查询节点监控指标
  • 历史节点监控指标
  • 实时节点监控指标
  • cache监控指标
     

 

5.2 完善的社区

https://imply.io/get-started   支持简单的报表制作,sql查询

https://github.com/implydata

          

©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页