Druid 评测

最近组里申请了三台机器对Druid进行测试,这里记录一下过程,并对Druid的表现做一下评测

环境

一共三台机器

  • 磁盘:SATA盘 4T
  • CPU:24核
  • 内存:128G
  • 系统:Red Hat Enterprise Linux 7.3
  • Zookeeper、HDFS、MySQL都用的已有的环境

三台机器名称用 t214、t218、t219代替.其中:

  • t214部署coordinatoroverlordbrokermiddle manager
  • t214、t218、t219均部署historical

安装

安装的时候先尝试了imply-2.2.0,但由于java兼容性问题没有成功,最终部署了druid-0.9.2。下面分别记录,对此部分不感兴趣可以直接跳过

imply-2.2.0 (失败)

由于Druid目前版本还在0.x.x,还处于成长阶段,因此原则上以部署最新版为宜。因此首先尝试部署了imply-2.2.0(*注:刚尝试完没两天imply就发布了2.2.2版本)

部署流程

因为最终没有部署imply-2.2.0,所以这里就不把部署流程记录的那么详细了。只说遇到的坑吧。

imply-2.2.0集成了druid-0.10.0,但是druid-0.10.0是依赖于Java8的。Java8的本地依赖好解决,但是在做hadoop-indexing的时候,需要将Druid的jar包发布到HDFS集群中作为任务jar运行,这个时候就会出现HADOOP集群的JAVA是1.7,但运行的任务jar是1.8的情况

尝试了重新编译druid-0.10.0的部分组件至java7版本,但是失败了:

  • 首先修改了Druid源码,将java8特性的部分修改为java7风格,这部分好解决
  • 但是druid依赖的jetty版本也是用java8编译的,尝试对jetty进行降级,但是druid也使用了jetty的一些新版特性,降级后出现了method not found的问题。再改下去牵扯范围有点广,因此放弃

druid-0.9.2

目前Druid次新的版本0.9.2是依赖于java7的,因此最终部署了0.9.2版本。druid-0.10.0的部分新特性见附录

部署流程

此部分建议和后面遇到的坑部分一起看

  1. 下载并解压安装包
  2. 修改zookeeper的地址
    • conf/druid/_common/common.runtime.properties
      • druid.zk.service.host
  3. 修改MetaStorage为MySQL
    • conf/druid/_common/common.runtime.properties
      • 注释掉#For Derby下面的配置
      • 取消注释#For Mysql 下面的几个配置, 并填写如正确的值
      • druid.extensions.loadList增加一项"mysql-metadata-storage"
  4. 修改DeepStorage为HDFS
    • 将Hadoop的一堆xml配置文件复制到conf/druid/_common
    • 修改conf/druid/_common/common.runtime.properties
      • druid.extensions.loadList增加一项"druid-hdfs-storage"
    • 注释掉# For local disk下面的配置
    • 取消注释# For HDFS:下面的配置,并填写正确的值
  5. (可选) 修改时区。需要修改的包括/conf/druid下面所有的模块,以及conf/druid/middleManager/runtime.properties-Duser.timezone=PRC。修改这些其实并没有什么实际的有意义,还可能会导致一些奇怪的bug。修改后查询返回的时区依然是UTC。只是segment路径、名称等东西,druid会用本地时区,类似于2017-05-20T00:00:00+0800这种形式,做idnexing的时候,也依然需要按照这种格式来指定时间段
  6. 打包、分发到各机器,可以用scprsync等工具
  7. 启动各组件
    • 执行bin/xxx.sh start来启动,bin/xxx.sh stop来停止
遇到的坑
  • 用MySQL做MetaStorage的话,需要注意:
    • 需要在 conf/druid/_common/common.runtime.properties 中配置 druid.extensions.loadList ,增加 "mysql-metadata-storage"
    • MySQL所用的database的Character需要配置为UTF-8
  • 启动broker失败
    • broker提示direct memory不足
      • 修改 conf/druid/broker/jvm.config ,增大 -XX:MaxDirectMemorySize
  • 启动historical失败
    • historical提示direct memory不足
      • 修改 conf/druid/historical/jvm.config ,增大 XX:MaxDirectMemorySize
  • hadoop-indexing的时候mapreduce任务运行失败
    • 原因1:集群hadoop版本为2.4.1,与druid提供的版本2.3.0部分jar包不兼容
      • 替换hdfs-extension:从 github 上clone了druid的源码,迁出 druid-0.9.2 ,修改hadoop的依赖版本为2.4.1,重新编译并替换掉 extension/druid-hdfs-storage 文件夹
      • 修改 hadoop-dependency 的依赖
        • 新建了 hadoop-dependency/client/2.4.1 的空文件夹,因为hadoop集群默认是有hadoo的jar包的,不用重复添加
        • 修改 conf/druid/middleManager/runtime.properties中的druid.indexer.task.defaultHadoopCoordinates属性为["org.apache.hadoop:hadoop-client:2.4.1"]
    • 原因2: 数据文件采用lzo压缩,但hadoop默认没有提供lzo的依赖
      • 将lzo包复制到hadoop-dependency/client/2.4.1
    • 原因3: druid的依赖包与hadoop集群的jar包版本冲突 (*注:这里有个大坑)
      • 官网介绍了两种on other hadoop version的方法:
        1. 设置mapreduce.job.classloader = true
        2. 设置mapreduce.job.user.classpath.first = true
      • 首先尝试了方法1,也是官网推荐的方法。hadoop-indexing需要执行两个连续的MR任务,修改后第一个MR任务成功,第二个任务总是报 java.lang.ClassNotFoundException: Class io.druid.indexer.IndexGeneratorJob$IndexGeneratorOutputFormat not found 错误。采用包括修改源码加log等各种方法调试无果,发现依赖包确实被加载到classpath中了,但就是无法识别。真的是巨坑无比
      • 最终采用了方法2,很容易就过了
    • 原因4: MR任务内存溢出
      • 尝试任务配置项的jobProperties中,调整如下配置
        • mapreduce.map.java.opts
        • mapreduce.reduce.java.opts
        • mapreduce.reduce.memory.mb
      • 尝试调整tuningConfigpartitionsSpectargetPartitionSize
      • 尝试调整tunningConfigmaxRowsInMemory配置项
    • 原因5: 文件数据错误
      • tuningConfigignoreInvalidRows设置为true
      • 控制好ETL流程,扔掉错误行
    • 原因6: Hadoop的一些其他配置问题
      • 在任务配置中增加了配置项mapreduce.job.queuename
      • kerberos等问题,具体配置
    • 原因7: hadoop-indexing运行完第一个MR任务之前或之后之提示找不到数据
      • 因为Druid用的是UTC时间,所以在任务配置中的intervals需要设置为类似"2017-05-20T00:00:00+0800/2017-05-22T00:00:00+0800"这样的形式,即后面加上+0800,否则会按照UTC时间处理

数据导入(hadoop-indexing)

部署好了之后,导入数据做了些测试。具体的Schema、过程等就不细说了,参考官方文档没什么大坑。这里只对Druid的表现做一下评测

所用数据

使用了某段时间的某数据,出于保密考虑略去。原始数据共104个字段,其中维度字段按照基数不同,划分为5类:

  1. t1 基数在70万左右,共2个
  2. t2 基数在2万~4万左右,共5个
  3. t3 基数在1千~1万之间,共5个
  4. t4 基数在1百~1千之间,共20个
  5. t5 基数在1百以内,共59个

根据数据的情况,考虑了如下3中导入方式:

  • 方案1: 不做roll up,导入所有列,跳过错误行
  • 方案2: 做roll up,导入所有列,跳过错误行
  • 方案3: 做roll up,去掉两个基数过大的列,跳过错误行

导入后数据情况如下:

数据情况大小条目数
原始未导入184.2 GB2.8 亿
方案1导入23.5 GB2.7亿
方案2导入11.1 GB2.5亿
方案3导入8.8 GB2.1亿

根据数据情况,安排了若干种查询来测试Druid的性能,单位是秒。以下测试全部通过Json Over Http查询方式完成:

查询方案1方案2方案3
TimeSeries count(*)0.464s0.741s0.347s
TimeSeries sum(count)0.546s0.457s0.411s
GroupBy t1 sum(count) limit 1035.841s28.459s
GroupBy t2 sum(count) limit 104.512s3.686s3.574s
GroupBy t3 sum(count) limit 104.162s4.464s3.334s
GroupBy t4 sum(count) limit 103.935s3.470s3.134s
GroupBy t5 sum(count) limit 104.044s3.195s2.734s
GroupBy t1 sum(count) limit 10 with filter 1.8亿36.475s28.431s
GroupBy t2 sum(count) limit 10 with filter 1.8亿2.542s2.077s1.434s
GroupBy t3 sum(count) limit 10 with filter 1.8亿1.372s1.423s0.989s
GroupBy t4 sum(count) limit 10 with filter 1.8亿1.513s1.411s1.199s
GroupBy t5 sum(count) limit 10 with filter 1.1亿2.612s2.389s2.026s
TopN t1 sum(count) limit 101.477s1.289s
TopN t2 sum(count) limit 101.129s1.286s0.855s
TopN t3 sum(count) limit 100.955s0.965s0.601s
TopN t4 sum(count) limit 101.364s0.922s0.727s
TopN t5 sum(count) limit 101.079s0.926s0.717s

结果分析:

基本与根据Druid的设计思路推测出来的适用场景相符:

  • roll up
    • 对Druid的存储空间影响非常明显。在维度基数不是特别大的情况下,做roll up节省了50%以上的存储空间
    • 对Druid的查询性能有一定影响,但不像存储空间那么明显
  • 维度基数
    • Druid的存储空间受维度基数的最大值影响明显,本例中去掉2个基数较大的字段,存储空间降低为原来的80%以下
    • 对查询性能影响非常明显,分为两点:
      • 对基数较大的维度进行汇总,响应时间很长
      • 对其他维度进行汇总,没有较大维度基数的表查询响应时间明显缩短
  • TimeSeries和TopN相对Group做的优化非常明显,在能使用这两种查询的前提下,应当尽量使用这两种查询
  • Apache Calcite目前还不能有效的将group by ... order by ... limit ...这种查询有效的转换为TopN查询,因此性能方面会有一定影响
  • 目前使用的是SATA磁盘,SSD磁盘的性能提升待测

附录

druid-0.10.0部分新特性

  • 支持numeric dimension,之前的版本要求dimension必须是字符串类型
  • 支持内置SQL,虽然依然是Experimental,提供了Json over http以及Apache Calcite Avatica两种接入方式
Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐