基于DataSource与Segment的数据结构

历史节点主要负责加载已经生成好的数据文件以及提供数据查询。

历史节点启动时,会先检查自身的本地缓存中已存在的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分隔的时间粒度是一个数据大小和时间范围的函数。一个超过一年的数据集最好按天分隔,而一个超过一天的数据集则最好按小时分隔。

https://tieba.baidu.com/p/5845990810
https://tieba.baidu.com/p/5845992597

https://tieba.baidu.com/p/5845997219
https://tieba.baidu.com/p/5845998481

https://tieba.baidu.com/p/5846002987
https://tieba.baidu.com/p/5846004329
https://tieba.baidu.com/p/5846006340

https://tieba.baidu.com/p/5846235530
https://tieba.baidu.com/p/5846237949
https://tieba.baidu.com/p/5846239682

https://tieba.baidu.com/p/5846247406

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 Filter Logical 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 标识大于、小于,不包含等于

猜你喜欢

转载自blog.csdn.net/qq_38459909/article/details/81835790
今日推荐