Druid架构设计
Druid的架构设计是对云友好和易维护的多处理分布式架构。架构在集群中有最大的灵活性,可以配置和独立扩展每一个Druid的节点类型。这样的架构设计也提供了更加强的容错性。一个节点的宕机不会立刻影响到另一个节点。
节点和服务器
Druid节点类型简介:
- Coordinator节点:管理集群的元数据。
- Overlord节点:控制数据的镊取。
- Broker 节点:处理外部客户端的查询。
- Router节点:处理Coordinator、Overlord、Broker上的路由选择。
- Historical节点:存储查询表的数据。
- MiddleManager节点:负责镊取数据。
Druid节点可以随意部署,但是为了简化部署,建议把上述6中节点部署成3类型服务器。
- Master:包括Coordinator和Overlord。管理元数据和管理数据镊取。
- Query:包括Broker和Router。管理外部客户端的查询请求。
- Data:包括Historical和MiddleManager。存储数据和执行镊取数据任务。
外部依赖
除了内置的节点类型,Druid还有3中外部的依赖节点类型。
深度存储节点
每一个Druid服务器可以访问共享的文件存储。在集群部署中,共享文件存储通常是分布式对象存储,例如S3、HDFS或者网络文件系统。在单节点部署,通常存储在本地磁盘。Druid使用深度存储存储所有镊取进入系统数据。
Druid的深度存储只能作为你数据的备份并在后台的节点间传输数据的方式。
查询时Historical不会从深度存储读取数据,而是在查询之前而是读取本地磁盘的预取的数据块(segments)。这意味着在查询数据时,Druid没有必要访问深度存储,从而尽可能的最大化的减少了查询延迟。这要求Druid必须在深度存储和Historical节点都有计划加载的磁盘空间。
深度存储是Druid弹性、容错设计非常重要的部分。即使任何一个单独的服务器宕机和重新配置,Druid可以从深度存储中引导恢复。
元数据存储
元数据存储存储了各种共享系统的元数据,例如块数据(segment)的可用性信息和任务信息。在集群部署中,元数据通常使用传统的关系型数据库存储,例如PostgreSQL 、Mysql。在单服务器部署时,元数据使用Derby存储。
Zookeeper
用于互联网服务发现,服务协调,主服务选举。
架构
下图展示了在推荐的Master节点、Query节点、数据节点三种节点的部署情况下查询和数据流在架构之间的情况。
Druid架构
存储设计
数据源和数据段(segments)
Druid数据存储在数据源中,数据源类似于传统关系数据库中的表。每一个数据源被时间分割,也可以选择被其他的属性值分割。每一个时间范围是一个数据块(chunk)(例如,时间范围是一天,数据源会一天一个分片)。一个数据块(chunk)被分割成一个或多个数据段(segments)。每一个数据段(segment)是一个文件,通常包括几百万行数据。几个数据段组成数据块,数据块在时间线上如下图所示:
数据块
一个数据源可能由几个数据段组成,也有可能是上千或者上百万个数据段组成。MiddleManager创建每一个数据段,在创建的时候数据段是易变的,没有提交的。数据段创建的数据文件是被压缩的而且可以支持快速查询,数据段的创建过程包含如下几个步骤:
- 转换成列式存储
- bitMap索引
- 多样性压缩算法
- 对文本列字典编码成id最小化存储
- bitMap索引进行bitMap压缩
- 对所有的列进行类型感知的压缩
数据段会定期的提交和发布。在这时,数据段会存储到深度存储,然后数据段会不可变,从MiddleManagers转移到Historical,数据段的入口信息会记录在元数据存储中。数据段的入口信息是自描述位源数据,包括数据块的模式、大小、在深度存储的位置。Coordinator利用数据块的入口信息知道数据在集群是否可用。
索引
通过索引机制来创建新数据段,通过handoff机制来发布数据块和在Historical开始服务。机制如下:
1、索引任务开始运行和创建一个新的数据段。任务在开始创建数据段之前需要确定数据段的标识符。对于追加索引任务(例如Kafka任务或者追加模式的索引任务)然后Overlord会收集数据和默认会把新的数据分区加入已经存在的数据段中。对于覆盖的索引任务(例如hadoop任务,非追加模式的索引任务)会锁住一段时间范围,然后创建一个新版本的数据段和一系列新数据段。
2、如果索引任务是一个实时任务(例如Kafka任务),这个时候的数据段是可以立刻查询到,可用的,但是还没有发布。
3、当索引任务结束从数据段读取数据,这个数据段会存储到深度存储,然后发布这个数据段,把数据段的相关信息存储到元数据。
4、如果索引任务是一个实时任务,会从Historical加载数据段。如果不是实时任务,索引任务已经存在。
Coordinator / Historical上的机制:
1、Coordinator定期把新发布的数据段(segments)提交到元数据存储。(默认一分钟提交一次)
2、当Coordinator发现已经发布、使用过但是不可用的数据段,Coordinator节点会指派Historical加载这些数据段。
3、Historical加载上述数据段,然后这些数据段开始服务。
4、此时索引服务开始切换索引。
Segment标识符
Segment标识符由下面四个部分组成:
- 数据源名字。
- 时间区间。数据段(segment)有对应的时间块时间,在镊取数据的时候会指定数据段粒度相对应的时间区间。
- 版本号。版本号一般是数据段第一次创建的时候的ISO8601时间戳。
- 分区标号。分区标号是一个int类型,和数据源名字、时间区间、版本号组合在一起是唯一的,但是没有必要是连续的。
例如:一个数据段的数据源是clarity-cloud0
,时间块是2018-05-21T16:00:00.000Z/2018-05-21T17:00:00.000Z
,版本是2018-05-21T15:56:09.909Z
,分区标号是1,因此此数据段的标识符如下:
clarity-cloud0_2018-05-21T16:00:00.000Z_2018-05-21T17:00:00.000Z_2018-05-21T15:56:09.909Z_1
如果数据段的分区标号是0,那么数据标识符可以忽略分区标号。数据块的数据段编号是从0开始。例如跟上述同一个数据块中分区编号为0的数据段的标识符如下:
clarity-cloud0_2018-05-21T16:00:00.000Z_2018-05-21T17:00:00.000Z_2018-05-21T15:56:09.909Z
数据段(Segment)的版本
数据段的版本是为了支持批量模式的覆盖。如果你只是追加数据,那么每一个数据块只有一个版本。但是当出现一个同一个数据源同一个时间区间间隔时,高版本号的数据会覆盖低版本的数据,这个时候老版本的数据会从集群移除。
新老版本的数据切换对用户来说瞬间的。Druid开始加载新版本的数据段之前这部分数据是查询不到的,等数据完全加载完成之后,查询使用的数据瞬间切换到新版本数据段,然后花几分钟删除老版本的数据。
数据段(Segment)的生命周期
数据段(Segment)的生命周期涉及到如下三个方面:
1、元数据:当一个数据段创建完成的时候数据段元数据会存储到元数据存储器中。元数据是一个及kb的json数据。数据段的元数据存储到元数据存储器这个过程称作发布
。数据段的元数据信息中有个布尔值的字段used
控制数据段是否可以查询。实时任务创建的数据段在发布之前就可以查询,然而数据段完成之后只有发布了,此数据段不会再接受数据的追加。
2、深度存储:数据段已经构造完成,数据段的数据文件会发送到深度存储,然后数据块发布元数据到元数据存储。
3、可查询性:数据块的查询可用性存在于实时任务节点和Historical节点。
你可以通过Druid SQL的sys.segment
这张表来查看当前活跃的数据段。
-
is_published
:当数据段的元数据已经发布到元数据存储且used
是true
时时,此字段为true
。 -
is_available
:当数据段在实时节点或Historical节点可以查询时,此字段值为true
。 -
is_realtime
:当数据段只在实时节点可以用是值为true
。当数据源在实时镊取数据时,此字段值为true
;当此数据段发布之后并切换源数据之后,值会变成false
。 -
is_overshadown
:当数据段已经发布并且完全被其他的已经发布的数据段覆盖时,值为true
。当数据源在实时镊取数据时,此字段值为true
;通常这是一个临时的状态,在此状态下数据段的used
标志会自动的设置为false
。
查询
查询需求首先发送到Broker,然后Broker会识别出查询需求需要用到数据段。识别数据段的方法是通过删除数据源上不相关的时间区间的数据段或者删除数据源上不相关的属性的数据段,这取决于数据源分割方式。Broker会识别出这些数据段分别在Historical和MiddlerManager上哪些节点上,并把子查询发送到数据段每一个节点。Historical/MiddlerManager会接受查询请求并执行查询,把结果返回。Broker接到查询结果,然后把查询结果组合成最后的结果,最后把结果发送给最初的查询客户端。
Broker的数据段剪枝是限制查询时扫描数据数量的重要方法,但是不是唯一方法。更小粒度水平过滤器的的剪枝效果比上面的Broker剪枝效果更好,因为在查询之前数据段里面的索引结构可以让Druid找出过滤数据。当Druid知道查询需要用到哪些行,Druid只需要访问这些需要的的行的列就可以了。
Druid使用三种不同技术最大化提高查询性能:
- 每一个查询只访问剪枝后的数据段。
- 在数据块中使用索引识别出需要访问的行。
- 在数据块中只读查询相关的行和列。
翻译:https://druid.apache.org/docs/latest/design/architecture.html