小站重新装修,欢迎到访。

[备忘]记一次使用Btrace排查线上问题

本文仅供博主备忘。

背景

我目前负责我司的分布式消息队列产品,某日,有业务方上报可能发生少量数据丢失,事态紧急,赶紧排查!

验证问题

观察监控曲线,发现15分钟动态监控报表的生产和消费确实有数量上的差异,丢失的不算少。这里要说明一下,一般来说如果生产消费是持续不断的,是很难通过此类报表判断出数据丢失,因为消费是有延迟的,统计会在时间维度上产生错位,幸运的是此业务方的消息是定时发送的,大概是一两分钟一批,消费速度也很快,所以统计曲线看起来就像是一条条抛物线,丢不丢一算便知。

检查各计数器和log

相关计数器正常,log也未发现任何疑点。

思考

服务正常,但消息确实有少量丢失,而且受影响的topic似乎也极少,这个仅从表面看想必是很难看出原因了。考虑到消息的消费先是由LeaderServer分配ID段的,所以我决定祭上神器Btrace,线上抓取分配的ID和ACK表对比,力求找出蛛丝马迹。

动手

脚本如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@BTrace
public class GetMessageIDGreaterThan {
@OnMethod(clazz = "com.yihaodian.architecture.jumper.common.inner.dao.impl.mongodb.MessageDAOImpl", method = "getMessageIDGreaterThan", location = @Location(Kind.RETURN))
public static void traceExecute(@ProbeClassName String name,@ProbeMethodName String method,String topicName, Long messageId,int size, int index,@Return Long ret){
if(BTraceUtils.compare(topicName,"dmp_user_creative_product")){
BTraceUtils.print(strcat("params","=>"));
BTraceUtils.print(strcat(topicName,","));
BTraceUtils.print(strcat(str(messageId),","));
BTraceUtils.print(strcat(str(size),","));
BTraceUtils.print(strcat(str(index),","));
BTraceUtils.println(strcat("result=>", str(ret)));
}
}
}

其中,messageId是已经分配的最大ID,返回值ret是此次分配的最大ID。理论上,一系列输出应该是连续的。也即上一行的ret和此行的messageId是一致的。事实确实如此。

继续比对ACK表。解析一系列输出,写了个脚本输出了一堆mongo的查询语句,目的是查出哪些段的ID丢失了ack。经对比,一个奇怪的现象出现了,所有长度为264的ID段都丢失了。经检查,此数值乃JVM启动参数之一,即-Dglobal.block.queue.fetchsize,用来配置ConsumerServer批量获取消息的大小。为何这么巧合,丢失的都是长度为264的ID段呢?

表面淡定内心纠结地查了一通,发现了疑点,不是每台机器的-Dglobal.block.queue.fetchsize都一致,但最大的是就是264。这个比较意外,但想了想应该也不会出啥大问题啊。

更加糊涂了。

翻查代码,终于查找到元凶。如下,

1
2
3
4
if (messageIDList != null && messageIDList.size() <= fetchSize) {
//logic goes here
return messageIDList;
}

其中,

1
private final int fetchSize = Integer.parseInt(System.getProperty("global.block.queue.fetchsize", "200"));

问题找出来了,LeaderServer的配置是264,所以ConsumerServer拿到的ID段长度也是264,但本机的fetchSize比264要小,所以被直接忽略了。

那么问题是怎么产生的呢?导致问题的代码看似多余,其实也是一种自我保护。所以问题还是要归咎于配置不一致,引起逻辑上的混乱。那么为什么配置会不一致呢,原来前阵子SA为了方便使用puppet部署,将各种配置都参数化了,效果就是代码部署时,通过对硬件的计算,修改各种参数取值,这样就降低了运维的复杂度。虽然申请了一批硬件配置相同的机器,但是其“软配置”却各不相同,导致有些关键参数未能完全一致,而我又未能察觉隐患,故导致此问题。马上修改配置,重启,解决。

反思

吃一堑长一智,这件事情的教训就是,类似问题很难保证完全避免,但在研发阶段就要多思考会导致问题的场景,及时写注释或记录到文档里。毕竟代码逻辑久了谁都会忘,但注释放那就会时刻提醒你不要犯错。当初如果在脚本的相关参数上备注:必须保证每台机器的此配置取值一致,问题也就不会发生了。

另外一点就是Btrace确实是排查问题之神器,让人无路之时转而柳暗花明。

迁移到hexo,重新开张

过而改之,善莫大焉。

不过,这一年多也不是懒,家事、公事事事操心。如今这博客重新装修下继续营业。后面我会逐渐把这遗失的一段时光写出来,谁让我记性太差。

为了此次重新开张,我换了工具和主题,放弃了jekyll转而启用hexo,主要是因为这个主题我比较喜欢。不过hexo自定义起来绝不轻松,折腾了好久。

还有一个不是理由的理由,之前被美帝子民抢注的jingege.me,终于又回到了我手里!

无论如何,欢迎回来。

oozie的使用总结

本篇不是oozie的教程,官网的文档虽然比较粗糙,但已经非常全面,可直接参考,下文总结下oozie使用中遇到的一些问题(持续更新中)。

oozie版本:3.3.2

###时区问题

oozie默认使用UTC时区,而服务器上可能是CST,建议统一使用GMT+0800。

  • 不要修改oozie-default.xml,无效。在oozie-site.xml中添加:
1
2
3
4
<property>
<name>oozie.processing.timezone</name>
<value>GMT+0800</value>
</property>
  • 可以用oozie info --timezones来查看支持的时区
  • 使用GMT+0800后,时间不可以再使用形如2014-01-24T13:40Z的格式,要使用对应的形如2014-01-24T13:40+0800的格式
  • 还有一点比较重要,即oozie web console的TimeZone设置要和上述一致,否则你在web console中看到的时间在感官上都是不正确的

###Hive相关
oozie会启动一个MR job来启动hive client,需要在你的oozie app里自行指定hive的配置,以及提供相关lib,因为不确定是哪一台节点,所以需要给每一台计算节点都分配hive metastore的权限。

hive-site.xml

  • 使用指定hive-site.xml位置,下例中对应位置为该oozie app所在的目录,也可以指定一个绝对的HDFS路径

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <action name="trackinfo">
    <hive xmlns="uri:oozie:hive-action:0.2">
    <job-tracker>${jobTracker}</job-tracker>
    <name-node>${nameNode}</name-node>
    <job-xml>hive-site.xml</job-xml>
    <configuration>
    <property>
    <name>mapred.job.queue.name</name>
    <value>${queueName}</value>
    </property>
    </configuration>
    <script>trackinfo.hql</script>
    <param>label=${wf:actionData('date')['lastday']}</param>
    </hive>
    <ok to="end"/>
    <error to="fail"/>
    </action>

####hive的lib

  • 在job.properties中设置oozie.use.system.libpath=true
  • 使用oozie.libpath=/path/to/lib指定lib路径

####metastore

  • 切记要把metastore的相应jdbc驱动放到lib里
  • 别忘了给每个计算节点授权,否则连接不上metastore

####hive action的错误如何分析?
hive action执行失败,怎么分析原因呢?在oozie的web console中,打开因出错被KILL的action节点,打开Console URL即可以看到对应的MR jobdetails页面,一般错误信息在Map的日志里,打开你就会发现,日志的内容涵盖了hive job的定义、hql以及控制台输出,足够分析错误原因了。

###前一天
由于我司大多数job是计算前一天的数据,故需在调度时动态计算前一天的日期字符串,使用shell action结合 可以捕捉控制台输出,输出格式需为A=B,这样就可以使用${wf:actionData('A')['B']}提取所需的字符串了。

1
2
3
4
5
6
7
8
9
10
11
12
13
<action name="date">
<shell xmlns="uri:oozie:shell-action:0.1">
<job-tracker>${jobTracker}</job-tracker>
<name-node>${nameNode}</name-node>
<exec>${cmd}</exec>
<argument>-d</argument>
<argument>1day ago</argument>
<argument>+lastday=%Y-%m-%d</argument>
<capture-output/>
</shell>
<ok to="nextStep"/>
<error to="fail"/>
</action>

其中cmd=/bin/date

###权限
遇到HDFS相关的权限问题,请通过修改oozie app提交用户或修改HDFS文件权限的方式自行解决。

博主是一个不是很聪明的码农。完美主义者,强迫症中期。这里会记录一些回忆和点滴,以博为镜。

武器库:

该博客使用基于  Hexo  的  simpleblock  主题。博客内容使用  CC BY-NC-SA 3.0  授权发布。最后生成于 2017-02-20.