Druid和Caravel在去哪儿大住宿的实践
长期以来,Qunar大住宿的数据仓库主要使用Hive作为主要的查询引擎,部分需求配合Postgres和Mysql数据库,用作报表的汇总和展示。
随着业务的发展,数据量和常用的维度都在快速的增长,以订单为例,目前常用的维度超过50个,采用关系型数据库存储,很难保证查询汇总的性能。急需一个适用于分析汇总查询的OLAP引擎。
Druid是一个开源的,分布式的,列存储的,适用于实时数据分析的存储系统,能够快速聚合、灵活过滤、毫秒级查询、和低延迟数据导入。
目前最新版本是0.9.1.1,在ApacheLicense2.0协议下开源。其设计的适用场景,就是对PB级数据的快速聚合查询。
Druid架构
为了做到毫秒级的查询响应,Druid的核心思想是对数据的索引和预汇总。
Druid集群采用了sharenothing的架构。一个Druid集群由5组不同角色的节点组成:
Realtime
Indexer
Broker
Historical
Coordinator
同时依赖3个外部服务:
Zookeeper
MetadataStore(一个关系型数据库)
DeepStorage(HDFS,本地文件系统,S3)
每组节点在集群中有不同的作用:
Coordinator节点负责统一分配管理Historical节点保存的segment
Realtime节点接收实时数据,并进行实时汇总
Indexer节点负责处理批量索引请求,管理批量索引任务
Broker节点负责处理客户端提交的查询
Historical节点是Druid集群的主力,负责缓存和查询索引好的数据,生成部分聚合结果,并将结果交给Broker节点做进一步聚合。
数据在不同节点间的流向:
Realtime节点接受实时数据,对数据进行实时聚合,并定期将已经完成的Segment推向Historical节点
Indexer节点接受批量导入请求,管理批量索引任务,并将生成的segment交给Historical节点
客户端的查询提交到Broker节点,然后根据Coordinator的信息,将查询拆分到不同的Historical节点和Realtime节点,再将得到的结果聚合返回给客户端。
Druid如何索引数据
Druid不保存明细数据,由Druid索引好的数据本身已经是部分聚合后的结构,可以进一步合并后得到最终的结果。
Druid将数据拆分为了三段:时间戳,维度和指标。
在上面的数据中,order_time是时间戳,用于将数据切分成不同的segment;hotel_seq,city是维度,Druid会对每一列存储下面三组不同的信息:
1.枚举字典
定义:对维度中的所有可能值进行编码,以最小化存储空间使用
以city一列为例,Druid会保存类似下面的字典信息:
{“beijing”:0,“shanghai”:1}
2.列存储
定义:经过编码的列存储。
以city一列为例,最终会保存[0,0,1,0]这样的信息
3.倒排bitmap索引
定义:每个枚举值,出现在数据中的哪些行中。用于快速检索。本质是一个巨大的稀疏矩阵。实际存储中一般的文本压缩就有非常好的压缩效果。
以city一列为例,会存储
{
“beijing”:[1,1,0,1],
“shanghai”:[0,0,1,0]
}
这样的索引。
这种存储结构是的可以保证快速的检索枚不同举值对应的部分聚合结果,并且每种索引都有非常高效的压缩存储算法。
业务使用场景
目前,我们主要使用Druid解决对订单实时的多维度分析的应用场景。不同于其他的数据,例如流量等,订单的业务场景很复杂,很多维度值和指标项随着订单处理流程,或者用户的主动变更,会发生变化。为了应对这种场景,目前我们采用了实时和离线结合的方式处理订单数据:
实时部分会接收业务线订单创建的相应的消息,抽取订单相应的维度,并且计算部分关键指标信息,通过kafka实时推到Druid集群
离线部分会根据每天的离线数据仓库每日计算的准确结果,重新索引,替换掉三个月内的数据。
再实际的使用中,我们发现这个方案有下面几个问题:
Druid没有保存明细数据,部分运营场景需要筛选订单明细,Druid无法支持;
Druid查询使用自己的DSL,类似ES。手工书写查询比较困难;
Caravel可以比较好的解决这两个问题。
Caravel
Caravel是由Airbnb开发的数据可视化平台,提供Druid和PythonDBAPI支持的关系型数据库,包括Mysql、Postgres、Presto、Kylin等。提供简单直观的配置界面,配置一个漂亮的Dashboard非常简单。
针对Druid使用中遇到的问题都提供了比较完善的解决方案:
查询不好写->Caravel可以通过拖拽和选择配置报表
明细数据无法导出->Caravel同时支持Presto和Druid,通过改造Caravel使其支持明细数据走Presto查询,汇总走Druid
踩过的坑
时区问题Druid切片依赖时间戳,默认时间戳为UTC,可能会导致切片不正确。需要通过指定JVM默认时区的方法指明时区。Caravel对关系数据库的默认时区支持同样有问题,计划解决后给官方提PR。
Druid官方文档中有对protobuf的支持,实际使用中不支持include,不支持嵌套结构。基本没有达到可用状态。
对非JSON的数据,尽量使用标准CSV格式。否则各种转转义问题会很麻烦。
不要把订单号之类的唯一键作为维度,没有意义。
适当选择分片大小。使用中发现通过MR索引,实际的索引会在reduce步进行,每个reducer处理一个分片。所以适当增加reduce内存。数据倾斜时可以考虑用多次MR索引。
结论
Druid有下面的优缺点:
优点:
高可用,水平扩展。靠堆硬件就能解决性能问题;
高效和压缩和索引算法,能做到PB级数据量下的过滤汇总10ms以下的相应时间;
实时导入和批量索引结合;
schema灵活。同一个datasource下的不同segment可以有不同的指标与维度;
缺点:
数据经过初步聚合索引,无法看到明细数据;
无法更新已导入的数据。更新需要重新索引整个segment;
维度和指标需要预先定义好,添加新指标需要重新索引,才能对历史数据生效;
经常有人将Druid和其他的OLAP引擎比较,和常见的可以用作OLAP的引擎比较:
对比ES:
ES偏向于文本检索。虽然ES目前支持聚合查询,但是聚合操作占用的资源非常高。
Druid更适合聚合分析。
对比Spark/Hive/Impala/Presto等查询引擎:
这些查询引擎重点是更灵活的查询方式和完整的SQL支持。
Druid专注于对热点数据的探索分析。
对比Kylin
在Kylin的开发者邮件组里,对这两个引擎做过非常深入的比较,主要有下面几点:
Kylin更适合复杂的OLAP场景,Druid适合实时分析场景;
Druid能实时从kafka拉取数据,Kylin的近实时分析功能还处在开发阶段;
Druid使用倒排Bitmap索引,Kylin对历史数据使用Olapcube存储;
Kylin支持SQL,Druid不支持SQL查询;
随着业务的发展,数据量和常用的维度都在快速的增长,以订单为例,目前常用的维度超过50个,采用关系型数据库存储,很难保证查询汇总的性能。急需一个适用于分析汇总查询的OLAP引擎。
Druid是一个开源的,分布式的,列存储的,适用于实时数据分析的存储系统,能够快速聚合、灵活过滤、毫秒级查询、和低延迟数据导入。
目前最新版本是0.9.1.1,在ApacheLicense2.0协议下开源。其设计的适用场景,就是对PB级数据的快速聚合查询。
Druid架构
为了做到毫秒级的查询响应,Druid的核心思想是对数据的索引和预汇总。
Druid集群采用了sharenothing的架构。一个Druid集群由5组不同角色的节点组成:
Realtime
Indexer
Broker
Historical
Coordinator
同时依赖3个外部服务:
Zookeeper
MetadataStore(一个关系型数据库)
DeepStorage(HDFS,本地文件系统,S3)
每组节点在集群中有不同的作用:
Coordinator节点负责统一分配管理Historical节点保存的segment
Realtime节点接收实时数据,并进行实时汇总
Indexer节点负责处理批量索引请求,管理批量索引任务
Broker节点负责处理客户端提交的查询
Historical节点是Druid集群的主力,负责缓存和查询索引好的数据,生成部分聚合结果,并将结果交给Broker节点做进一步聚合。
数据在不同节点间的流向:
Realtime节点接受实时数据,对数据进行实时聚合,并定期将已经完成的Segment推向Historical节点
Indexer节点接受批量导入请求,管理批量索引任务,并将生成的segment交给Historical节点
客户端的查询提交到Broker节点,然后根据Coordinator的信息,将查询拆分到不同的Historical节点和Realtime节点,再将得到的结果聚合返回给客户端。
Druid如何索引数据
Druid不保存明细数据,由Druid索引好的数据本身已经是部分聚合后的结构,可以进一步合并后得到最终的结果。
Druid将数据拆分为了三段:时间戳,维度和指标。
在上面的数据中,order_time是时间戳,用于将数据切分成不同的segment;hotel_seq,city是维度,Druid会对每一列存储下面三组不同的信息:
1.枚举字典
定义:对维度中的所有可能值进行编码,以最小化存储空间使用
以city一列为例,Druid会保存类似下面的字典信息:
{“beijing”:0,“shanghai”:1}
2.列存储
定义:经过编码的列存储。
以city一列为例,最终会保存[0,0,1,0]这样的信息
3.倒排bitmap索引
定义:每个枚举值,出现在数据中的哪些行中。用于快速检索。本质是一个巨大的稀疏矩阵。实际存储中一般的文本压缩就有非常好的压缩效果。
以city一列为例,会存储
{
“beijing”:[1,1,0,1],
“shanghai”:[0,0,1,0]
}
这样的索引。
这种存储结构是的可以保证快速的检索枚不同举值对应的部分聚合结果,并且每种索引都有非常高效的压缩存储算法。
业务使用场景
目前,我们主要使用Druid解决对订单实时的多维度分析的应用场景。不同于其他的数据,例如流量等,订单的业务场景很复杂,很多维度值和指标项随着订单处理流程,或者用户的主动变更,会发生变化。为了应对这种场景,目前我们采用了实时和离线结合的方式处理订单数据:
实时部分会接收业务线订单创建的相应的消息,抽取订单相应的维度,并且计算部分关键指标信息,通过kafka实时推到Druid集群
离线部分会根据每天的离线数据仓库每日计算的准确结果,重新索引,替换掉三个月内的数据。
再实际的使用中,我们发现这个方案有下面几个问题:
Druid没有保存明细数据,部分运营场景需要筛选订单明细,Druid无法支持;
Druid查询使用自己的DSL,类似ES。手工书写查询比较困难;
Caravel可以比较好的解决这两个问题。
Caravel
Caravel是由Airbnb开发的数据可视化平台,提供Druid和PythonDBAPI支持的关系型数据库,包括Mysql、Postgres、Presto、Kylin等。提供简单直观的配置界面,配置一个漂亮的Dashboard非常简单。
针对Druid使用中遇到的问题都提供了比较完善的解决方案:
查询不好写->Caravel可以通过拖拽和选择配置报表
明细数据无法导出->Caravel同时支持Presto和Druid,通过改造Caravel使其支持明细数据走Presto查询,汇总走Druid
踩过的坑
时区问题Druid切片依赖时间戳,默认时间戳为UTC,可能会导致切片不正确。需要通过指定JVM默认时区的方法指明时区。Caravel对关系数据库的默认时区支持同样有问题,计划解决后给官方提PR。
Druid官方文档中有对protobuf的支持,实际使用中不支持include,不支持嵌套结构。基本没有达到可用状态。
对非JSON的数据,尽量使用标准CSV格式。否则各种转转义问题会很麻烦。
不要把订单号之类的唯一键作为维度,没有意义。
适当选择分片大小。使用中发现通过MR索引,实际的索引会在reduce步进行,每个reducer处理一个分片。所以适当增加reduce内存。数据倾斜时可以考虑用多次MR索引。
结论
Druid有下面的优缺点:
优点:
高可用,水平扩展。靠堆硬件就能解决性能问题;
高效和压缩和索引算法,能做到PB级数据量下的过滤汇总10ms以下的相应时间;
实时导入和批量索引结合;
schema灵活。同一个datasource下的不同segment可以有不同的指标与维度;
缺点:
数据经过初步聚合索引,无法看到明细数据;
无法更新已导入的数据。更新需要重新索引整个segment;
维度和指标需要预先定义好,添加新指标需要重新索引,才能对历史数据生效;
经常有人将Druid和其他的OLAP引擎比较,和常见的可以用作OLAP的引擎比较:
对比ES:
ES偏向于文本检索。虽然ES目前支持聚合查询,但是聚合操作占用的资源非常高。
Druid更适合聚合分析。
对比Spark/Hive/Impala/Presto等查询引擎:
这些查询引擎重点是更灵活的查询方式和完整的SQL支持。
Druid专注于对热点数据的探索分析。
对比Kylin
在Kylin的开发者邮件组里,对这两个引擎做过非常深入的比较,主要有下面几点:
Kylin更适合复杂的OLAP场景,Druid适合实时分析场景;
Druid能实时从kafka拉取数据,Kylin的近实时分析功能还处在开发阶段;
Druid使用倒排Bitmap索引,Kylin对历史数据使用Olapcube存储;
Kylin支持SQL,Druid不支持SQL查询;