一、认识
分布式的支持实时分析的数据存储系统,处理海量规模的数据和实时方面比传统的olap系统有显著的性能改善
-
特点
快速查询,数据预聚合和内存华,使用bitmap和各种压缩技术减少内存占用,并维护一些倒排索引,有效提高数据的查询效率,提高内存的使用率
水平扩展方面,把数据按照时间进行分区处理,对于高基数维度的数据还可以再进行segment的切片,历史数据可存储至HDFS、云服务等(需要查询时再装载到内存),节点故障还可以根据zk进行数据重构
不可变的过去,仅追加的未来
-
数据格式
在数据摄入之前,需要定义一个DataSource,包含三个部分:时间序列、维度、指标
数据摄入:支持实时流式数据和批处理数据
数据查询:原生采用json格式通过http请求,支持大多数的标准sql查询
二、架构
2.1、节点描述
Druid包含4个节点:
实时节点:消费流数据,以及生成segment文件
-
历史节点:加载生成好的数据文件,提供数据查询
历史节点在启动的时候,会先检查自己的本地缓存中已存在的Segment,再从deep storage中下载属于自己的但是不在本地磁盘的Segment文件,然后再把相关的Segment加载到内存里提供查询服务。查询效率跟历史节点的内存大小有很大关系
Druid采用层的概念,将集群中的历史节点按照性能容量等指标划分为不同的层,并且可以让不同性质的DataSource使用不用的层来存储Segment文件
高可靠与可扩展,节点的上下线通过zk进行状态通讯
协调节点:负责历史节点的数据负载均衡,以及通过规则(DataSource设置的加载或丢弃具体的数据文件)管理数据的生命周期(从元数据里获取Segment的相关信息,将其根据规则的设置分配给符合条件的历史节点)
-
查询节点:负责对外提供数据查询服务
查询节点提供数据的对外查询服务,一般考虑到负载均衡和高可靠会有多个查询节点
同时Druid依赖3个外部节点:
深度存储:存放生成的segment文件,提供给历史节点进行下载,存储介质可以是本地硬盘、云服务、HDFS等
元数据存储:存储segment文件的相关信息
分布式协调服务:提供一致性的协调服务组件,一般是Zookeeper
2.3、节点之间的工作联系
Druid类LSM-tree架构的实时节点,在消费实时流数据的时候,不会将数据写入到WAL,而是将实时流数据写入到堆内存缓冲区里(相当于HBase的Memstore),当条件满足后,会将数据flush到磁盘中生成一个数据块(Segment Split),同时会在周期性时间内将同一时间段内的数据块合并成一个大的数据块Segment(相当于HBase的Compact),然后再上传到Deep Storage中
当历史节点加载到了Segment后,会通过协调服务声明从此时开始负责该Segment的读取服务,当实时节点收到该声明后也会立即向集群声明不再负责对该Segment的查询服务。
对全局数据来说,查询节点会从实时节点(少量当前数据)和历史节点(大量历史数据)分别查询后再做一个结果的合并,最后再返回
2.2、存储
采用了LSM-tree(日志结构合并树)的方法进行数据存储
对比\树结构 | 二叉查找树 | B+树 | 日志结构合并树 |
---|---|---|---|
特点 | 二叉有序树,保证左边节点一定小于根节点 | 树的内部节点(索引节点)只保存键,叶子节点保存值,并且有指向相邻节点的指针 | 使用了两种树的数据结构来进行数据存储,C0树负责新的数据的插入更新和读请求,存放在内存缓存中,C1树由C0树的数据刷写到磁盘中生成,特点是有序且不可更改(类似HBase的数据写入与合并过程) |
查找效率 | log2N-logN,跟树的高度、平衡性相关 | 所有叶子节点与根节点的距离相同,任何查询的效率很相似 | 存在内存中的数据提供快速读写更新,刷到磁盘中的数据使用布隆布隆过滤器进行读取 |
优点 | 实现简单,树平衡的状态下能达到log2N的查找效率 | 树的高度较低,查找效率高,与二叉树相比,树的更新操作直接从叶子节点开始,以较小的代价实现自平衡 | 利于海量数据的快速写入,海量数据中快速读取某条数据 |
缺点 | 极端非平衡状态下,树的查找效率达到logN,相当于顺序查找, | 随着数据的不断插入,叶子节点会发生分裂,导致逻辑上原本连续的数据会存在不同的物理磁盘位置上,在做范围查询时会导致较高的IO | 不适合大范围的数据量查询,不适合更新删除操作频繁的场景 |
2.4、数据结构
-
DataSource:类似于mysql的表,包含时间字段(默认使用utc格式且精确到毫秒级别,这个时间字段是聚合与查询的重要维度)、数据维度、指标,DataSource是一个逻辑概念
-
Segment:Segment是数据的物理存储格式,Druid通过Segment对数据进行了横纵向的数据切割。横向指的是按照设定的时间粒度进行将数据划分到不同Segment的操作,这一设计使得Druid在查询时不必再进行明细数据的范围扫描,只需对所在时间范围内的数据块进行读取操作,提高了数据的查询效率;纵向指的是Druid使用了Bitmap等压缩技术对数据块进行了压缩
sequenceDiagram participant 实时节点 participant 元数据管理 participant 深度存储 participant 协调节点 participant 历史节点 participant zk 实时节点-->>元数据管理: 消费实时数据生成Segment的元信息 实时节点->>深度存储: 传输实时消费生成的Segment文件 元数据管理-->>协调节点: 协调节点-->>元数据管理: 协调节点得知Segment元信息 协调节点-->>zk: 根据规则分配给符合条件的历史节点 zk-->>协调节点: 历史节点->>深度存储: 从存储系统里下载指定的Segment zk-->>历史节点: 获取对应Segment的下载指令 历史节点-->>zk: 通过zk声明其提供该Segment的查询服务 实时节点-->>zk: 通过zk声明不再提供该Segment的查询服务