Durid是在2013年底开源出来的,当前最新版本0.9.2, 主要解决的是对实时数据以及较近时间的历史数据的多维查询提供高并发(多用户),低延时,高可靠性的问题。对比Druid与其他解决方案,Kylin对数据按照分区每天构建前一天的cube数据提供给用户查询,用户查询的是历史数据。而Druid不断的从ingest去拉取数据,持续构建cube,提供实时查询,主要作者下面两位, 其中一位创建了一家公司继续发展druid (Imply.io)
目录:
- druid简介
- Druid特性
- 使用场景
- RealtimeNode
- HistoricalNode
- BrokerNode
- CoordinatorNode
- 架构介绍
- 分布式集群
Druid简介
- Druid是一个为在大数据集之上做实时统计分析而设计的开源数据存储。这个系统集合了一个面向列存储的层,一个分布式、shared-nothing的架构,和一个高级的索引结构,来达成在秒级以内对十亿行级别的表进行任意的探索分析。
- 互联网技术的快速增长催生了各类大体量的数据,Hadoop很大的贡献在于帮助企业将他们那些低价值的事件流数据转化为高价值的聚合数据,这适用于各种应用
- 但Hadoop擅长的是存储和获取大规模数据,但是它并不提供任何性能上的保证它能多快获取到数据。此外,虽然Hadoop是一个高可用的系统,但是在高并发负载下性能会下降
- Hadoop是一个很好的后端、批量处理和数据仓库系统。在一个需要高并发并且保证查询性能和数据可用性的并需要提供产品级别的保证的需求,Hadoop并不能满足,因此创建了Druid,一个开源的、分布式、列存储、实时分析的数据存储。在许多方面,Druid和其他OLAP系统有很多相似之处,交互式查询系统,内存数据库(MMDB),众所周知的分布式数据存储。其中的分布式和查询模型都参考了当前的一些搜索引擎的基础架构
Durid特性
- 亚秒级查询:druid提供了快速的聚合能力以及亚秒级的OLAP查询能力,多租户的设计,是面向用户分析应用的理想方式
- 实时数据注入:druid支持流数据的注入,并提供了数据的事件驱动,保证在实时和离线环境下事件的实效性和统一性
- 可扩展的PB级存储:druid集群可以很方便的扩容到PB的数据量,每秒百万级别的数据注入。即便在加大数据规模的情况下,也能保证时其效性
- 多环境部署:druid既可以运行在商业的硬件上,也可以运行在云上。它可以从多种数据系统中注入数据,包括hadoop,spark,kafka,storm和samza等
- 丰富的社区:druid拥有丰富的社区,供大家学习
使用场景
第一:适用于清洗好的记录实时录入,但不需要更新操作
第二:支持宽表,不用join的方式(换句话说就是一张单表)
第三:可以总结出基础的统计指标,可以用一个字段表示
第四:对时区和时间维度(year、month、week、day、hour等)要求高的(甚至到分钟级别)
第五:实时性很重要
第六:对数据质量的敏感度不高
第七:用于定位效果分析和策略决策参考
RealNode
- 实时节点封装了导入和查询事件数据的功能,经由这些节点导入的事件数据可以立刻被查询。
- 实时节点只关心一小段时间内的事件数据,并定期把这段时间内收集的这批不可变事件数据导入到Druid集群里面另外一个专门负责处理不可变的批量数据的节点中去。
- 实时节点通过Zookeeper的协调和Druid集群的其他节点协调工作。实时节点通过Zookeeper来宣布他们的在线状态和他们提供的数据
- 实时节点为所有传入的事件数据维持一个内存中的索引缓存, 随着事件数据的传入,这些索引会逐步递增,并且这些索引是可以立即查询的,查询这些缓存于JVM的基于堆的缓存中的事件数据,Druid就表现得和行存储一样
- 为了避免堆溢出问题,实时节点会定期地、或者在达到设定的最大行限制的时候,把内存中的索引持久化到磁盘去
- 这个持久化进程会把保存于内存缓存中的数据转换为基于列存储的格式,所有持久化的索引都是不可变的,并且实时节点会加载这些索引到off-heap内存中使得它们可以继续被查询
- 上图实时节点缓存事件数据到内存中的索引上,然后有规律的持久化到磁盘上。在转移之前,持久化的索引会周期性地合并在一起。查询会同时命中内存中的和已持久化的索引
- 所有的实时节点都会周期性的启动后台的计划任务搜索本地的持久化索引,后台计划任务将这些持久化的索引合并到一起并生成一块不可变的数据,这些数据块包含了一段时间内的所有已经由实时节点导入的事件数据,我们称这些数据块为”Segment”。在传送阶段,实时节点将这些segment上传到一个永久持久化的备份存储中,通常是一个分布式文件系统,例如S3或者HDFS,Druid称之为”Deep Storage”。
实时节点处理流程:导入、持久化、合并和传送这些阶段都是流动的,并且在这些处理阶段中不会有任何数据的丢失,数据流图如下:
- 节点启动于13:47,并且只会接受当前小时和下一小时的事件数据。当事件数据开始导入后,节点会宣布它为13:00到14:00这个时间段的Segment数据提供服务
- 每10分钟(这个时间间隔是可配置的),节点会将内存中的缓存数据刷到磁盘中进行持久化,在当前小时快结束的时候,节点会准备接收14:00到15:00的事件数据,一旦这个情况发生了,节点会准备好为下一个小时提供服务,并且会建立一个新的内存中的索引。
- 随后,节点宣布它也为14:00到15:00这个时段提供一个segment服务。节点并不是马上就合并13:00到14:00这个时段的持久化索引,而是会等待一个可配置的窗口时间,直到所有的13:00到14:00这个时间段的一些延迟数据的到来。这个窗口期的时间将事件数据因延迟而导致的数据丢失减低到最小。
- 在窗口期结束时,节点会合并13:00到14:00这个时段的所有持久化的索引合并到一个独立的不可变的segment中,并将这个segment传送走,一旦这个segment在Druid集群中的其他地方加载了并可以查询了,实时节点会刷新它收集的13:00到14:00这个时段的数据的信息,并且宣布取消为这些数据提供服务。
HistoricalNode
- 历史节点封装了加载和处理由实时节点创建的不可变数据块(segment)的功能。在很多现实世界的工作流程中,大部分导入到Druid集群中的数据都是不可变的,因此,历史节点通常是Druid集群中的主要工作组件。
- 历史节点遵循shared-nothing的架构,因此节点间没有单点问题。节点间是相互独立的并且提供的服务也是简单的,它们只需要知道如何加载、删除和处理不可变的segment (注:shared nothing architecture是一 种分布式计算架构,这种架构中不存在集中存储的状态,整个系统中没有资源竞争,这种架构具有非常强的扩张性,在web应用中广泛使用)
- 类似于实时节点,历史节点在Zookeeper中通告它们的在线状态和为哪些数据提供服务。加载和删除segment的指令会通过Zookeeper来进行发布,指令会包含segment保存在deep storage的什么地方和怎么解压、处理这些segment的相关信息
- 在历史节点从deep storage下载某一segment之前,它会先检查本地缓存信息中看segment是否已经存在于节点中,如果segment还不存在缓存中,历史节点会从deep storage中下载segment到本地
- 一旦处理完成,这个segment就会在Zookeeper中进行通告。此时,这个segment就可以被查询了。历史节点的本地缓存也支持历史节点的快速更新和重启,在启动的时候,该节点会检查它的缓存,并为任何它找到的数据立刻进行服务的提供,如下图:
- 历史节点从deep storage下载不可变的segment。segment在可以被查询之前必须要先加载到内存中
- 历史节点可以支持读一致性,因为它们只处理不可变的数据。不可变的数据块同时支持一个简单的并行模型:历史节点可以以非阻塞的方式并发地去扫描和聚合不可变的数据块
Tiers: 历史节点可以分组到不同的tier中,哪些节点会被分到一个tier中是可配置的。Tier的目的是可以根据segment的重要程度来分配高或低的优先级来进行数据的分布。
- 可以为不同的tier配置不同的性能和容错参数。例如,可以使用一批很多个核的CPU和大容量内存的节点来组成一个“热点数据”的tier,这个“热点数据”集群可以配置来用于下载更多经常被查询的数据。
- 一个类似的”冷数据”集群可以使用一些性能要差一些的硬件来创建,“冷数据”集群可以只包含一些不是经常访问的segment
可用性: 历史节点依赖于Zookeeper来管理segment的加载和卸载。
- 如果Zookeeper变得不可用的时候,历史节点就不再可以为新的数据提供服务和卸载过期的数据,因为是通过HTTP来为查询提供服务的
- 对于那些查询它当前已经在提供服务的数据,历史节点仍然可以进行响应。这意味着Zookeeper运行故障时不会影响那些已经存在于历史节点的数据的可用性。
BrokerNode
- Broker节点扮演着历史节点和实时节点的查询路由的角色。
- Broker节点知道发布于Zookeeper中的关于哪些segment是可查询的和这些segment是保存在哪里的,Broker节点就可以将到来的查询请求路由到正确的历史节点或者是实时节点,
- Broker节点也会将历史节点和实时节点的局部结果进行合并,然后返回最终的合并后的结果给调用者
缓存:Broker节点包含一个支持LRU失效策略的缓存。这个缓存可以使用本地堆内存或者是一个外部的分布式 key/value 存储,例如Memcached
- 每次Broker节点接收到查询请求时,都会先将查询映射到一组segment中去。这一组确定的segment的结果可能已经存在于缓存中,而不需要重新计算。
- 对于那些不存在于缓存的结果,Broker节点会将查询转发到正确的历史节点和实时节点中去,一旦历史节点返回结果,Broker节点会将这些结果缓存起来以供以后使用,这个过程如下图所示
- 注意:实时数据永远不会被缓存,因此查询实时节点的数据的查询请求总是会被转发到实时节点上去。实时数据是不断变化的,因此缓存实时数据是不可靠的
- 上图:结果会为每一个segment缓存。查询会合并缓存结果与历史节点和实时节点的计算结果
- 缓存也可作为数据可用性的附加级别。在所有历史节点都出现故障的情况下,对于那些命中已经在缓存中缓存了结果的查询,仍然是可以返回查询结果的
可用性:在所有的Zookeeper都中断的情况下,数据仍然是可以查询的。如果Broker节点不可以和Zookeeper进行通信了,它会使用它最后一次得到的整个集群的视图来继续将查询请求转发到历史节点和实时节点,Broker节点假定集群的结构和Zookeeper中断前是一致的。在实践中,在我们诊断Zookeeper的故障的时候,这种可用性模型使得Druid集群可以继续提供查询服务,为我们争取了更多的时间
说明:通常在ShareNothing的架构中,如果一个节点变得不可用了,会有一个服务将下线的这个节点的数据搬迁到其他节点,但是如果这个节点下线后又立即重启,而如果服务在一下线的时候就开始搬迁数据,是会产生跨集群的数据传输,实际上是没有必要的。因为分布式文件系统对同一份数据会有多个副本,搬迁数据实际上是为了满足副本数.而下线又重启的节点上的数据不会有什么丢失的,因此短期的副本不足并不会影响整体的数据健康状况.何况跨机器搬迁数据也需要一定的时间,何不如给定一段时间如果它真的死了,才开始搬迁
CoordinatorNode
- 主要负责数据的管理和在历史节点上的分布。协调节点告诉历史节点加载新数据、卸载过期数据、复制数据、和为了负载均衡移动数据。
- Druid为了维持稳定的视图,使用一个多版本的并发控制交换协议来管理不可变的segment。如果任何不可变的segment包含的数据已经被新的segment完全淘汰了,则过期的segment会从集群中卸载掉。
- 协调节点会经历一个leader选举的过程,来决定由一个独立的节点来执行协调功能,其余的协调节点则作为冗余备份节点
- 协调节点会周期性的执行来确定集群的当前状态,它通过在运行的时候对比集群的预期状态和集群的实际状态来做决定。和所有的Druid节点一样,协调节点维持一个和Zookeeper的连接来获取当前集群的信息
- 协调节点也维持一个与MySQL数据库的连接,MySQL包含有更多的操作参数和配置信息。
- 其中一个存在于MySQL的关键信息就是历史节点可以提供服务的所有segment的一个清单,这个表可以由任何可以创建segment的服务进行更新,例如实时节点。
- MySQL数据库中还包含一个Rule表来控制集群中segment的是如何创建、销毁和复制
Rules:Rules管理历史segment是如何在集群中加载和卸载的。
- Rules指示segment应该如何分配到不同的历史节点tier中,每一个tier中应该保存多少份segment的副本。
- Rules还可能指示segment何时应该从集群中完全地卸载。Rules通常设定为一段时间,例如,一个用户可能使用Rules来将最近一个月的有价值的segment载入到一个“热点数据”的集群中,最近一年的有价值的数据载入到一个“冷数据”的集群中,而将更早时间前的数据都卸载掉。
- 协调节点从MySQL数据库中的rule表加载一组rules。Rules可能被指定到一个特定的数据源,或者配置一组默认的rules。协调节点会循环所有可用segment并会匹配第一条适用于它的rule
负载均衡:在典型的生产环境中,查询通常命中数十甚至上百个segment,由于每个历史节点的资源是有限的,segment必须被分布到整个集群中,以确保集群的负载不会过于不平衡。
- 要确定最佳的负载分布,需要对查询模式和速度有一定的了解。通常,查询会覆盖一个独立数据源中最近的一段邻近时间的一批segment。平均来说,查询更小的segment则更快
- 这些查询模式提出以更高的比率对历史segment进行复制,把大的segment以时间相近的形式分散到多个不同的历史节点中,并且使存在于不同数据源的segment集中在一起
- 为了使集群中segment达到最佳的分布和均衡,根据segment的数据源、新旧程度、和大小,开发了一个基于成本的优化程序
副本/复制(Replication):
- 协调节点可能会告诉不同的历史节点加载同一个segment的副本。每一个历史节点tier中副本的数量是完全可配置。
- 设置一个高级别容错性的集群可以设置一个比较高数量的副本数。segment的副本被视为和原始segment一样的,并使用相同的负载均衡算法
- 通过复制segment,单一历史节点故障对于整个Druid集群来说是透明的,不会有任何影响
可用性:
- 协调节点有Zookeeper和MySQL这两个额外的依赖,协调节点依赖Zookeeper来确定集群中有哪些历史节点
- 如果Zookeeper变为不可用,协调节点将不可以再进行segment的分配、均衡和卸载指令的发送。不过,这些都不会影响数据的可用性
- 对于MySQL和Zookeeper响应失效的设计原则是一致的:如果协调节点一个额外的依赖响应失败了,集群会维持现状
- Druid使用MySQL来存储操作管理信息和关于segment如何存在于集群中的segment元数据。如果MySQL下线了,这些信息就在协调节点中变得不可用,不过这不代表数据不可用
- 如果协调节点不可以和MySQL进行通信,他们会停止分配新的segment和卸载过期的segment。在MySQL故障期间Broker节点、历史节点、实时节点都是仍然可以查询的
架构介绍
- Druid 由上面介绍的角色组成的构架图:
- 查询路径:红色箭头:①客户端向Broker发起请求,Broker会将请求路由到②实时节点和③历史节点
- Druid数据流转:黑色箭头:数据源包括实时流和批量数据. ④实时流经过索引直接写到实时节点,⑤批量数据通过IndexService存储到DeepStorage,⑥再由历史节点加载. ⑦实时节点也可以将数据转存到DeepStorage
- Druid的集群依赖了ZooKeeper来维护数据拓扑. 每个组件都会与ZooKeeper交互,如下:
- 实时节点在转存Segment到DeepStorage, 会写入自己转存了什么Segment
- 协调节点管理历史节点,它负责从ZooKeeper中获取要同步/下载的Segment,并指派任务给具体的历史节点去完成
- 历史节点从ZooKeeper中领取任务,任务完成后要将ZooKeeper条目删除表示完成了任务
- Broker节点根据ZooKeeper中的Segment所在的节点, 将查询请求路由到指定的节点
- 对于一个查询路由路径,Broker只会将请求分发到实时节点和历史节点, 因此元数据存储和DeepStorage都不会参与查询中(看做是后台的进程).
MetaData Storage 与 Zookeeper
- MetaStore和ZooKeeper中保存的信息是不一样的. ZooKeeper中保存的是Segment属于哪些节点. 而MetaStore则是保存Segment的元数据信息
- 为了使得一个Segment存在于集群中,MetaStore存储的记录是关于Segment的自描述元数据: Segment的元数据,大小,所在的DeepStorage
- 元数据存储的数据会被协调节点用来知道集群中可用的数据应该有哪些(Segment可以通过实时节点转存或者批量数据直接写入).
- 除了上面介绍的节点角色外,Druid还依赖于外部的三个组件:ZooKeeper, Metadata Storage, Deep Storage,数据与查询流的交互图如下:
- ① 实时数据写入到实时节点,会创建索引结构的Segment.
- ② 实时节点的Segment经过一段时间会转存到DeepStorage
- ③ 元数据写入MySQL; 实时节点转存的Segment会在ZooKeeper中新增一条记录
- ④ 协调节点从MySQL获取元数据,比如schema信息(维度列和指标列)
- ⑤ 协调节点监测ZK中有新分配/要删除的Segment,写入ZooKeeper信息:历史节点需要加载/删除Segment
- ⑥ 历史节点监测ZK, 从ZooKeeper中得到要执行任务的Segment
- ⑦ 历史节点从DeepStorage下载Segment并加载到内存/或者将已经保存的Segment删除掉
- ⑧ 历史节点的Segment可以用于Broker的查询路由
- 由于各个节点和其他节点都是最小化解耦的, 所以下面两张图分别表示实时节点和批量数据的流程:
- 数据从Kafka导入到实时节点, 客户端直接查询实时节点的数据.
- 批量数据使用IndexService,接收Post请求的任务,直接产生Segment写到DeepStorage里.DeepStorage中的数据只会被历史节点使用.
所以这里要启动的服务有: IndexService(overlord), Historical, Coordinator(协调节点通知历史节点下载Segment),
分布式集群(测试)
- 2台 Kafka
- 2台realtime节点,并安装了zookeeper节点
- 1台broker节点
- 3台historical节点,这几台机器比较独立
- 1台coordinator节点,并安装了mysql服务,zookeeper节点
- deep storage -> hdfs
druid有三种类型的数据结构: timestamp列,维度列,指标列. 时间撮和指标在底层都是int数组或long数组. 指标值是int或long,而时间撮为long. Segment文件的内部结构可以看做是列式存储. 每一列的数据都是以不同的数据结果存储. 通过列式存储,查询时只查询需要的列可以减少延迟. 因为列式存储,要保存的是某一列的所有行. 所以数组的每一个元素表示的是每一行的这一列的值. 列式存储还有一个好处是压缩,当查询确定要查询哪些行之后, 首先解压缩(因为存储的时候会压缩),然后直接取出相关的行(定位到数组的指定位置),并运用聚合算子计算指标
目录
- 数据集
- 数据结构
- segment
数据集
- Druid中的数据表(称为数据源)是一个时间序列事件数据的集合,并分割到一组segment中,而每一个segment通常是0.5-1千万行。
- 在形式上,我们定义一个segment为跨越一段时间的数据行的集合。Segment是Druid里面的基本存储单元,复制和分布都是在segment基础之上进行的
- Druid将数据集分成三种类型: Timestamp column, Dimension columns(过滤数据), Metric columns(聚合和计算)
-
- 事件的定义: 时间戳
- Dimensions: (things to filter on) 参与事件过滤
- Metrics: (things to aggregate over) 要聚合的字段
- 以下面的事件为例,Druid会将publisher,advertiser,gender,country当做维度列,click和price当做指标列,如下:
timestamp publisher advertiser gender country click price 2011-01-01T01:01:35Z bieberfever.com google.com Male USA 0 0.65 2011-01-01T01:03:63Z bieberfever.com google.com Male USA 0 0.62 2011-01-01T01:04:51Z bieberfever.com google.com Male USA 1 0.45 2011-01-01T01:00:00Z ultratrimfast.com google.com Female UK 0 0.87 2011-01-01T02:00:00Z ultratrimfast.com google.com Female UK 0 0.99 2011-01-01T02:00:00Z ultratrimfast.com google.com Female UK 1 1.53 - ruid读取数据的入口并不会直接存储原始数据, 而是使用Roll-up这种first-level聚合操作压缩原始数据,如下:
timestamp publisher advertiser gender country impressions clicks revenue 2011-01-01T01:00:00Z ultratrimfast.com google.com Male USA 1800 25 15.70 2011-01-01T01:00:00Z bieberfever.com google.com Male USA 2912 42 29.18 2011-01-01T02:00:00Z ultratrimfast.com google.com Male UK 1953 17 17.31 2011-01-01T02:00:00Z bieberfever.com google.com Male UK 3194 170 34.01 -
用SQL表示类似于对时间撮和所有维度列进行分组,并以原始的指标列做常用的聚合操作
GROUP BY timestamp, publisher, advertiser, gender, country :: impressions = COUNT(1), clicks = SUM(click), revenue = SUM(price) -
为什么不存原始数据? 因为原始数据量可能非常大,对于广告的场景,一秒钟的点击数是以千万计数. 如果能够在读取数据的同时就进行一点聚合运算,就可以大大减少数据量的存储.这种方式的缺点是不能查询单条事件,也就是你无法查到每条事件具体的click和price值了.由于后面的查询都将以上面的查询为基础,所以Roll-up的结果一定要能满足查询的需求.通常count和sum就足够了,因此Rollup的粒度是你能查询的数据的最小时间单位. 假设每隔1秒Rollup一次,后面的查询你最小只能以一秒为单位,不能查询一毫秒的事件.默认的粒度单位是ms.
- Druid的分片是Segment文件. Druid首先总是以时间撮进行分片, 因为事件数据总是有时间撮. 假设以小时为粒度创建下面的两个Segment文件
- 在几乎所有的NoSQL中都有数据分片的概念,比如ES的分片,HBase的Region,都表示的是数据的存储介质.为什么要进行分片,因为数据大了,不能都存成一个大文件吧,所以要拆分成小文件以便于快速查询. 伴随拆分通常都有合并小文件
- 从Segment文件的名称可以看出它包含的数据一定是在文件名称对应的起始和结束时间间隔之内的
- Segment文件名称的格式:dataSource_interval_version_partitionNumber.最后一个分区号是当同一个时间撮下数据量超过阈值要分成多个分区了
-
- 分片和分区都表示将数据进行切分. 分片是将不同时间撮分布在不同的文件中, 而分区是相同时间撮放不下了,分成多个分区
- 巧合的是Kafka中也有Segment和Partition的概念.Kafka的Partition是topic物理上的分组,一个topic可以分为多个partition,它的partition物理上由多个segment组成.即Partition包含Segment,而Druid是Segment包含Partition.
数据结构
- 维度列因为要支持过滤和分组,每一个维度列的数据结构包含了三部分:
-
- 值到ID的Map映射
- 列的值列表, 存储的是上一步对应的ID
- 倒排索引
- 示例进行说明:
- 代表这一维度列的数据结构如下:
1: Dictionary that encodes column values { "Justin Bieber": 0, "Ke$ha": 1 } 2: Column data [0, 0, 1, 1] 3: Bitmaps - one for each unique value of the column value="Justin Bieber": [1,1,0,0] value="Ke$ha": [0,0,1,1]
-
注意: 在最坏情况下前面两种会随着数据量的大小而线性增长. 而BitMap的大小则等于数据量大小 * 列的个数.
结构说明
- 字典表的key都是唯一的, 所以Map的key是unique的column value, Map的value从0开始不断增加. 示例数据的page列只有两个不同的值. 所以为Bieber编号0, Ke$ha编号为1.
Key |Value ---------------|----- Justin Bieber |0 Ke$ha |1
- 列的数据: 要保存的是每一行中这一列的值, 值是ID而不是原始的值. 因为有了上面的Map字典, 所以有下面的对应关系,这样列的值列表直接取最后一列: [0,0,1,1],
rowNum page ID 1 Justin Bieber 0 2 Justin Bieber 0 3 Ke$ha 1 4 Ke$ha 1
- BitMap的key是第一步Map的key(列的原始值). value数组的每个元素表示指定列的某一行是否包含/存在/等于当前key. 注意: BitMap保存的value数组只有两个值: 1和0, 1表示这一行包含或等于BitMap的key, 0表示不存在/不包含/不等于,如下:
第一行的page列值为Justin Bieber/列值为Justin Bieber的在第一行里 ^ | value="Justin Bieber": [1,1,0,0] value="Ke$ha": [0,0,1,1] ^ | 第一行的page列值不是Ke$ha
-
这种存储方式, 如果unique重复的列很少,比如page列的每一个值都是不同的. BitMap就会是一个稀疏矩阵.
A: [1,0,0,0,0,0,0,0,0,0,0] B: [0,1,0,0,0,0,0,0,0,0,0] C: [0,0,1,0,0,0,0,0,0,0,0] D: [0,0,0,1,0,0,0,0,0,0,0] E: [0,0,0,0,1,0,0,0,0,0,0]
-
unique的重复数量很少也叫做high cardinality,表示基数很高,不同列的数量很多,列值相同的记录数很少.
-
稀疏矩阵对于BitMap而言却是有优点的,因为越是稀疏,它可以被压缩的比例越大,最后存储的空间越少(相对原始数据).
- 上面只是针对page列的BitMap, 对于其他的维度列, 都有自己的BitMap! 即每一个维度列都有一个BitMap.
segment
- 数据进入到Druid首先会进行索引, 这给予了Druid一个机会可以进行分析数据, 添加索引结构, 压缩, 为查询优化调整存储结构
-
- 转换为列式结构
- 使用BitMap索引
- 使用不同的压缩算法
- 索引的结果是生成Segment文件,Segment中除了保存不同的维度和指标,还保存了这些列的索引信息
- Druid将索引数据保存到Segment文件中,Segment文件根据时间进行分片. 最基本的设置中, 每一个时间间隔都会创建一个Segment文件
- 这个时间间隔的长度配置在granularitySpec的segmentGranularity参数.为了Druid工作良好,通常Segment文件大小为300-700M
- 前面Roll-up时也有一个时间粒度:queryGranularity指的是在读取时就进行聚合.segmentGranularity则是用于分片进来之后的数据.
谈到大数据,大家首先想到的肯定是Hadoop,近年来互联网技术的快速增长催生了各类大体量数据的爆发,Hadoop最大的贡献在于帮助企业将那些低价值的事件流数据转化为高价值的聚合数据,为企业的经营决策提供数据支撑。但Hadoop擅长的是存储和获取大规模数据,但是它并不提供任何性能上的保证。从这个角度来讲,我们可以把Hadoop看作是一个很好的后端、批量处理和数据仓库系统。在一个需要高并发并且保证查询性能和数据可用性的场景下,Hadoop并不能满足需求, 因此而引出了我们今天要介绍的主角: Druid集群。
Druid是一套基于大数据集之上做实时统计分析而设计的开源系统,主要特点包含分布式、列存储、内存数据库、实时分析。当前版本 0.10.0 (官网:http://druid.io), 主要作者:杨仿金同学已回到中国,另一老外成立自己的公司,在Druid集群之上提供商业化服务,包含PloySQL、Pivot等增值化组件。
在五横一竖常规的大数据架构中,从数据采集、数据处理、数据分析、数据访问、数据应用每一层均提供N多可供选择的组件包,可谓是“百花齐放”、能让您选花了眼。Druid 主要优势在于实时流式数据的处理与高效OLAP分析,与其它开源组件优劣对比见上图。
Druid集群主要包含了以下几类角色:
Ø RealNode:封装了导入和查询事件数据的功能,经由这些节点导入的事件数据可以立刻被查询
Ø HistoricalNode:封装了加载和处理由实时节点创建的不可变数据块(segment)的功能。在很多现实世界的工作流程中,大部分导入到Druid集群中的数据都是不可变的,因此,历史节点通常是Druid集群中的主要工作组件。
Ø BrokerNode:扮演着历史节点和实时节点的查询路由的角色。
Ø CoordinatorNode:主要负责数据的管理和在历史节点上的分布。协调节点告诉历史节点加载新数据、卸载过期数据、复制数据、和为了负载均衡移动数据。
Ø 除了上面介绍的节点角色外,Druid还依赖于外部的三个组件:ZooKeeper, Metadata Storage, Deep Storage,数据与查询流的交互如上图,简述如下:
ü ① 实时数据写入到实时节点,会创建索引结构的Segment
ü ② 实时节点的Segment经过一段时间会转存到DeepStorage
ü ③ 元数据写入MySQL; 实时节点转存的Segment会在ZooKeeper中新增一条记录
ü ④ 协调节点从MySQL获取元数据,比如schema信息(维度列和指标列)
ü ⑤ 协调节点监测ZK中有新分配/要删除的Segment,写入ZooKeeper信息:历史节点需要加载/删除Segment
ü ⑥ 历史节点监测ZK, 从ZooKeeper中得到要执行任务的Segment
ü ⑦ 历史节点从DeepStorage下载Segment并加载到内存/或者将已经保存的Segment删除掉
ü ⑧ 历史节点的Segment可以用于Broker的查询路由
实时节点在处理数据导入、持久化、合并和传送这些阶段都是流动的,并且在这些处理阶段中不会有任何数据的丢失,数据流图如上:
Ø 节点启动于13:47,并且只会接受当前小时和下一小时的事件数据。当事件数据开始导入后,节点会宣布它为13:00到14:00这个时间段的Segment数据提供服务
Ø 每10分钟(这个时间间隔是可配置的),节点会将内存中的缓存数据刷到磁盘中进行持久化,在当前小时快结束的时候,节点会准备接收14:00到15:00的事件数据,一旦这个情况发生了,节点会准备好为下一个小时提供服务,并且会建立一个新的内存中的索引。
Ø 随后,节点宣布它也为14:00到15:00这个时段提供一个segment服务。节点并不是马上就合并13:00到14:00这个时段的持久化索引,而是会等待一个可配置的窗口时间,直到所有的13:00到14:00这个时间段的一些延迟数据的到来。这个窗口期的时间将事件数据因延迟而导致的数据丢失减低到最小。
Ø 在窗口期结束时,节点会合并13:00到14:00这个时段的所有持久化的索引合并到一个独立的不可变的segment中,并将这个segment传送走,一旦这个segment在Druid集群中的其他地方加载了并可以查询了,实时节点会刷新它收集的13:00到14:00这个时段的数据的信息,并且宣布取消为这些数据提供服务。
上图为我们正式集群DataSource 存储结构示意图,所有的NoSQL中都有数据分片的概念,比如ES的分片,HBase的Region,都表示的是数据的存储介质.为什么要进行分片,因为数据大了,不能都存成一个大文件吧?所以要拆分成小文件以便于快速查询. 伴随拆分通常都有合并小文件
通过实时采集充电日志,以按区域、场站等维度实时分析充电相关的指标分析为场景进行案例分享
分享案例统计维度如上图
数据流处理示意图
扩展Grafana可视化组件的数据源插件、UI插件
按区域实时监控每分钟充电指标,通过选择左上角的导航面板中不同的区域,触发Dashboard内其它面板数据刷新
基于场站实时监控,支持全文检索或选择高级过滤的方式快速定位电站,选中第一列的小眼睛,行背景变绿,触发当前电站下面板内其它部件数据刷新。
同样原理,更细粒度下到终端的实时监控。
历史年度数据趋势分析
大数据需要伴随着业务的驱动来发挥其应有的价值,需要大家的共同参与来一步步夯实其底层的基础平台,期待着下一期更精彩的技术分享。
转载于:https://my.oschina.net/hblt147/blog/1571543