druid.io剖析
简介
druid做为如今最有潜力的海量数据实时分析系统,在优酷广告团队中扮演者很是重要的角色node
总体架构

如今已经用tranquility+indexing service替换realtimemysql
实时数据经由tranquility被推送到Indexing Service,而后生成索引(Segment),同时提供来自用户的 查询请求。当索引所在的时间段过去之后,Indexing Service会将索引推送到deep storage。 索引信息会被注册到metadata中。协调节点定时同步metadata,感知新生成的Segment, 并经过Zookeeper去通知历史节点加载索引。git
除了实时加载数据,Druid也支持批量导入数据。导入的数据生成索引segment,写入deep storage, 经与上述一样的步骤,经过修改metadata信息将segment加载到历史节点。github
Lambda架构思想
Lamda架构用来同时处理离线和实时数据算法

druid借助lambda思想,很好的解决了实时处理逻辑会丢弃时间窗口之外的数据的问题。sql
- 实时:经过tranquility拉取数据并导入druid
- 离线:经过druid提供的Hadoop Indexer(其实是mr任务)获取hdfs数据,生成segment并导入hdfs,并将索引元信息导入mysql。druid定时轮询mysql并获取最新索引数据,最后经过指定负载均衡算法分配给工做节点
依赖
kafka(16c32g480gx8)
TT日志-->storm etl-->kafkajson
kafka做为storm和druid之间的缓冲缓存
tranquility(4c8g120gx9)
/home/admin/druid-tranquility/default架构
config目录存储了不少json文件,定义了数据源的schema,也就是druid表结构,全部schema中配置的segmentGranularity都是hour级别, queryGranularity有hour也有minute级别并发
重要参数
segmentGranularity不等于queryGranularity
- segmentGranularity:索引粒度,也就是一个segment文件包含的数据时间范围
- queryGranularity:查询粒度,也就是最小聚合粒度,表明数据存储的时候,在维度相同的状况下,同一查询粒度范围内的数据会自动被聚合,致使查询的时候只能查到该粒度级别的数据
- intermediatePersistPeriod:定时持久化增量索引的周期,目前大可能是5min
- windowPeriod:时间窗口,表示若是数据时间比当前时间老或者比当前时间新,超过该窗口范围以外的所有被丢弃,目前大可能是10min,也有5min
推荐配置:intermediatePersistPeriod ≤ windowPeriod < segmentGranularity,queryGranularity<= segmentGranularity
tranquility工做流程:

简而言之,tranquility会作两件事:
- 创建索引任务并发给overlord:tranquility发送的task会被overlord接受,最后会占用middle-manager的一个空闲slot。为防止太多task,tranquility会为同一个segmentGranularity范围以内的task分配同一个id,这个全部发送过去的task都会被合并。还有两个配置项影响task数量,tranquility能够在schema中为每一个数据源配置partitions和replicants,一个小时之内请求的task数量= partitions * replicants。目前middle-manager的全部slot数量均可以在overlord UI查看,能够根据剩余slot数量来修改配置中的partitions和replicants参数。创建的task主要目的是明确每一个tranquility该往哪一个peon发送实时数据,即实时数据在众多peon中负载均衡的策略(稍后讨论handoff阶段)
- 将实时数据发送给peon进程:peon会经过EventReceiverFirehose服务暴露一个http接口,tranquility经过zookeeper获取task的分配信息,明确实时数据该往哪一个peon发,并将peon暴露的接口发起post请求,提交实时数据
内部组件
indexing service
indexing service分为三个组件(工做进程),用相似storm的nimbus->supervisor->worker的方式工做
- overlord(4c8g120gx2):接收tranquility请求的实时索引task,选择slot空闲最多的middle-manager,经过zk将task分配给middle-manager,填满为止。目前overlord两台机器,master-slave结构
- middle-manager(4c8g120gx51):经过zk获取task,启动本地进程peon执行task
- peon:获取实时数据,执行task,完成索引创建。peon自己还负责索引查询服务
index service接收tranquility请求并处理的整个流程:

peon完成了索引build,merge,handoff的整个生命周期

每一个middle-manger有N个slot,对应N个peon,每次分配一个索引task就会建立一个peon进程,这个小时之内peon会占据这个slot,等完成handoff以后才释放
这个小时之内,peon会不断生成增量索引,定时持久化索引,合并索引生成segment,最后handoff segment
handoff流程:

historical(16c32g480gx10)
historical提供索引加载和查询服务

历史节点在从下载segment前,会从本地缓存检查是否存在,若是不存在才从hdfs下载。下载完成以后,会根据zk获取到的压缩信息进行解压处理并加载到内存,这时就能提供查询服务。
能够经过配置给历史节点划分不一样的层,而后在coordinator配置规则来加载指定数据源到某个层。这样能够实现冷热数据划分处理,热数据查询多存量小,采用更好的cpu和内存机型配置,冷数据查询少存量大,采用更大的硬盘机型配置
broker(16c32g480gx2)
broker负责查询索引,目前是master-master结构
broker是查询节点,负责接受查询请求,解析查询对象中的时间范围,根据时间范围将实时索引请求(当前小时)路由到peon节点,将历史索引请求(1小时以前)路由到historical节点。接收peon和historical查询返回的数据,在作一次合并,最后返回结果
为了提升查询效率,broker会将查询结果缓存(LRU),目前提供了两种方式:
- heap memory(目前使用)
- kv存储,如memcached
只会缓存历史节点返回的数据,由于peon返回的实时数据常常改变,没有缓存的价值
coordinator(4c8g120gx2)
coordinator会协调历史节点中segment的分配
- rules:每分钟从mysql拉取druid_rules和druid_segments,rules用来告知historical将如何load和drop索引文件,coordinator会读取这些rules,而后修改zk,通知historical加载删除指定的segment,这些均可以在coordinator的UI配置
- load balance:根据zk中每一个historical node负责的segment量,作负载均衡
- replication:在coordinator的UI中配置rules时,能够同时配置加载segment的备份数量,这些备份数量会以load balance的形式,分配到多个historical上面。这个备份数量与hdfs的segment备份数量不同,hdfs那个保证深度存储的数据不会丢失,historical上面备份是为了保证当某个historical挂掉的时候,其余存储了备份segment的节点能接着提供查询服务
外部依赖
zookeeper
druid依赖zk实现集群之间的交互
druid采用shard-nothing架构,每一个节点之间不直接和其余打交道,而是采用zk来沟通。这样保证了druid自己的HA特性
peon和historical发布索引
- /druid/announcements:声明全部peon和historical的host
- /druid/segments:记录全部peon和historical的host,以及他们负责的索引
提供indexing service相关数据(overlord页面数据来源)
- /druid/indexer
- leaderLatchPath:overlord选主
- tasks:运行的peon任务
- status:peon任务状态
- announcements:声明middle-manager的capacity
coordinator用来通知historical加载卸载索引
- /druid/loadQueue/_historical_host/_segement_id:记录历史节点所负责的segment
coordinator选主
- coordinator:记录coordinator信息
集群通讯
附属功能
- /druid/listeners:存储lookup数据
deep storage —— hdfs
存储索引文件
metadata storage —— mysql
存储元数据
- druid_segments:索引元数据,数据源、是否可用、大小、维度、指标
- druid_rules:通知historical该如何加载、卸载索引的规则,能够在coordinator配置
- druid_config:存放运行时配置信息
- druid_audit:记录配置、规则的变化
- druid_task(相关的几张表):overlord用来存放索引task数据,防止overlord挂掉致使task丢失
索引文件
segment就是压缩后的索引文件,命名方式为datasource_intervalStart_intervalEnd_version_partitionNum。如dsp_report_2011-01-01T01:00:00Z_2011-01-01T02:00:00Z_v1_0,表明dsp_report数据源,从2011-01-01那天1点到2点的数据,版本号为v1,分区数为0
深刻剖析segment存储结构
- version.bin:4字节,记录segment version
- XXXXX.smoosh:该文件存放多个逻辑意义上的子文件,经过记录offset来管理这些子文件。有的子文件存放了column信息,有的存放了索引元信息。column信息也就是真实存储的数据
- meta.smoosh:上面这些子文件名称以及他们出现的offset都记录在meta.smoosh中
XXXXX.smoosh中存放的column是最重要的,能够分为Timestamp, Dimensions, Metrics三部分。
timestamp |
domain |
advertiser |
device |
city |
click |
cost |
2015-11-25T10:00:00Z |
youku.com |
BMW |
Android |
Peking |
9 |
0.9 |
2015-11-25T10:00:00Z |
youku.com |
BMW |
Iphone |
HongKong |
3 |
0.3 |
2015-11-25T10:00:00Z |
tudou.com |
PANDORA |
Iphone |
HongKong |
2 |
0.2 |
2015-11-25T10:00:00Z |
tudou.com |
PANDORA |
Iphone |
Peking |
1 |
0.1 |
- Timestamp:用时间戳来表示时间,能够用一系列时间戳表示该segment全部Timestamp列信息,采用LZ4算法压缩
- Metrics:也是数字,存放方法同上,压缩算法同上
- Dimensions:因为Dimensions大可能是字符串,采用上面的存放方式没法很好压缩。目前Dimensions拆分红多个结构进行存储。
Dimensions结构:
- 将字符串映射为整数id的字典
- 记录该dimension每一行的值,值用上述字典编码
- 为每一个不一样dimension的值,定义一个bitmap,存储该值出现的行号,采用roaring压缩算法
上述结构中,1能够有效减小索引文件的大小,2的基础上作排序能够很方便的作groupby合并处理,3是快速完成where条件查询的利器。
内存管理
druid使用了三种不一样类型的内存:
- 堆内存:broker用来缓存查询结果、简单计算
- 直接内存:通常用来存储聚合操做中所产生的临时数据
- MMap:历史节点用来加载segment,快速,减小一次系统复制操做。memory_for_segments = total_memory - heap - direct_memory - jvm_overhead,segment可用的内存越小,mmap操做就会致使更多的内存换页操做
参考资料