大数据-155 Apache Druid 架构与原理详解 数据存储 索引服务 压缩机制
点一下关注吧!!!非常感谢!!持续更新!!!
目前已经更新到了:
- Hadoop(已更完)
- HDFS(已更完)
- MapReduce(已更完)
- Hive(已更完)
- Flume(已更完)
- Sqoop(已更完)
- Zookeeper(已更完)
- HBase(已更完)
- Redis (已更完)
- Kafka(已更完)
- Spark(已更完)
- Flink(已更完)
- ClickHouse(已更完)
- Kudu(已更完)
- Druid(正在更新…)
章节内容
上节我们完成了如下的内容:
- Apache Druid 基础架构 详解
- Apache Druid 架构演进 详解
数据存储
- Druid中的数据存储在被称为DataSource中,DataSource类似RDBMS中的Tablet
- 每个DataSource按照时间划分,每个时间范围成为一个Chunk(比如按天分区,则一个Chunk为一天)
- 在Chunk中数据被分为一个或多个Segment,Segment是数据实际存储结构,Datasource、Chunk只是一个逻辑概念
- Segment是按照时间组织称Chunk,所以在按照时间查询数据时,效率非常高。
- 每个Segment都是一个单独的文件,通过包含几百万行的数据
数据分区
- Druid处理的是事件数据,每条数据都会带有一个时间戳,可以使用时间进行分区
- 上图指定了分区粒度为天,那么每天的数据都会被单独存储和查询
Segment内部存储
- Druid采用列式存储,每列数据都是在独立的结构中存储
- Segment中的数据类型主要分为三种:
- 类型1 时间戳:每一行数据,都必须有一个TimeStamp,Druid一定会基于事件戳来分片
- 类型2 维度列:用来过滤Fliter或者组合GroupBY的列,通过是String、Float、Double、Int类型
- 类型3 指标列:用来进行聚合计算的列,指定的聚合函数 sum、average等
MiddleManger节点接受到Ingestion的任务之后,开始创建Segment:
- 转换成列式存储格式
- 用bitmap来建立索引(对所有的dimension列建立索引)
- 使用各种压缩算法
- 算法1:所有的使用 LZ4 压缩
- 算法2:所有的字符串采用字典编码、标识以达到最小化存储
- 算法3:对位图索引使用位图压缩
Segment创建完成之后,Segment文件就是不可更改的,被写入到深度存储(目的是为了防止MiddleManager节点宕机后,Segment丢失)。然后Segment加载到Historicaljiedian,Historical节点可以直接加载到内存中。
同时,Metadata store 也会记录下这个新创建的Segment的信息,如结构、尺寸、深度存储的位置等等
Coordinator节点需要这些元数据来协调数据的查找。
索引服务
索引服务是数据导入并创建Segment数据文件的服务
索引服务是一个高可用的分布式服务,采用主从结构作为架构模式,索引服务由三大组件构成:
- overlord 作为主节点
- MiddleManage作为从节点
- peon用于运行一个Task
索引服务架构图如下图所示:
服务构成
Overlord组件
负责创建Task、分发Task到MiddleManger上运行,为Task创建锁以及跟踪Task运行状态并反馈给用户
MiddleManager组件
作为从节点,负责接收主节点分配的任务,然后为每个Task启动一个独立的JVM进程来完成具体的任务
Peon(劳工)组件
由 MiddleManager 启动的一个进程用于一个Task任务的运行
对比YARN
- Overlord 类似 ResourceManager 负责集群资源管理和任务分配
- MiddleManager 类似 NodeManager 负责接收任务和管理本节点的资源
- Peon 类似 Container 执行节点上具体的任务
Task类型
- index hadoop task:Hadoop索引任务,利用Hadoop集群执行MapReduce任务以完成Segment数据文件的创建,适合体量较大的Segments数据文件的创建任务
- index kafka task:用于Kafka数据的实时摄入,通过Kafka索引任务可以在Overlord上配置一个KafkaSupervisor,通过管理Kafka索引任务的创建和生命周期来完成Kafka数据的摄取
- merge task:合并索引任务,将多个Segment数据文件按照指定的聚合方法合并为一个segments数据文件
- kill task:销毁索引任务,将执行时间范围内的数据从Druid集群的深度存储中删除
索引及压缩机制
Druid的查询时延低性能好的主要原因是采用了五个技术点:
- 数据预聚合
- 列式存储、数据压缩
- Bitmap索引
- mmap(内存文件映射方式)
- 查询结果的中间缓存
数据预聚合
- Druid 通过一恶搞RollUp的处理,将原始数据在注入的时候就进行了汇总处理
- RollUp可以压缩我们需要保存的数据量
- Druid会把选定的相同维度的数据进行聚合操作,可以存储的大小
- Druid可以通过queryGranularity来控制注入数据的粒度,最小的queryGranularity是millisecond(毫秒级别)
Roll-Up
聚合前:
聚合后:
位图索引
Druid在摄入的数据示例:
- 第一列为时间,Appkey和Area都是维度列,Value为指标列
- Druid会在导入阶段自动对数据进行RollUp,将维度相同组合的数据进行聚合处理
- 数据聚合的粒度根据业务需要确定
按天聚合后的数据如下:
Druid通过建立位图索引,实现快速数据查找。
BitMap索引主要为了加速查询时有条件过滤的场景,Druid生成索引文件的时候,对每个列的每个取值生成对应的BitMap集合:
索引位图可以看作是:HashMap<String, BitMap>
- Key就是维度的值
- Value就是该表中对应的行是否有该维度的值
SQL查询
SELECT sum(value) FROM tab1
WHERE time='2020-01-01'
AND appkey in ('appkey1', 'appkey2')
AND area='北京'
执行过程分析:
- 根据时间段定位到Segment
- appkey in (‘appkey1’, ‘appkey2’) and area=‘北京’ 查到各自的bitmap
- (appkey1 or appkey2)and 北京
- (110000 or 001100) and 101010 = 111100 and 101010 = 101000
- 符合条件的列为:第一行 & 第三行,这几行 sum(value)的和为40
GroupBy查询
SELECT area, sum(value)
FROM tab1
WHERE time='2020-01-01'
AND appkey in ('appkey1', 'appkey2')
GROUP BY area
该查询与上面的查询不同之处在与将符合条件的列:
- appkey1 or appkey2
- 110000 or 001100 = 111100
- 将第一行到第四行取出来
- 在内存中做分组聚合,结果为:北京40、深圳60