Apache durid数据库

简介

入门介绍主要是基于官网的技术Druid | Technology官网的技术做了一些翻译和个人加工,大家若英文好,也可以直接看官网介绍。

druid首先是一个完全开源的分布式时序数据库(Timeseries DB),这一点要优于influxDB(因为它只是单机版本开源,分别是版本只存在于InfluxDB enterprise)

从下图我们可以看到Druid锁关注的领域在时序数据库(TimeSeries DBS),数据仓库(Data warehouse),搜索系统(Search system)三者间的结合处,Druid官方也不甘心Druid只是时序数据库,更希望人们把它叫做高性能的实时分析数据库。

特点

  • 列式存储:Druid基于LSM-Tree数据模型,持久层按列存储和压缩,那么按特定列的排序、分组和扫描就会非常快。
  • 原生支持搜索索引:Druid可以为字符串创建倒排索引实现搜索过滤。
  • 流批摄取:适配多种连接器和流处理(Kafka,HDFS,AWS S3等)支持开箱即用
  • 柔性Schemas(架构):Druid可以优雅处理模式演进和内嵌数据。
  • 针对时间进行优化分区:Druid会自动基于时间分区,那么这就对基于时间的查询,相比于传统数据库带来了显著的提升。(备注:基于时间的分区对于时序数据库的查询很重要,InfluxDB也有类似的策略,但OpenTSDB则没有)
  • SQL支持:除了原生的基于JSON语言,Druid还可以通过HTPP或JDBC使用SQL
  • 水平扩展,Druid已经被用于生产环境下每秒摄取百万事件,对于保留多年的数据提供亚秒级查询。
  • 操作简单:集群中增加或删除服务器所形成的扩张与搜索,Druid都会自动平衡。围绕服务器故障的容错架构路由

摄取

Druid对于数据源的摄取既支持流式也支持批量,例如:连接到Kafka加载流式数据,或者连接HDFS进行批量数据加载,不过Druid会对加载的原始数据进行所谓“Indexing”的转换,形成一种更加为了读取优化的格式,Druid内部称为:“segment”。

存储

Druid就像许多数据分析存储一样,数据以列存储,根据列的字段类型(例如:string、number等等),应用不同的压缩或编码方法。Druid还基于列类型构建不同类型的索引。

类似于搜索系统,Druid为了对字符串列快速查询和过滤,建立了倒排索引。

类似于时序数据库,为了能面向时间的快速查询,Druid更智能地按照时间进行数据分区。

不同于许多传统系统,Druid在数据摄取时可以有选择地预聚合,这个预聚合步骤被称为上卷(rollup),这能极大地节省存储空间。

查询

Druid支持通过json-over-http或sql进行数据查询。除了标准sql功能之外,druid还支持一些转悠的功能,利用近似算法套件提供了快速计数,排名和分位数(quantiles)

架构

Druid基于微服务架构,可以被认为被拆封开了的数据库,在druid中,每个核心服务(摄取,查询,调度)都能独立或联合的方式,部署在必要的硬件上。

Druid如此明确命名每个主服务,就是为了让操作者根据用例和工作负载好调整每个服务,例如根据工作负载要求,操作者需要投入更多的资源给Druid摄取服务,同时讲更少的资源分配给Druid查询服务。

那么这种架构下,Druid服务就算出现单独故障也是不影响其他服务的操作。

存储

Apache Druid依赖深度存储,元数据库和分布式协调器

  • 深度存储主要是解决数据高可靠问题,也就是说,如果Druid数据节点的持久化数据出现丢失,可以从深度存储中恢复。深度存储可以使用本地文件,Hadoop HDFS,Amazon S3等方式,我们这里选择HDFS。
  • 元数据存储集群元数据,包括Datasource,Segment,Supervisors(监督者)、Tasks、Rules等前期配置元数据与运行期产生的各项元数据。我们可以使用Derby、MySQL、PostgreSQL等方式,我们这里选择PostgreSQL。
  • 分别是协调器是Apache Druid的核心组件,应用与Druid集群不用子服务之间的状态条件。Druid使用的分布式协同器为Zookeeper。

配置

部署Druid集群主要考虑$DRUID_HOME/conf/druid/cluster下面的四个目录,分别是:_common、data、master、query,代表了通用性配置、数据节点配置、主节点配置、查询节点配置。

如果我们是按照最少的硬件资源配置,那就至少分配3个节点,每个节点都只具有1个功能服务。

  • 通用配置,对应了_common目录。
  • 主节点配置:对应master/coordinator-overlord目录,运行coordinator服务、overlord服务。
  • 数据节点,对应data/historical,data/middleManager目录,运行historical服务、middleManager服务。
  • 查询节点,对应query/broker,query/router目录,运行broker服务、route服务。

在此基础之上增加节点的话,建议先增加数据节点,实现数据节点在Druid上双副本(默认),这样不仅可以带来数据节点服务的高可用,而且查询请求也可以实现多数据节点的均衡负载。

然后再增加查询节点和主节点,增强集群服务的高可用性和查询方面应对并发请求的均衡负载。

元数据相关概念

Segment

Segment是druid管理数据的最基本单元,一个Datasource包含多个Segment,每个Segment保存着Datasource某个时间段的数据,这个特定时间段数据组织方式是通过Segment的payload(json)来定义的,payload内部定义了某个Segment的维度,指标等信息。

同一个Datasource的不同segment的payload信息(维度,指标)可以不相同,Segment信息主要包含下面几部分:

  • 时间段interval:用于描述数据的开始时间和结束时间
  • DataSource:用字符串表示,指定的segment隶属于哪个datassource
  • 版本version:用一个时间表示,时间段Interval相同的Segment,版本高的Segment数据可见,版本低的Segment会被删除掉。
  • Payload信息:主要包含了Segment的维度和指标信息,以及Segment数据存在DeepStorage位置信息等等。

Datasource

DataSource相当于关系型数据的表,DataSource的Schema是根据其可用的Segment动态变化的,如果某个Datasource没有可用的Segment(userd=1),在Druid-web的Datasource列表界面和查询界面看不到这个Datasource。

源数据库中druid_dataSource表并没有保存Schema信息,只保存了该Datasource对应实时任务消费素具的偏移量信息,都说Druid的Datasource相当于关系型数据库的表,但Druid中包(Datasource)Schema信息,并不是定义在druid_dataSource元数据表里。

那么在druid-web 页面上看到的Datasource 的Schema信息是怎么来的呢?

其实它是实时根据该Datasource下所有Segment元数据信息合并而来,所以DataSource的Schema是实时变化的,

这样设计的好处是很好的适应了Datasource维度不断变化的需求在 :

Rule

Rule定义了Datasource的Segment留存规则,主要分两大类:Load和Drop。

Load 表示Segment 保留策略。

Drop 表示 Segment 删除策略。

Load/Drop规则均有三个子类,分别是Forever Load/Drop,Interval Load/Drop以及Period Load/Drop,一个Datasource包含1个或多个Rule规则,如果没有定义Rule规则就使用集群的Default Rule规则。

Datasource Rule规则列表是有序的(自定义规则在前面,集群默认规则在后面),在运行Run规则时,会对该Datasource下所有可用的Segment信息,按照Run规则的先后顺序进行判断,只要Segment满足某个Rule规则,后面的规则Rule就不再运行(如图:Rule处理逻辑案例)。Rule规则主要包含下面几部分信息:

【类型】:类型有删除规则和加载规则。

【Tier和副本信息】:如果是Load规则,需要定义在不同Tier的Historical机器副本数。

【时间信息】:删除或加载某个时间段的Segment。

[
   {
   "period": "P7D",
   "includeFuture": true,
   "tieredReplicants": {
     "_default_tier": 1,
     "vStream":1
   },
   "type": "loadByPeriod"
 },
 {
   "type": "dropForever"
 }
 ]

ask主要用于数据的摄入(本文主要讨论实时摄入kafka数据的任务),在Task的运行过程中,它会根据数据时间列产生一个或者多个Segment,Task分为实时和离线任务。

实时任务(kafka)是Overload进程根据Supervisor定义自动生成;
离线任务(类型:index_hadoop,index_parallel)则需要外部系统通过访问接口方式提交。

每个任务主要包含下面几部分信息:

【dataSchema】:定义了该任务生成的Segment中有哪些维度(dimensionsSpec),指标(metricsSpec),时间列(timestampSpec),Segment粒度(segmentGranularity),数据聚合粒度(queryGranularity)。

【tuningConfig】:任务在摄入数据过程中的优化参数(包括Segment生成策略,索引类型,数据丢弃策略等等),不同的任务类型有不同的参数设置。

【ioConfig】:定义了数据输入的源头信息,不同的数据源配置项有所不同。

【context】:关于任务全局性质的配置,如任务Java进程的option信息。

【datasource】:表示该任务为那个Datasource 构造Segment。

2.5 Supervisor
Supervisor 用于管理实时任务,离线任务没有对应的Supervisor,Supervisor与Datasource是一对一的关系,在集群运行过程中Supervisor对象由Overlord进程创建,通过Overlord接口提交Supervisor信息后,会在元数据库(MySQL)中持久化,Supervisor内容与Task相似,可以认为实时Task是由Supervisor 克隆出来的。

整体架构

前面笼统地介绍了Druid元数据相关概念,为了深入的了解Druid元数据,先从宏观的角度认识一下Druid的整体架构。

可以形象地把Druid集群类比为一家公司,以Druid不同组件类比这家公司中不同类型员工来介绍Druid集群,Druid组件大体可以分为三类员工:领导层,车间员工和销售员工,如下图:

领导层: 领导根据外部市场需求(Overlord接收外部摄入任务请求),然后把生产任务下发到对应的职业经理人(MiddleManager),职业经理人管理团队(MiddleManager 启动Peon进程),下发具体生产任务给不同类型的员工(Peon进程)。

车间员工: 生产员工(Peon) 负责生产产品(segment),仓库管理员(Coordinator)负责把生产出来的产品(segment)分配到仓库(Historical)中去。

销售员工: 销售员(Broker)从生产员工(Peon)获取最新的产品(segment),从仓库中获取原来生产的产品(segment),然后把产品整理打包(数据进一步合并聚合)之后交给顾客(查询用户)。

上面通过类比公司的方式,对Druid集群有了初步的整体印象。

下面具体介绍 Druid 集群架构,Druid 拥有一个多进程,分布式架构,每个Druid组件类型都可以独立配置和扩展,为集群提供最大的灵活性。

一个组件的中断不会立即影响其他组件。

下面我们简要介绍Druid各个组件在集群中起到的作用。

  • Overlord:负责接受任务,协调任务的分配、创建任务锁以及收集、返回任务运行状态给调用者。当集群中有多个Overlord时,则通过选举算法产生leader,其他follower作为备份。
  • MiddleManagaer:负责接受Overload分配的实时任务,同时创建新的进程用于启动peon来执行实时任务,每一个MiddleManager可以运行多个peon实例,每个实时peon既提供实时数据查询也负责实际数据的摄入工作。
  • Coordinator:主要负责Druid集群中的Segment的管理与发布(主要是管理历史Segment),包括加载新的Segment、丢弃不符合规则的Segment、管理Segment备份以及Segment负载均衡等。如果集群中存在多个Coordinator Node,则通过选举算法产生Leader,其他Follower作为备份。
  • Historical:负责加载Druid中非实时窗口内且满足加载规则的所有历史数据的Segment。每一个Historical Node只与Zookeeper保持同步,会把加载完成的Segment同步到Zookeeper
  • Broker:Boker Node是整个集群查询的入口,Broker实时同步Zookeeper上保存的集群内所有已发布的Segment元信息,即每个Segment保存在哪些存储节点上,Broker为Zookeeper中每个Datasource创建一个timeline,timeline按照时间顺序描述了每个segment的存放位置。

每个查询请求都会包含dataSouce以及interval信息,Broker根据这俩项信息去查找timeline中满足条件的Segment所对应的存储节点,并将查询请求发往对应的节点。

Druid元数据存储介质

Druid 根据自身不同的业务需要,把元数据存储在不同的存储介质中,为了提升查询性能,同时也会将所有元数据信息缓存在内存中。把历史数据的元数据信息保存到元数据库(MySQL),以便集群重启时恢复。

由于Druid拥有一个多进程,分布式架构,需要使用zookeeper进行元数据传输,服务发现,主从选举等功能,并且历史节点会把Segment元数据存储在本地文件。

那么历史节点(historical)为什么会把该节点的Segment元数据信息缓存在自己节点的本地呢?

这是因为历史节点发生重启后,读取segment的元数据信息不用去Mysql等其他元数据存储截止进行跨节点读取而是本地读取,这样就极大提升了历史节点数据的恢复效率。

下面分别介绍这些存储介质(内存、元数据库、Zookeeper、本地文件)里的数据和作用。

元数据库

MySQL 数据库主要用于长期持久化 Druid 元数据信息,比如segment部分元数据信息存在druid_segments表中,历史的Task信息存在druid_tasks,Supervisor信息存储在druid_supervisors等等。

Druid部分服务进程在启动时会加载元数据库持久化的数据,如:Coordinator进程会定时加载表druid_segments 中used字段等于1的segment列表,Overlord 启动时会自动加载druid_supervisors表信息,以恢复原来实时摄入任务等等。

zokeeper

Zookeeper 主要存储 Druid 集群运行过程中实时产生的元数据,Zookeeper 数据目录大概可以分为Master节点高可用、数据摄入、数据查询3类目录。

下面介绍Druid相关Zookeeper目录元数据内容。

内存

Druid为了提升元数据访问的效率会把元数据同步到内存,主要通过定时SQL 查询访问方式同步MySQL元数据或者使用Apache Curator Recipes实时同步Zookeeper上的元数据到内存如下图。

每个进程中的元数据不一样,下面一一介绍一下各个角色进程缓存了哪些数据。

4.3.1 Overlord
实时同步Zookeeper目录(d r u i d . z k . p a t h s . i n d e x e r . b a s e / a n n o u n c e m e n t s )下的数据,使用变量 R e m o t e T a s k R u n n e r : : z k W o r k e r s (类型: M a p )存储,每 Z k W o r k e r 对应一个 M M 进程,在 Z k W o r k e r 对象中会实时同步 Z o o k e e p e r 目录( {druid.zk.paths.indexer.base}/announcements)下的数据,使用变量RemoteTaskRunner::zkWorkers(类型:Map)存储,每ZkWorker对应一个MM进程,在ZkW orker对象中会实时同步Zookeeper目录(druid.zk.paths.indexer.base/announcements)下的数据,使用变量RemoteTaskRunner::zkWorkers(类型:Map)存储,每ZkWorker对应一个MM进程,在ZkWorker对象中会实时同步Zookeeper目录({druid.zk.paths.indexer.base}/status/${mm_host:port})任务信息,使用RemoteTaskRunner::runningTasks变量存储。

默认每分钟同步数据库中druid_tasks active = 1的数据,使用变量TaskQueue::tasks(类型:List )存储,在同步时会把内存中的Task列表与最新元数据里的Task列表进行比较,得到新增的task列表和删除的task列表,把新增的Task加到内存变量TaskQueue::tasks,清理掉将要被删除的task

3 4.3.2 Coordinator

默认每1分钟同步元数据库中druid_segemtns 中列 used=1的segment列表到变量SQLMetadataSegmentManager::dataSourcesSnapshot。

默认每1分钟同步元数据库druid_rules表信息到SQLMetadataRuleManager::rules变量

使用CoordinatorServerView类(后面会介绍)实时同步d r u i d . z k . p a t h s . b a s e / a n n o u n c e m e n t s , {druid.zk.paths.base}/announcements,druid.zk.paths.base/announcements,{druid.zk.paths.base}/segments的数据,用于与元数据库中的segment对比,用来判断哪些segment应该加载或删除。

4.3.3 Historical
会实时同步d r u i d . z k . p a t h s . b a s e / l o a d Q u e u e / {druid.zk.paths.base}/loadQueue/druid.zk.paths.base/loadQueue/{historical_host:port} 下的数据,进行segment的加载与删除操作,操作完成之后会主动删除对应的节点。

Historical通过上报segment信息到${druid.zk.paths.base}/segments来暴露segment。

4.3.4 MiddleManager
会实时同步d r u i d . z k . p a t h s . i n d e x e r . b a s e / t a s k s / {druid.zk.paths.indexer.base}/tasks/druid.zk.paths.indexer.base/tasks/{mm_host:port}的数据,进行任务(peon)进程的启动,启动完成之后会主动删除对应的节点。

MiddleManager上报segment信息到${druid.zk.paths.base}/segments来暴露segment。

4.3.5 Broker
使用BrokerServerView类实时同步d r u i d . z k . p a t h s . b a s e / a n n o u n c e m e n t s , {druid.zk.paths.base}/announcements,druid.zk.paths.base/announcements,{druid.zk.paths.base}/segments的数据,构建出整个系统的时间轴对象(BrokerServerView::timelines) 作为数据查询的基本依据。同步过程中类的依赖关系如下图。

下层的类对象使用监听上层类对象的方式感知sement的增删改,并做相应的逻辑处理, 会同时监听d r u i d . z k . p a t h s . b a s e / a n n o u n c e m e n t s 和 {druid.zk.paths.base}/announcements和druid.zk.paths.base/announcements和{druid.zk.paths.base}/segments的数据的数据变化,通过回调监听器的方式通知到下层类对象。

本地文件

本地文件的元数据主要用于恢复单个节点时读取并加载。

例如:Historical节点第一个数据目录下的info_dir目录(如:/data1/druid/segment-cache/info_dir),保存了该节点加载的所有segment信息,在Historical进程重启时会读取该目录下的segment元数据信息,判断本地是否有该segment的数据,如果没有就去深度存储系统(hdfs)下载,数据下载完成后会上报segment信息到Zookeeper(路径:${druid.zk.paths.base}/segments)。

Druid 元数据相关业务逻辑

摄入任务管理

由于Druid组件类型比较多,业务逻辑比较复杂,从整体到局部方式,从宏观到细节,循序渐进地了解Druid的业务逻辑,以便了解Druid元数据在业务逻辑中发挥的作用。

5.1 Druid 元数据整体业务逻辑
前面从整体了解了 Druid 集群各个组件的协作关系,下面分别从摄入任务管理、数据摄入、数据查询三个方面的业务逻辑来梳理元数据在 Druid 集群所起的作用。

5.1.1 摄入任务管理
摄入数据之前需要用户提交摄入任务,Overlord根据任务的配置会相应命令MiddlerManager启动该任务的相关进程(peon进程)用于摄入数据,具体流程如下图中数据序号顺序执行。
下面分别按照上图中数字序号顺序介绍 Druid 内部关于任务管理的业务逻辑:

① Overlord进程收到任务提交请求之后,会把任务信息写入druid_tasks表,此时字段active等于1。

② Overlord分配任务给特定的MiddleManager节点,并把task信息写入Zookeeper目录(${druid.zk.paths.indexer.base}/tasks )下。

③ MiddleManager进程监听当前节点在Zookeeper目录(${ruid.zk.paths.indexer.base}/task)需要启动的task信息。

④ MiddleManager会以fork的方式启动Peon进程(task)此时Peon进程开始摄入数据,并把任务Running状态写入Zookeeper目录(${ruid.zk.paths.indexer.base}/status)。

⑤ Overlord会实时监听Zookeeper目录(${ruid.zk.paths.indexer.base}/status)获取任务运行最新状态。

⑥ 任务完成后Overlord会把task状态信息更新到数据库表druid_tasks,此时字段active=0。

数据摄入逻辑

下面分别按照上图中数字序号顺序介绍Druid内部关于数据摄入的业务逻辑:

① Peon进程在本地生产segment之后,会上传segment数据到深度存储Hdfs。

② 插入一条segment元数据信息到元数据druid_segments表中,包括segment数据hdfs地址,Interval信息,注意此时used字段为1。

③ Coordinator进程定时拉取druid_segments表中used为1的数据。

④ Coordinator进程把segment分配信息写入Zookeeper目录:${druid.zk.paths.base}/loadQueue。

⑤ HIstorical进程监听当前节点在Zookeeper目录(${druid.zk.paths.base}/loadQueue)获取需要加载的segment信息。

⑥ 从Hdfs下载segment数据,加载segment。

⑦把已加载的segment的元数据信息同步到Zookeeper目录(${druid.zk.paths.base}/segments)。

数据查询逻辑

数据查询主要涉及到Peon、Historical,Broker三个角色,Broker会根据client的查询请求中包含的dataSource和interval信息,筛选出需要查询的segment,然后Broker作为客户端从Peon获取实时数据,从Historical获取历史数据,再根据查询要求,将两部分数据进一步聚合,如下图:

Druid 元数据具体业务逻辑

有了前面对Druid集群整体认识之后,下面更为细致的探讨Druid元数据在各个组件之间发挥的作用。

如下图虚线箭头表示元数据的传输,下面按照图中数字序号介绍每个虚线箭头两端组件与元数据存储介质(MySQL、Zookeeper)之间的元数据,每条具体从组件对元数据存储介质包含读和写两方面来介绍,如下:

① 写:启动任务时写入task信息,提交实时任务时写入supervisor信息。读:broker调用overlord接口时会查询不同状态下的task信息,进程重启时恢复supervisor信息。

② 写:分配任务到MiddleManager时,写入任务信息。读:同步正在运行任务的状态信息。

③ 写:写入当前节点任务状态信息到Zookeeper,读:读取带启动或终止任务信息。

④ 写:任务启动后上报实时segment信息。

⑤ 读:coordinator定时读取字段used=1的segment列表信息。

⑥ 写:coordinator分配的segment信息,读:已分配的segment列表信息。

⑦ 写:已加载完成的segment信息,读:需要加载的segment信息。

⑧ 读:加载完成的segment信息,作为数据查询的依据。
原文链接:https://blog.csdn.net/zxf126126/article/details/131744687

Last modification:November 17, 2023
如果觉得我的文章对你有用,请随意赞赏