货拉拉HBase Bulkload实践

2,848 阅读8分钟

一、引言

HBase是基于Hadoop构建的一个高可用、高性能、支持水平扩展的NoSQL数据库,因其优秀的表现能力,多用在实时或准实时场景。在货拉拉,HBase承担了在线存储的职责,支持着风控、地图、实时标签等重要业务场景。

同时,在实际生产环境中,存在大量数据T+1的场景:即用户需要每日基于Hive中各系统的各类数据生成特定业务数据(如标签、指标等),定期将这部分数据(全量或增量)导入HBase系统,以提供在线服务查询,支撑风控、运营等场景。

二、问题与挑战

在初期,由于缺乏统一的离线数据导入流程,业务基于HBase Bulkload实现各自的Hive to HBase代码,在线上实践中,存在一些痛点与稳定性的风险,总结如下:

  1. 开发效率低下重复开发,部分业务开发不熟悉大数据组件技术,耗时又耗力。
  2. 缺少链路保障:部分业务对数据产出时间有较大依赖,对任务失败或延期、数据丢失等问题缺少发现能力。
  3. 影响HBase稳定性:高峰期执行Bulkload造成多次线上隐患,甚至出现过用户将A集群HFile Load到B集群(由于B集群到A集群的NN端口不通),导致线上HBase 20min不可用。

因此,需要构建一套Hive To HBase的通用工具,并且保障整个数据链路的稳定性,主要需求点:

  1. 简单通用:能满足绝大多数业务的数据转化需求,集成到数据开发平台,用户只需简单参数配置即可。
  2. 可观测,可告警:能明确输入输出数据量,任务执行时间管理等。在任务异常(延时、报错等)时能主动通知到任务负责人,重要业务场景需执行对应的应急预案。
  3. 可管控:依托数据开发平台,对任务变更、上线全流程增加审批管控,减少变更带来的稳定性风险。

三、调研

Hive To HBase的实现通常有两种架构,流程分别如下。

  • 方案一 1.png
  1. 在离线集群(YARN/HDFS/Hive混部集群)利用Spark/MR读取Hive数据转换成HBase内部数据格式,并直接将HFile写到在线HBase集群上。
  2. 通过HBase内置工具LoadIncrementalHFile将生成的HFile加载到HBase集群中。

优缺点: 2.png

  • 方案二 3.png
  1. Transform阶段将HFile写到离线集群中。
  2. 引入Hadoop DistCp工具,依赖其灵活的限速配置,将生成好的HFile拷贝到在线HBase集群中。

结论:由于不限速对在线HBase存在较大稳定性隐患,最终使用了方案二来实现。

四、实践过程

Transform

关于数据处理的实现逻辑,社区中有较多优秀的实践,这里就不再展开。在货拉拉的实际生产中,大部分业务的需求场景比较简单,使用统一的Transform流程即可,但也存在由于业务特性,需要定制化实现的场景,本次仅介绍统一方案。为了便于使用,程序封装在脚本中,集成在数据开发平台作为模版任务,用户仅需配置几个简单参数即可创建任务,实现数据处理,方便快捷。

脚本具备如下特性:

  • 多RowKey生成策略:自定义RowKey设计,例如Hash,加盐和截取字段。
  • 列名映射:支持HBase表列名与Hive列名的自定义映射,不需要强求按照hive表的字段名字或顺序。

任务上线后,在生产环境灰度时,也发现了如下问题:

  1. 触发Compact高峰,抢占资源:风控业务每次为单个Region生成多个HFile(单个文件比较小),在CompactionChecker的下次检测时,会触发Compact高峰大量占用CPU和IO资源,并且这个数据导入任务依赖公司离线报表,离线报表产出发生延时后,Bulkload操作就可能在业务高峰期才被执行,对HBase在线服务存在较大的稳定性风险。
  2. 数据本地率低,查询P99上涨:Distcp随机选择DN节点进行拷贝,这会导致HBase表的本地率下降,对于延时较为敏感的业务场景,是不可接受的。

针对上述两个问题,结合业务特点的分析,提出整体的解决方案:

  1. 业务合并任务,减少为一个Region生成的HFile数量。
  2. 优化表级Compaction配置,尽量在Bulkload后不触发Compaction(可以在Transform过程中设置hbase.mapreduce.hfileoutputformat.compaction.exclude 来避免)。
  3. 部分业务不能设置TTL,此时Bulkload的文件无法被Minor Compaction清理,为避免单个Region下文件数不断增加,通过一个外挂的Major Compaction工具,在业务低峰期调起Major Compaction来合并HFile。
  4. 改造Distcp,支持设置favoredNodes,提高数据本地率。

Compaction工具

在进行了HFile数优化和Compact配置的调整后,业务在Bulkload的查询抖动得到了明显的改善:

优化前 优化后 5.png

但由于业务特性,这部分业务表不能设置TTL,所以只能通过Major Compact来清理数据与降低HFile数。为了避免在高峰期执行Major Compact,关闭了默认的Major Compact,并且实现了一套Major Compact工具,支持多种调度策略:

  1. OffpeakCompact:用来控制低峰期才提交Compact任务,具体的Region选举策略为如下几个。
  2. TimeCompact:基于时间间隔的策略,计算Region上一次Major Compact与当前时间之差。
  3. FileNumberCompact:计算Region的Store中的HFile数是否超过配置值。

效果
上线运行了一段时间,观察合理控制了表的HFile数量,同时规避了Bulkload后的Compact高峰。

但也在实际的运行中,发现一些问题:

  1. 存在一些Region的lastMajorCompactionAge始终为0,这就导致使用TimeCompact策略时是失效的。通过分析,发现Bukload的HFile文件中的create_time_ts为0 。 6.jpeg 再结合代码分析,发现HFileOutputFormat2在构建HFile时未设置CreateTime,代码如下:
  //1.生成HFile时创建writer
  HFileOutputFormat2.getNewWriter()

      HFileContextBuilder contextBuilder = new HFileContextBuilder()
              .withCompression(compression)
              .withChecksumType(HStore.getChecksumType(conf))
              .withBytesPerCheckSum(HStore.getBytesPerChecksum(conf))
              .withBlockSize(blockSize);
                
  //2.load HFile时split HFile
  LoadIncrementalHFiles.copyHFileHalf()
    
      HFileContext hFileContext = new HFileContextBuilder().withCompression(compression)
         .withChecksumType(HStore.getChecksumType(conf))
         .withBytesPerCheckSum(HStore.getBytesPerChecksum(conf)).withBlockSize(blocksize)
         .withDataBlockEncoding(familyDescriptor.getDataBlockEncoding()).withIncludesTags(true)
         .build();
      halfWriter = new StoreFileWriter.Builder(conf, cacheConf, fs).withFilePath(outFile)
         .withBloomType(bloomFilterType).withFileContext(hFileContext).build();

优化了相关代码后,整体运行结果符合预期,同时也将这部分回馈到了社区,相关JIRA:HBASE-27636HBASE-27688PHOENIX-6955

  1. 尽管配置了Off-peak Compactions,但在业务低峰期依旧不能充分的利用集群资源去合并HFile。通过分析,发现目前的Off-peak Compactions是全局比较,同一时间只能运行一个Off-peak的Compact。关键代码如下:
private static final AtomicBoolean offPeakCompactionTracker = new AtomicBoolean(); 

    // Normal case - coprocessor is not overriding file selection.
    if (!compaction.hasSelection()) {
     boolean isUserCompaction = priority == Store.PRIORITY_USER;
     boolean mayUseOffPeak =
       offPeakHours.isOffPeakHour() && offPeakCompactionTracker.compareAndSet(falsetrue);
     try {
       compaction.select(this.filesCompacting, isUserCompaction, mayUseOffPeak,
         forceMajor && filesCompacting.isEmpty());
     } catch (IOException e) {
       if (mayUseOffPeak) {
         offPeakCompactionTracker.set(false);
       }
       throw e;
     }
     assert compaction.hasSelection();
     if (mayUseOffPeak && !compaction.getRequest().isOffPeak()) {
       // Compaction policy doesn't want to take advantage of off-peak.
       offPeakCompactionTracker.set(false);
     }

针对此处的优化,我们正在内部进行相关的优化验证,也创建了相关JIRA:HBASE-27861,希望能与社区一起讨论。

DistCp

对于延时要求较高的业务,数据本地率对其RT的P99P999影响较大,尽管HBase在 HBASE-12596 中优化了Bulkload的数据本地率,但使用DistCp后,数据本地率则无法保障。对此针对DistCp工具进行改造并且封装成新的工具,具备如下特性:

  1. 指定FavoredNodes:支持为不同的HFile文件设置特定的FavoredNodes。
  2. 多集群拷贝:用户只需简单配置目标集群名,并可完成同份数据拷贝到集群。

对于DistCp的改造,部分能力已开源,参见 HADOOP-18629。同时也针对HBase进行两项对应的优化,见: HBASE-27670HBASE-27733

数据校验

部分业务非常在意Bulkload后数据的质量,有多少数据成功落HBase,是否存在丢数等。为解决用户的担忧,保证数据的正确性和可靠性,主要进行了如下验证:

  1. Bulkload流程验证,通过如下指标判断完成度
    • Transform:实现Counter计算行数。
    • Distcp:拷贝前后HFile目录总大小。
    • Load:HFile文件是否被清空,表级Bulkload的相关监控。
  2. 抽样验数,用户指定RowKey查询HBase表。

五、稳定性保障

数据链路作为服务的延伸,在整体的稳定性保障中也相当重要,对此我们也构建了对应的链路稳定性保障方案(方案如下),从而提升整体的稳定性,目前能力基本已落地或正在落地。 7.jpg

六、总结

本文从用户痛点与服务稳定性的角度出发,介绍了货拉拉大数据基础架构团队在HBase离线数据链路保障上的思考与实践。希望能为读者提供一些参考,也欢迎与我们沟通交流。

笔者介绍:
闫蒙蒙|大数据基础架构组,专注于HBase稳定性维护,Apache HBase Contributor 章啸|大数据基础架构组,负责大数据存储方向,Apache HBase Contributor

相关引用
issues.apache.org/jira/browse…
issues.apache.org/jira/browse…
issues.apache.org/jira/browse…
issues.apache.org/jira/browse…
issues.apache.org/jira/browse…
issues.apache.org/jira/browse…
issues.apache.org/jira/browse…
issues.apache.org/jira/browse…