你好,游客 登录 注册 发布搜索
背景:
阅读新闻

淘宝:HBase Bulkload bug修复及patch提交

[日期:2014-01-28] 来源:CSDN  作者: [字体: ]

Hadoop,谷歌GFS、MapReduce及BigTable的开源实现,而这3个工具最初被谷歌开发的原因也是支撑其庞大的搜索帝国。因此,如果谈最适合的使用场景,搜索绝对是最适合业务之一;同时,基于业务的类型,互联网公司也是当之无愧的Hadoop先行者。放眼国内互联网公司,最有经验的无疑归属百度、阿里、腾讯、360等公司,这里为大家分享的是淘宝在HBase使用过程中出现的问题及给出的修复方案。

第一部分:问题排查。

在店铺搜索相关需求的开发自测过程中,碰到了一个问题:bulkload数据的过程时间过长,运行了很久都没有结束,于是查看日志,发现bulkload的程序在不停的重试,信息如下(当天信息未保存,这是刚重现时截的)。

这些信息看起来没啥问题,bulkload在往表test_shopinfo里load各个hfile,失败了,但是错误是可恢复的,将会重试,接着又看到如下的信息:

好了,问题就是这样,bulkload在不停的失败,不停的重试,没有个尽头。开始怀疑是hbase集群出了情况,经过对hbase的一番排查,最后在regionserver的日志里发现了对应的一些信息:

从日志里看到,regionserver检查ladder这个family的hfile bounds,发现与regionserver的bounds匹配上了,应该是成功往里load了,但是ecrm这个family的hfile load失败了,日志里的错误信息是由于发生了split才失败的,但是是可以恢复的。

但是我们对于hbase表的策略是通过设定hfile的最大size来避免发生split的,所以基本上不会发生split(我们将最大max设得很大),于是觉得regionserver在处理ecrm的hfile时一定出现了问题,接着找到了HRegion.java的代码,相关代码如下:

// validation failed, bail out before doing anything permanent. if (failures.size()
    != 0) { StringBuilder list = new StringBuilder(); for (Pair<byte[],
    String> p : failures) { list.append('n').append(Bytes.toString(p.getFirst())).append('
    : ') .append(p.getSecond()); } // problem when validating LOG.warn('There
    was a recoverable bulk load failure likely due to a' + ' split. These (family,
    HFile) pairs were not loaded: ' + list); return false; }

接着看failures的来源,代码如下,就在上面这段代码的上方:

      List<IOException> ioes = new ArrayList<IOException>();
      List<Pair<byte[], String>> failures = new ArrayList<Pair<byte[], String>>();
      for (Pair<byte[], String> p : familyPaths) {
        byte[] familyName = p.getFirst();
        String path = p.getSecond();

        Store store = getStore(familyName);
        if (store == null) {
          IOException ioe = new org.apache.hadoop.hbase.exceptions.DoNotRetryIOException(
              'No such column family ' + Bytes.toStringBinary(familyName));
          ioes.add(ioe);
          failures.add(p);
        } else {
          try {
            store.assertBulkLoadHFileOk(new Path(path));
          } catch (WrongRegionException wre) {
            // recoverable (file doesn't fit in region)
            failures.add(p);
          } catch (IOException ioe) {
            // unrecoverable (hdfs problem)
            ioes.add(ioe);
          }
        }
      }

一共两处代码往failures里add了东西,下面一处,是先调用了HStore.assertBulkLoadHFileOk(),查看该方法代码后发现,regionserver日志中检查hfile和region bounds的内容就是该方法输出的,而对于ecrm这个family的hfile,根本没有输出相关的bounds信息,因此确定是由上面这段代码第一处failures.add(p)添加进去的,这个时候才反应过来:ecrm这个family是这一次新添加的数据,但是对应hbase表没有重建以添加该family。于是在环境里把hbase表重建,再跑bulkload,很轻松的成功跑完。OK,自测的问题到此已经解决,但是遗留了一个问题:往这hbase表里bulkload不存在的family的hfile,日志竟然告诉我recoverable,然后无限的重试,这不是坑爹吗?于是有了下面的故事。

第二部分:hbase社区上的一番折腾

本着排查问题刨根问底的精神,我又回到了那段坑爹的代码上,仔细的看了两遍,然后发现了问题:

先看这段代码所在方法的说明:

  /**
   * Attempts to atomically load a group of hfiles.  This is critical for loading
   * rows with multiple column families atomically.
   *
   * @param familyPaths List of Pair<byte[] column family, String hfilePath>
   * @param bulkLoadListener Internal hooks enabling massaging/preparation of a
   * file about to be bulk loaded
   * @param assignSeqId
   * @return true if successful, false if failed recoverably
   * @throws IOException if failed unrecoverably.
   */
  public boolean bulkLoadHFiles(List<Pair<byte[], String>> familyPaths, boolean assignSeqId,
      BulkLoadListener bulkLoadListener) throws IOException</pre>

成功返回true,失败且recoverable,返回false,失败且unrecoverable,抛出IOException。

把这整段代码贴上来,方便看:

      List<IOException> ioes = new ArrayList<IOException>();
      List<Pair<byte[], String>> failures = new ArrayList<Pair<byte[], String>>();
      for (Pair<byte[], String> p : familyPaths) {
        byte[] familyName = p.getFirst();
        String path = p.getSecond();

        Store store = getStore(familyName);
        if (store == null) {
          IOException ioe = new org.apache.hadoop.hbase.exceptions.DoNotRetryIOException(
              'No such column family ' + Bytes.toStringBinary(familyName));
          ioes.add(ioe);
          failures.add(p);
        } else {
          try {
            store.assertBulkLoadHFileOk(new Path(path));
          } catch (WrongRegionException wre) {
            // recoverable (file doesn't fit in region)
            failures.add(p);
          } catch (IOException ioe) {
            // unrecoverable (hdfs problem)
            ioes.add(ioe);
          }
        }
      }

      // validation failed, bail out before doing anything permanent.
      if (failures.size() != 0) {
        StringBuilder list = new StringBuilder();
        for (Pair<byte[], String> p : failures) {
          list.append('n').append(Bytes.toString(p.getFirst())).append(' : ')
            .append(p.getSecond());
        }
        // problem when validating
        LOG.warn('There was a recoverable bulk load failure likely due to a' +
            ' split.  These (family, HFile) pairs were not loaded: ' + list);
        return false;
      }

      // validation failed because of some sort of IO problem.
      if (ioes.size() != 0) {
        IOException e = MultipleIOException.createIOException(ioes);
        LOG.error('There were one or more IO errors when checking if the bulk load is ok.', e);
        throw e;
      }</pre>

上面一段代码,在处理一批hfile时,将对应的失败和IOException保存在List里,然后在下面一段代码里进行处理,好吧,问题就在这:上面的代码抓到的IOException,都意味着该次bulkload是肯定要失败的,然而在后续的处理中,代码竟然先处理了failures里的信息,然后输出warm的log告诉用户recoverable,并且返回了false,直接把下面处理IOException的代码跳过了。理一下逻辑,这个地方的处理,必然应该是先处理IOException,如果没有IOException,才轮到处理failures。

至此,问题已经清楚,解决方法也基本明确,可这hbase的代码,不是咱说改就能改的,咋整?

就在这时,道凡大牛伸出了援手。道凡说,就在这,提交issue,可以解决问题!

我寻思着能为hbase做些贡献好像还不错的样子,于是怀着试一试的心态点开了链接,注册,create issue,然后用不太熟练的英文把上面的问题描述了一遍,OK,issue创建完了,心想着应该会有大牛过来看看这个bug,然后很随意的帮忙fix一下,就搞定了,也没我啥事了。

第二天到公司,道凡突然发来一条消息,说issue有人回复了,点进去一看,一位大牛Ted Yu进来表示了赞同,还来了一句“Any chance of a patch ?” 我一想,这是大牛在鼓励咱这newbie大胆尝试嘛,果然很有大牛的风范,冲着对大牛的敬仰,以及此时咱后台组群里大哥哥大姐姐们的鼓励,咱抱着“不能怂”的心态,决定大胆尝试一把。

接下来的事情喜闻乐见,完全不知道怎么整的我根本不知道该干啥,好在有Ted Yu的指点和同事们的鼓励、帮助,一步一步的完成了check out代码,修改代码,搭建编译环境,提交patch,补充test case,在自己的环境运行test case,提交带test case的patch,等等等等等等一系列复杂的过程(此处省略好几万字),终于在今天上午,一位committer将我的patch提交到了多个版本的trunk上,事情到此已经基本了结,svn的log里也出现了我的名字,也让我感觉这些天的努力没有白费(由于时差,跟其它人讨论问题以及寻求帮助都需要耐心的等待)。

在此也希望广大同胞们能勇于提交issue,帮助自己也帮助更多使用这些开源软件的同学们,为造福人类贡献绵薄之力。

附上这次的issue的链接: https://issues.apache.org/jira/browse/HBASE-8192

最后附上一个issue从提交到解决的大概过程,希望对后续提交issue的同学能有所帮助:

1. 创建issue,尽可能的把问题描述清楚,如果解决方案比较明确,一并附上,如果不是很明确,可以在comment里跟其他人讨论、交流。

2. 有了解决方案以后,准备自己提交patch的话,就得搭建开发环境(如果没搭过),包括check out代码(patch一般都是打在trunk上的),安装mvn、jdk等(暂时不清楚具体的jdk版本依赖,我自己搭建的时候用1.6编译出错了,换1.7编译通过的)。这里有一些官方的手册,可能会给你带来一些帮助。

3. 修改代码,重新编译,运行test case,上面的手册对这些过程也有帮助,碰到问题可以参考。修改代码的时候有一些注意事项:可以先看一下这里。运行test case的时候关注一下磁盘的剩余空间,因为没空间时报的错误信息可能不是直接相关的,会是其它的一些Exception,所以要多想着这事(我被这个坑了不少次),test data会占据不小的空间(几个G),还有就是记得mvn clean。

4. attach files将你的patch上传,然后submit patch。这里提交的是一份你代码与trunk代码的diff,要从hbase trunk的svn根目录svn diff。

5. 每次attach files之后,过一会就会有Hadoop QA(不是很清楚是否为自动的)来测试你的patch。test result里列出来的问题是需要解决的(除了那些不是你代码改动带来的test case fail)。

6. 提交了patch之后,issue的状态会变为patch available,这时候(可能需要等一段时间)会有人(不确定是否一定是committer)来帮你review,如果觉得没问题的话他们会在comment里留下+1,或是lgtm(looks good to me)之类的东西。

7. 如果patch基本没问题之后,需要等committer来把你的patch拖到一些branch上进行测试,然后他们会在测试通过之后将你的patch commit到对应的svn上。

收藏 推荐 打印 | 录入:574107552 | 阅读:
相关新闻      
本文评论   查看全部评论 (0)
表情: 表情 姓名: 字数
点评:
       
评论声明
  • 尊重网上道德,遵守中华人民共和国的各项有关法律法规
  • 承担一切因您的行为而直接或间接导致的民事或刑事法律责任
  • 本站管理人员有权保留或删除其管辖留言中的任意内容
  • 本站有权在网站内转载或引用您的评论
  • 参与本评论即表明您已经阅读并接受上述条款