Je Vois Tout

Druid问题及解决

在这个帖子里面记录使用Druid过程中出现的问题及其解决,大问题另开帖子,小问题就准备持续追加

1. 关于Druid pending task 报警以及误报消除(2017-06-22)

druid提上来一套报警,核心逻辑是curl http://<Overlord_IP>:<Overlord_Port>/druid/indexer/v1/pendingTasks -H "Content-Type:application/json" -X GET获取overload的pending的task,如果有task长期pending证明druid集群有问题,要么是资源不够,要么是有资源但是没有分配任务,导致任务处于pending状态,但是有时候在整点时刻会收到报警短信。到线上环境观察发现没有task处于pending状态,查看overload的日志,整点时刻会提交大量任务,所有的任务在running之前都会短暂的处于pending状态,大概持续几十毫秒,如果有大量任务都有几十毫秒的pending状态,那么curl请求很有可能会捕捉到这一行为,从而发出报警短信,所以现在没做其他处理,让报警服务把获取到的pending状态的task打印出来,看一下是不是只持续几十毫秒,等下一下报警的时候再看这个问题。

2. 关于实施查询的延迟问题(2017-07-03)

用户反映某DataSource的查询时延严重,在查询50分钟之后数据才趋于固定,而之前使用sparkstreaming入redis的时候,数据延迟几分钟。 下表是用户查询的结果,所有查询时间段是相同的, "intervals": "2017-07-02T16:00:00.000Z/2017-07-03T08:20:00.000Z", 用户从16:20刚过就开始查,一直到17:13之后才稳定。同时用户进行过时间戳比较,还没发现有写入时间戳和本地时间戳超过20分钟的。
   16:23  4187666770
   16:31  4254156264
   16:32  4257247347
   16:33  4262472723
   16:37  4293654970
   16:41  4323229837
   16:44  4346392255
   16:47  4370042705
   16:50  4388952373
   16:53  4408228544
   16:56  4423678662
   17:00  4450052142
   17:13  4455014775
   17:20  4455014775
起初怀疑是消息延迟所致,但是考虑到之前他们的数据没有这么久的延迟,以及没有超过20分钟的,明显与观察不符。他们怀疑是Druid构建索引的时间太长,但是看过log并结合使用经验明显不可能。
之后,在UTC时间2017-07-03T09:00-09:10之间多次查询 "intervals": "2017-07-03T08:00:00.000Z/2017-07-03T09:00:00.000Z“的数据,数据已经稳定,此时这个realtime任务还在接收数据(queryGranularity:HOUR,windowPeriod: PT10M),这个现象说明,数据延迟绝对没有超过10分钟。
后来了解到,他们的queryGranularity是HOUR,才想到,在用户查询的过程中,数据是不断摄入的,2017-07-02T16:00:00Z - 2017-07-03T08:20:00Z 的效果相当于2017-07-02T16:00:00Z - 2017-07-03T09:00:00Z ,因此即便查询时间已经过去,但是还是看到它不停地涨,同时即便数据没有任何延迟,也是会看到这种现象的。
如果用户反映查询时间段内的数据一直增长,需要由浅入深考虑以下几点:1、 查询intervals用的是北京时间,这样可能一直涵盖最新的时间点; 2、数据源延迟问题  3、queryGranularity问题,也就是该条记录的问题  4、Druid构建索引的速度,这个还没有去验证过
如果想消除这种现象的话,可以在配置文件里面把HOUR改成MINUTE,但是有可能更改后segment膨胀很多(这种情况对应现有粒度下聚合效果特别好),也可能变化不大(这种情况对应现有粒度下聚合效果一般),这个需要测试。
用户现有segment一小时最大450M,如果真的需要MINUTE的查询粒度的话,假如在HOUR粒度下聚合效果良好,那么最大有可能每小时segment大小是 450 M * 60 = 27G(这种情况对应的是,每个小时出现的数据在每个分钟内都能出现,数据的出现完全均匀分布,猜测基本不可能出现这种情况),每天数据量是 648G。如果真的有这种需求,不建议减小segment时间长度,还是1小时,但是增加partition数量,控制在每个segment大小1G,那么需要27个peon为其跑任务,光这个业务就要准备54个槽位应对切换峰值,就是把现有集群规模翻一番,计算资源说完了再说存储资源。
集群现有空闲存储空间34T = 34,000G,剩余空间全部用来存储这个业务能够存储 52天。
如果这种需求真的跑下来,查询速度将不会快,查询一小时扫过的数据量已经超过其业务一天扫过的数据量还要大2.5倍,查询一天扫过的数据量已经是现在查2个月的数据量,因此将会特别慢,即便实时入估计也得半实时查,甚至不如落地到HDFS跑Hive了。
以上是最坏的情况,具体如何需要测试。当然问过用户他不需要分钟粒度的查询维持现状。如果有上述假设的变态的需求的话,需要这么去考虑。   
所以算了一圈,如果再最坏情况下,会为解决一个问题引入了新问题,估计效果还不如原来好,所以在这种情况下应该拒绝这种变态需求。如果在不是很恶劣的情况下资源消耗应该没有这么多,这就需要去权衡减小查询粒度值不值得了。应该先测一把,然后再下结论。

3. 关于peon实时任务/tmp路径满导致相应segment创建失败(该问题反映出Druid以PEON为执行主体的实时任务架构的脆弱性,一旦实时任务出现问题,实时数据将全部丢失,除非人工干预,否则数据将不能恢复。)

第一反映是他的实时任务没有成功创建,因为之前两周他的实时任务曾多次因为数据源有问题而缺失中间的segment。但是他反映其任务全部发送成功。
想到从HDFS(所有PEON启动的realtime日志都会存在druid.indexer.logs.directory=hdfs://ns1/user/druid/localStorage/middle中,在$DRUID_HOME/config/middleManager/runtime.properties中)上找一下相关日志,如果日志存在,那么实时任务已经跑过那么就不是用户的问题了,缺失时间段的日志全部存在,这下可以肯定是任务本身有问题了。
联想到是本地空间不够,2.140的/tmp路径易满,同时发现2.140的middleManager启动时候尚未加-Djava.io.tmpdir=/data0/druid/tmp 参数,这样的话该参数的值默认为/tmp。后来通过查日志,发现所有失败任务的确全都是跑在2.140上的。
正确启动middleManager的指令应该是nohup java -Djava.io.tmpdir=/data0/druid/tmp -Xmx1g -Xms1g -XX:+UseConcMarkSweepGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Duser.timezone=UTC -Dfile.encoding=UTF-8 -Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager -classpath config/_common/:config/middleManager/:/usr/local/hadoop-2.4.0/etc/hadoop:lib/* io.druid.cli.Main server middleManager >> /data0/druid/middlemanager/middle.log 2>>/data0/druid/middlemanager/middle_error.log &
解决方法:
    1) 先使用接口<MiddleManager_IP:PORT>/druid/worker/v1/disable把2.140这个middleManager给disable掉,效果是:该middleManager不会再接收新任务,等跑完现有的实时任务后,便可以重启。(具体指令是:curl http://<MiddleManager_IP>:<MiddleManager_Port>/druid/worker/v1/disable -H "Content-Type:application/json" -X POST )。所有槽位72个,目前峰值占用槽位53个,2.140提供槽位15个,因此他自己disabled之后没有影响。
    2) Disable之后使用接口 <MiddleManager_IP:PORT>/druid/worker/v1/enabled 查看其状态,发现已经是disabled,同时经过几个整点后,发现也确实没有再接收新的任务。(验证disable的具体指令是: curl http://<MiddleManager_IP>:<MiddleManager_Port>/druid/worker/v1/enabled -H "Content-Type:application/json" -X GET ,返回结果{"<MiddleManager_IP>:<MiddleManager_Port>":false} ,证明没有enabled )
    3) 等该middleManager上所有任务结束后,杀掉该进程,使用正确的启动方式启动,指定-Djava.io.tmpdir=/data0/druid/tmp
    4) 再次启动middleManager进程就能自动变为enabled。若不重启,需要使用接口 <MiddleManager_IP:PORT>/druid/worker/v1/enable 将middleManager进行enable,这样就能正常工作(具体指令 curl http://<MiddleManager_IP>:<MiddleManager_Port>/druid/worker/v1/enable -H "Content-Type:application/json" -X POST)
与Peon写入本地的路径相关的配置项
$DRUID_HOME/config/middleManager/runtime.properties文件中
    Property
    Description
    Default
    生产集群配置
   druid.indexer.task.baseDir    Base temporary working directory.     /tmp    /data0/druid/index
   druid.indexer.task.baseTaskDir     Base temporary working directory for tasks.     /tmp/persistent/tasks    /data0/druid/index/persistent/tasks
   druid.indexer.task.hadoopWorkingPath     Temporary working directory for Hadoop tasks.     /tmp/druid-indexing    无,默认。目前不使用该功能
   druid.indexer.runner.javaOpts    -X Java options to run the peon in its own JVM.    -Djava.io.tmpdir默认为/tmp    -Djava.io.tmpdir=/data0/druid/tmp(部分)
   druid.segmentCache.locations              [{"path": "/data0/druid/indexCache", "maxSize"\: 5000000000000}]

4. 关于PEON启动的参数 — 紧接上一个问题

在$DRUID_HOME/config/middleManager/runtime.properties中,
已经有如下配置:
druid.indexer.runner.javaOpts=-server -Xmx6g -XX:+UseG1GC -XX:MaxGCPauseMillis=100 -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Duser.timezone=UTC -Dfile.encoding=UTF-8 -Djava.io.tmpdir=/data0/druid/tmp。该参数指定PEON的启动参数。
很奇怪的是,明明已经指定 -Djava.io.tmpdir=/data0/druid/tmp,可为什么在启动middleManager时候没有加入 -Djava.io.tmpdir=/data0/druid/tmp就写入默认/tmp呢?如果该参数指定的值没被加载的话,那么启动的PEON的内存就应该和middleManager一样大是1G而不是看有的将近10G。
后来看Runtime Configuration(http://druid.io/docs/0.8.2/configuration/indexing-service.html),发现对参数druid.indexer.runner.javaOpts的解释:“-X Java options to run the peon in its own JVM.”。才发现该参数中指定的值只加载 -X参数,-D就忽略了,也就印证了之前的现象。
关于这个现象的代码解释:
原因在 io.druid.indexing.overlord.ForkingTaskRunner中,Line 177 - Line 200
Override task specific javaOpts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Object taskJavaOpts = task.getContextValue(
"druid.indexer.runner.javaOpts"
);
if (taskJavaOpts != null) {
Iterables.addAll(
command,
new QuotableWhiteSpaceSplitter((String) taskJavaOpts)
);
}
for (String propName : props.stringPropertyNames()) {
for (String allowedPrefix : config.getAllowedPrefixes()) { // "com.metamx","druid","io.druid","user.timezone","file.encoding","java.io.tmpdir","hadoop"
if (propName.startsWith(allowedPrefix)) {
command.add(
String.format(
"-D%s=%s",
propName,
props.getProperty(propName)
)
);
}
}
}
// 不过关于这段逻辑是有问题的(不过不涉及本问题),在Druid 0.8.3中已经被修复,详见 https://github.com/druid-io/druid/issues/1841
这是将middleManager的启动参数中,先读入 $DRUID_HOME/config/middleManager/runtime.properties中druid.indexer.runner.javaOpts 定义的参数,再读入以AllowedPrefixes开头的配置项的内容读出并作为Fork出的Peon的参数,该类的代码采用反射无法追下去,但是可以看到props的类型是JAVA原生的Properties,猜测这个就是JVM参数,也就是MiddleManager运行参数。由于JVM同名参数后出现的覆盖前面出现的,这么做的结果就是$DRUID_HOME/config/middleManager/runtime.properties中druid.indexer.runner.javaOpts对应的 -D参数全部被覆盖,也就符合官网说的-X Java options to run the peon in its own JVM。这么做是方便传递参数?保证系统稳定?毕竟middleManager这时候已经起来了,这时候参数是可以用的?

5. 关于Tranquility任务开多副本/分区

在提交任务的配置文件里面,添加如下配置:
--> properties
1
2
3
4
"properties" : {
"task.partitions" : "1",
"task.replicants" : "1"
}
多副本:
把task.replicants由默认的1,改为大于1。同一时interval内的任务可以相互替代。
经过测试,发现同一interval的2个task能够相互避开不在同时一台机器上(在只有一个middleManager的测试集群上测试的时候Tranquility还一直报错,从侧面印证了这个特征)。同时测试过,干掉其中一个不影响查询结果。
多分区:
task.partitions更改值大于1。同一interval内的task不可相互替代,都分担该时段内数据的一部分。
代码解释之后给出。

6. /tmp路径满导致实时任务的segment创建失败

类似问题: 路径满导致实时任务失败

7. Druid Peon任务多Replicants在 handoff阶段 HDFS 报 Lease mismatch 导致任务失败

8. Druid长时间跨度查询慢

9. Druid支持多Hadoop集群

10. Druid BUG – 某segment无法加载,有更新版本的同segment产生,仍旧尝试加载那个不能加载的

11. Druid Overlord 发生主备切换所有实时任务失败

12. Druid Broker Full GC 进程卡死

13. Druid批量任务优化相关

Druid批量任务调优
Druid批量任务数据时间列集中数据量大而失败

14. Druid入数配置文件,timestamp写在dimension导致查询错误

  做select查询时报错如下:
"error" : "Invalid format: \"1504001344000\" is malformed at \"4000\"" }```
1
2
一个错误的配置如下:
```"dimensions" : ["timestamp","group","cluster","topic","partition"]
错在,把timestamp这个时间列写在了dimension,去掉就行了 可以看到,把timestamp当成维度了

John Doe

A designer, developer and entrepreneur. Spends his time travelling the world with a bag of kites. Likes journalism and publishing platforms.

Comments