(2)Druid架构详解

Druid的架构中包括以下4类节点:
  • 实时节点(realtime node)  实时摄入数据,并生成Segment数据文件
  • 历史节点(historical node) 加载已经生成的数据文件,以供数据查询
  • 查询节点(Broker node)对外提供数据查询服务,并同时从实时节点与历史节点查询数据,合并后返回给调用方。
  • 协调节点(coordinator  node)负责历史节点的数据负载均衡 ,以及通过规则Rule管理数据的生命周期。
同时,集群还包括以下三类依赖:
  • 元数据库,存储Druid集群的元数据信息,一般用mysql
  • 分布式协调服务,为druid集群提供一致性的协调服务,通常为zookeeper
  • 数据文件存储库(deepStorage)存放生成的Segment数据文件,并供历史节点下载,对于分布式集群一般是HDFS

Druid架构设计思想:
1  索引对树结构的选择
对于大多数Druid的使用场景,Druid本质上是一个分布式的时序数据库,为了加速对数据库的访问,大多数传统的数据库都会用一个数据结构叫做索引index,索引一般不使用hash算法,而使用Tree结构,
二叉树 --》 平衡二叉树 --》 B+树  --》 LSM-Tree  日志结构合并树
LSM-Tree最大的特点是同时使用了两种分类树的数据结构来存储数据,并同时提供查询,其中一部分数据结构C0树存在于内存缓存(memtable)中,负责接受新的数据插入更新以及读请求,并直接在内存中对数据进行排序,另一部分数据结构C1存在于硬盘上(sstable)它们是由存在于内存缓存中的C0树写到磁盘而成的,主要负责提供读操作,特点是有序且不可更改。
LSM-Tree的另一大特点是有日志文件(commit log)来为数据恢复做保障,这三类数据结构的协作顺序是:所有的新插入与更新都首先被记录到commit log中,该操作叫做WAL 然后再写到memtable,当达到一定条件时数据会从memtable写到sstable,并抛弃相关的log数据,memtable和sstable可同时提供查询,当memtable出问题时,可以从commit log与 sstable中将memtable的数据恢复。
LSM-Tree的这种结构非常有利于数据快读写入,但不利于读,因为理论上读的时候可能需要同时从memtable和所有硬盘上的sstable中查询数据,这样显然会对性能造成较大的影响。所以LSM-Tree采取以下措施:
1  定期将硬盘上小的sstable合并,较少sstable的数量,删除操作也在合并的时候进行
2  对每个sstable使用布隆过滤,减少数据总的查询时间

Druid对LSM-Tree的应用
LSM-Tree显然比较适合那些数据插入操作远多于更新删除和读的场景,同时Druid在一开始就是为时序数据场景设计的,正好符合LSM-Tree的优势特点。
Druid的类LSM-Tree架构中的实时节点(Realtime node)负责消费实时数据,与经典LSM-Tree架构不同的是,Druid不提供日志和WAL原则,实时数据首先会被直接加载进实时节点内存中的堆结构缓存区(相当于memtable),当条件满足时,缓存区里的数据会被写入到硬盘上形成一个数据块(Segment Split),同时实时节点又会立即将新生成的数据块加载到内存中的非堆区,因此无论是堆结构缓存还是非堆区里的数据,都能被查询节点(Broker node)查询。
同时,实时节点会周期性的将磁盘上同一个时间段内生成的所有数据块合并为一个大的数据块(Segment),这个过程在实时节点中叫segment Merge操作,合并好的segment会立即被实时节点上传到数据文件存储库(deepStorage)中,随后协调节点(coordinator node)会指导一个历史节点(historical node)去文件存储库,将新生成的segment下载到其本地磁盘中,当历史节点成功加载到segment胡,会通过协调服务zk在集群中声明其从此刻负责提供该segment的查询,当实时节点收到该声明后也会立即声明其不再提供该segment的查询,接下来查询节点会转从该历史节点查询此segment的数据,而对于全局数据来说,查询节点会同时从实时节点(少量当前数据)和历史节点(大量历史数据)分别查询,然后做个结果的整合,最后再返回给用户。

基于 DataSource 与 Segment的数据结构
1 DataSource结构
与传统的RDBMS比较,Druid的DataSource可以理解为RDBMS的table
DataSource的结构包含以下几个方面:
  • 时间列(timestamp)表明每行数据的时间值,精确到毫秒
  • 维度列(dimension)
  • 指标列(metric)用于聚合和计算的列
无论是实时数据消费还是批量数据处理,Druid在基于DataSource结构 存储数据时即可选择对任意的指标列进行聚合操作,该聚合操作主要基于 维度列和时间范围
  1. 同维度列的值做聚合:所有维度的值都相同时,这一类行数据符合聚合操作
  2. 对指定时间粒度内的值做聚合
相对于其他时序数据库,Druid在数据存储时便可对数据进行聚合操作是其一大特点,该特点使得druid不仅能够节省存储空间,而且提高聚合查询效率。
2 segment结构
DataSource是个逻辑概念, Segment却是数据的实际物理存储格式,Druid正是通过Segment实现了对数据的横纵向切割操作,从数据按时间分布的角度看,通过参数segmentGranularity的设置,Druid将不同时间范围内的数据存储在不同的segment数据块中,这便是所谓的数据横向切割,这种设计为Druid带来了一个优点 ,按时间范围查询数据时,仅需要访问对应时间段内的这个segment数据块,而不需要进行全表数据范围的查询,这使得效率得到了极大的提高。
同时,在segment中也面向列进行数据压缩存储,这便是所谓的数据纵向切割,而且在segment中使用了Bitmap等技术对数据的访问进行优化。


3  扩展系统
Druid可以添加扩展系统,通过两种方法:
1)将扩展加载到Druid Service的classpath,Druid便能加载到相关扩展
2)在common.runtime.properties文件中通过druid.extensions.directory指定扩展目录

实时节点
实时节点主要负责即时摄入实时数据,以及生成segment数据文件。
实时节点通过Firehose来消费实时数据,Firehose是Druid中消费实时数据的模型,可以有不同的具体实现,比如Druid自带的基于Kafka High Level API实现的用于消费Kafka数据的druid-kafka-eight Firehose,还有低级的kafkaAPI,
同时,实时节点会通过另一个用于生成segment数据文件的模块plumber,按照指定的周期,按时将本周期内生产出的所有数据块合并成一个大的segment数据文件。
segment数据文件从制造到传播要经历一个完整的流程,步骤如下:
1) 实时节点生产出segment数据文件,并将其上传到DeepStorage中
2) segment数据文件的相关元数据信息被存放到metaStore(mysql)中
3) master节点(也就是coordinator节点)从metaStore里得知Segment数据文件的相关元数据信息后,将其根据规则的设置分配给符合条件的历史节点。
4) 历史节点得到指令后会主动从deepStorage中拉取指定的segment数据文件,并通过zookeeper向集群声明其负责提供该segment数据文件的查询服务。
5) 实时节点丢弃该segment数据文件,并向集群声明其不再提供该segment数据文件的查询服务。
从设计上看,实时节点拥有很好的可扩展性和高可用性,对于Druid版本来说,其消费kafka数据时一般使用Druid自带的用于消费kafka数据的druid-kafka-eight Firehose,使用pull的方式从kafka获取数据,而该Firehose能够让Druid有较好的可扩展性和高可用性,使用一组实时节点组成一个kafka consumer group来共同消费同一个kafka topic的数据,各个节点会负责独立消费一个或多个该topic,所包含的partition的数据,并且保证同一个partition不会被多于一个的实时节点消费,当每一个实时节点完成部分数据的消费后,会主动将数据消费进度offset提交到zookeeper集群,这样,当这个节点不可用时,该kafka consumer group会立即在组内对所有可用节点进行partition的重新分配,接着所有节点将会根据记录在zookeeper集群里的每一个partition的offset来继续消费为曾被消费的数据,从而保证所有数据在任何时候都会被Druid集群至少消费一次,进而实现了这个角度上的高可用性,同样的道理,当集群中添加新的实时节点时,也会触发相同的事件,从而保证了实时节点能够轻松实现线性扩展。
但是,这样的方式有个问题,当一个kafka consumer group内的实时节点不可用时,该方法虽然能够保证它所负责的partition里未曾被消费的数据能够被其他存活的实时节点分配且被消费,但是该不可用实时节点已经消费,但是没有传到deepStorage上且被其他历史节点下载的segment数据却会被集群所遗漏,从而形成了这个基于druid-kafka-eight Firehose的消费方案的高可用性的一个缺陷,解决这个问题有两种方式:
    1   想办法让不可用的实时节点重新货到集群可用的节点,那么当它重新启动时会将之前已经生成但尚未被上传的segment数据文件统统加载回来,并最终将其合并并上传到deepstorage,保证完整
    2   使用tranquility与索引服务对具体的kafka topic partition进行精确消费和备份,由于tranquility可以通过push的方式将指定的数据推向druid集群,因此它可以同时对同一个partition制造多个副本,所以当某个数据消费的任务失败,系统依然可以准确地选择使用另一个相同任务所创建的segment数据块。

历史节点
历史节点在启动的时候,首先会检查自己的本地缓存中已经存在的segment数据文件,然后从deepstorage中下载属于自己但目前不再自己本地磁盘上的segment数据文件, 无论是何种查询,历史节点都会首先将相关segment数据文件从磁盘加载到内存,然后在提供查询服务。
不难看出,历史节点的查询效率受内存空间富裕程度的影响很大,内存空间富余,查询时需要从磁盘加载数据的次数就少,查询速度就快。
原则上历史节点的查询速度与其内存空间大小和所负责的segment数据文件大小之比成正比关系。

对于一个分布式系统来说,在规划存储时需要同时考虑硬件的异构性和数据温度,异构性是指集群中不同节点的硬件性能不同,数据温度则是用来形容数据被访问的频繁程度。
热数据: 经常被访问,特点是总数据量不大,但要求响应速度最快
温数据:不经常被访问,特点是总的数据量一般,且要求响应速度尽量快
冷数据:偶尔被访问,特点是总的数据量占比最大,但响应速度不用很快
druid也考虑了这个问题,提出了层的概念 tier 将集群中所有的历史节点根据性能容量等指标分为不同的层,并且可以让不同性质的dataSource使用不同的层来存储segment数据文件。
历史节点有很高的可扩展性和高可用性,新的历史节点被添加后,会通过zookeeper被 协调节点发现,然后 协调节点将会自动分配相关的segment给它,原有的历史节点被移出集群后,同样会被协调节点发现,然后协调节点会将原本分配给它的segment重新分配给其他处于工作状态的历史节点。

查询节点
同时从实时节点和历史节点查询数据,合并后返回给调用方。druid集群中直接对外提供查询的节点只有查询节点,因此查询节点是整个集群的查询中枢点。

很多数据库都会利用缓存来存储之前的查询结果,当相似的查询再次发生时,可以直接利用之前存储在cache中的数据作为部分或全部的结果,而不用再次访问库中的相关数据,这样,在cache中的数据的命中率较高时,查询效率也会得到明显的提高。
Druid也是cache机制来提高自己的查询效率,Druid提供了两类介质以供选择:
  • 外部cache ,比如mencache
  • 本地cache 例如查询节点或者历史节点的内存作为cache
如果用查询节点的内存做cache,查询的时候会首先访问其cache,只有当不命中的时候才会去访问历史节点与实时节点以查询数据。

一般一个druid集群中只需要一个查询节点即可,但是为了防止出现单个节点失败导致无查询节点可用,通常会多加一个查询节点到集群中,无论访问哪个查询节点都能得到同样的查询结果,因此,在实践中也常常会通过nginx作为客户端查询的gateway来完成多个查询节点的负载均衡,达到集群高可用的效果。


协调节点
负责历史节点数据的负载均衡,已经通过rule管理数据的生命周期。
对于整个druid集群来说,其实并没有真正意义上的master节点, 因为实时节点与查询节点能自行管理并不受其他节点管理,但是对于历史节点来说,协调节点便是他们的master,因为协调节点将会给历史节点分配数据,完成数据分布在历史节点间的负载均衡,当协调节点不可访问时,历史节点依然可以提供查询服务,但是不能再接收新的segment数据了。
Druid利用针对每个DataSource设置的规则Rule来加载load或丢弃Drop具体的数据文件,以管理数据生命周期。可以对一个DataSource按顺序添加多条规则,对于一个segment数据文件来说,协调节点会逐条检查规则,当碰到当前segment数据文件符合某条规则的情况下,协调节点会立即命令历史节点对该segment数据文件执行这条规则--加载或丢弃,并停止检查余下的规则,否则继续检查下一条设置好的规则。
副本:Druid允许用户对某个dataSource定义其segment数据文件在历史节点中的副本数量,副本数量默认为1,即仅有一个历史节点提供某segment数据文件的查询,存在单点问题,如果用户社会中更多的副本数量,则意味着某segment数据文件在集群中存在多个历史节点中,当某个历史节点不可用的时候,还能从其他同样拥有该segment数据文件副本的历史节点中查询到相关数据--segment数据文件的单点问题就解决了。同样在集群做升级的情况下也能保证集群的查询服务在次过程中不间断。
高可用:只要在集群中添加若干个协调节点即可,当某个协调节点退出服务,集群中的其他协调节点依然自动完成相关工作。
索引服务
除了实时节点可以生产segment数据文件之外, Druid还提供了一组名为索引服务的组件也可以生产segment数据文件,相比实时节点生产的segment,索引服务的优点是对数据能用 pull的方式外,也可以支持push方式,不同于手工编写数据消费配置文件的方式,可以 通过API编程的方式来灵活定义任务配置,可以更灵活的管理与使用系统资源,可以完成segment副本数量的控制,能够灵活完成跟segment数据文件相关的所有操作,如合并,删除segment数据文件。
主从结构的架构:索引服务实际包括一组组件,以master-slave结构作为架构,统治节点overlord node为主节点,中间管理者middle manager为从节点。

统治节点作为索引服务的主节点,对外负责接受任务请求,对内负责将任务分解并下发到从节点(中间管理者),统治节点有以下两种运行模式
  • 本地模式    默认的,在该模式下,统治节点不仅负责集群的任务协调分配工作,也能负责启动一些苦力(peon)来完成一部分具体的任务
  • 远程模式 在该模式下,统治节点与中间管理节点分别运行在不同节点上,它不仅负责集群的任务协调分配工作,不负责具体的任务,统治节点提供restful 的访问方式,因此客户端可以通过HTTP POST请求向统治节点提交任务。
客户端可以发出查看任务状态,接受segment等任务,同时统治节点也有个控制台console

中间管理者是索引服务的工作节点,负责接受统治节点分配的任务,然后启动祥光的苦力(独立的JVM),类似于yarn



任务



猜你喜欢

转载自blog.csdn.net/yyqq188/article/details/79419739