[HBase] - 一次由 Hive HBase 外部表引发的读

2022-05-17  本文已影响0人  六层楼那么高

1 问题

近期业务反馈 HBase GET 读性能不佳,查看监控,每天有几个时间波段,GET P99 请求时延很高。

2 问题原因

定位有一个业务有 7 张 Hive HBase 外部表,每天有两个时间定时通过 sql 的方式,并发导出全表到 hive 。代码类似于:

 insert into table_hive select * from hive_hbase_external_table;

相当于每天定时并发对 7 张大的 HBase 全表 ScanScan 默认会写缓存,这个会导致:

  1. Bucket Cache 被 scan block 写满, 缓存开始淘汰,出现 cache miss (缓存命中率降低) 和 cache eviction (缓存淘汰)。cache 命中率直接影响 HBase 读性能。
  2. 出现 I/O 风暴,读请求没有命中缓存需要读 hdfs,此时由于 I/O 瓶颈,读性能问题进一步被放大。

3 问题分析

查看这几个时间段 GET 请求没有明显变多(单节点 1k/s),而 READ 请求猛增,单节点 read request 达到 600-900k/s。

推断是出现了大量 scan 或者大 scan。这里为什么要推理,因为 Ambari grafana 的 HBase scan 请求数无法显示。

缓存命中率直接影响读性能,查看 BucketCache 命中率情况,这个时间段 cache miss 和 eviction 都陡增。 缓存

推断是 scan 导致缓存写满,所以出现缓存被驱逐(eviction)。

没有命中缓存,就会走 hdfs,查看 datanode 监控有大量的 block read,查看 datanode 磁盘监控,磁盘 I/O 这段时间被打满。 datanode 磁盘

大量的读导致了磁盘瓶颈,读性能更差了

到这里,剩下的就是揪出是谁在进行大 Scan 操作,HBase 上承载的业务众多,一个个问业务不太现实。
于是通过热点 Regionserver 节点的日志看到该时间段,有一些 Scan 的报错,找到表名,拿去一问业务就问出来问题了。(这种方式着实有点原始)。
业务每天定时并发对 7 张 HBase 大表进行全表 Scan,查询导出数据到 Hive。

4 Hive HBase 外部表使用与坑

创建 HBase 的 hive 外部表典型建表语句如下,rowkey 字段为 hbase 的 rowkey,label 为列族:

CREATE EXTERNAL TABLE user_profile.mix_label_scene_tag_test_1(rowkey string COMMENT 'rowkey', label map<string,string> COMMENT 'cf')
ROW FORMAT SERDE 'org.apache.hadoop.hive.hbase.HBaseSerDe'
STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
WITH SERDEPROPERTIES('hbase.columns.mapping'=':key,label:')
TBLPROPERTIES('hbase.table.name'='profile:mix_label_scene_tag_pre_release')

HBase 相关的参数可以直接通过 set 的方式配置进去,例如:

set hbase.client.scanner.timeout.period=600000;

但是这里有一个坑需要注意,默认 hive hbase 外部表 scan 是会缓存 block 的,这个不容易被开发注意到。如果有大量的 scan 类型的批处理、 mr 任务等,会对缓存是一种破坏性的写入。影响其他正常读请求。HBase 官方也有建议,针对批处理类型的 scan 操作,要设置 setCacheBlocks 为 false:

Scan instances can be set to use the block cache in the RegionServer via the setCacheBlocks method. For input Scans to MapReduce jobs, this should be false. https://hbase.apache.org/book.html#perf.hbase.client.blockcache

Hive 社区也有人提了这个问题 HIVE-20484,在 Hive 3 以上的版本进行了修复,将外部表 scan.setCacheBlocks 默认设为 false。hive 这块的代码如下:

public static final String HBASE_SCAN_CACHEBLOCKS = "hbase.scan.cacheblock";

...

String scanCacheBlocks = tableProperties.getProperty(HBaseSerDe.HBASE_SCAN_CACHEBLOCKS);
if (scanCacheBlocks != null) {
  jobProperties.put(HBaseSerDe.HBASE_SCAN_CACHEBLOCKS, scanCacheBlocks);
}

...

String scanCacheBlocks = jobConf.get(HBaseSerDe.HBASE_SCAN_CACHEBLOCKS);
if (scanCacheBlocks != null) {
  scan.setCacheBlocks(Boolean.parseBoolean(scanCacheBlocks));
}

如果没法升级 hive ,解决办法有两个:

  1. 重新创建外部表,SERDEPROPERTIES 里指定: 'hbase.scan.cacheblock'='false',这种方式可以屏蔽 scan 写缓存,又保留正常的 get 读缓存。
CREATE EXTERNAL TABLE user_profile.mix_label_scene_tag(rowkey string COMMENT 'rowkey', label map<string,string> COMMENT 'cf')
ROW FORMAT SERDE 'org.apache.hadoop.hive.hbase.HBaseSerDe'
STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
WITH SERDEPROPERTIES('hbase.columns.mapping'=':key,label:','hbase.scan.cacheblock'='false')
TBLPROPERTIES('hbase.table.name'='profile:mix_label_scene_tag_pre_release')
  1. 修改表的属性,修改列族的缓存策略,这种方式全局屏蔽读缓存,会影响正常的 get 读写缓存
alter 'profile:mix_label_scene_tag', {NAME => 'label', BLOCKCACHE => 'false'}

值得一提的是,setCacheBlocks 是设置:是否把读取的数据写入到缓存中。所以不管怎么设定 setCacheBlocks 的 bool 值,HBase 默认的读请求都会先查缓存。

4.1 Hive HBase 外部表相关参数

附上其他的配置,源码

 public static final String HBASE_COLUMNS_MAPPING = "hbase.columns.mapping";
  public static final String HBASE_TABLE_NAME = "hbase.table.name";
  public static final String HBASE_TABLE_DEFAULT_STORAGE_TYPE = "hbase.table.default.storage.type";
  public static final String HBASE_KEY_COL = ":key";
  public static final String HBASE_TIMESTAMP_COL = ":timestamp";
  public static final String HBASE_PUT_TIMESTAMP = "hbase.put.timestamp";
  public static final String HBASE_COMPOSITE_KEY_CLASS = "hbase.composite.key.class";
  public static final String HBASE_COMPOSITE_KEY_TYPES = "hbase.composite.key.types";
  public static final String HBASE_COMPOSITE_KEY_FACTORY = "hbase.composite.key.factory";
  public static final String HBASE_STRUCT_SERIALIZER_CLASS = "hbase.struct.serialization.class";
  public static final String HBASE_SCAN_CACHE = "hbase.scan.cache";
  public static final String HBASE_SCAN_CACHEBLOCKS = "hbase.scan.cacheblock";
  public static final String HBASE_SCAN_BATCH = "hbase.scan.batch";
  public static final String HBASE_AUTOGENERATE_STRUCT = "hbase.struct.autogenerate";
  /**
   * Determines whether a regex matching should be done on the columns or not. Defaults to true.
   * <strong>WARNING: Note that currently this only supports the suffix wildcard .*</strong>
   */
  public static final String HBASE_COLUMNS_REGEX_MATCHING = "hbase.columns.mapping.regex.matching";
  /**
   * Defines the type for a column.
   **/
  public static final String SERIALIZATION_TYPE = "serialization.type";

  /**
   * Defines if the prefix column from hbase should be hidden.
   * It works only when @HBASE_COLUMNS_REGEX_MATCHING is true.
   * Default value of this parameter is false
   */
  public static final String HBASE_COLUMNS_PREFIX_HIDE = "hbase.columns.mapping.prefix.hide";

5 todo

HBase 怎么提升缓存命中率,除了缓存命中率,还有什么会影响读性能?
HBase 如何监控大 Scan?
HBase 如何进行表级别的监控?
HBase Hive 外部表如何有效监控管理,避免用户随意 scan 操作?

上一篇下一篇

猜你喜欢

热点阅读