HBase bulkload后scan性能问题如何优化

本文探讨了如何优化HBase的bulkload过程,通过提升compaction效率来缓解大规模数据扫描时的CPU负载问题。重点介绍了社区提出的主动compaction方案及其局限,以及一种改进策略,即异步检查并调度compaction任务。

问题简述

Bulk load主要面向需要大批量的向HBase导入数据的场景。这种方式是先生成HBase的底层存储文件 HFile,然后直接将这些 HFile 移动到HBase的存储目录下。它相比调用HBase的put API添加数据,处理效率更快并且对HBase 运行影响更小。
业务使用bulkload+scan的模式非常普遍,往往是一个离线任务bulkload一批文件后,就启动另一个离线任务去读所有bulkload过的数据。普遍存在的问题是,bulk load后scan全表时请求处理慢。
且这种情况同时可能伴随高CPU load,导致整个RS的全部region请求都受影响。

优化方向

bulk load的最后阶段,通过RPC向region导入文件时,由于只是进行非常轻量级的文件移动,所以对于region内部来说,短时间内新增了大量导入的文件。
进行查询时,对于单region来说,需要对其内部所有的文件来进行解压和堆排序。
所以,文件的数量大小,是这里影响性能的关键因素。根据线上现象观察,单Region 上千级别文件的查询,可以引起全表扫描时,cpu busy的百分比显著上升。
HBase减少文件数量的方式只有compaction。所以,提升bulk load过程中的compaction效率,可以缓解scan时的性能问题。
但是注意单次compact的文件数量不能过多,比如我们曾经遇到使用StripeStoreEngine时,单次compact全部L0的文件,而bulkload又是所有文件先进L0,就造成了单个compact任务选中上万文件而长时间无法结束的问题。

社区方案

HBase社区提出了bulk load后主动触发compaction的优化方案,HBASE-25213 Should request Compaction after bulkLoadHFiles is done
但是社区的方案是在每个文件bulk load后,都进行了compaction文件的force select,所以会导致compaction引起的CPU load过高问题。
而且社区之后添加了HBASE-25213功能的开关(HBASE-25630 Set switch compaction after bulkload default as false),来规避CPU load高的问题。

针对社区方案的优化

bulk load后主动检查region是否需要compact,将comapct请求放入队列,等待调度线程根据每次放入队列时检查优先级来判断是否需要立即选择文件进行compact。提到社区的issue:HBASE-26249 Ameliorate compaction made by bulk-loading files

### 使用Spark BulkLoad批量读写HBase的方法 #### 批量写(BulkLoad) 1. **数据准备** 在使用Spark BulkLoadHBase批量写入数据之前,需要将数据处理成HBase可以接受的格式,即 `KeyValue` 或 `Put` 对象。通常会将数据从数据源(如文件、数据库等)读取到Spark中进行处理。 ```python from pyspark.sql import SparkSession from pyspark.sql.functions import col # 创建SparkSession spark = SparkSession.builder \ .appName("SparkBulkLoadToHBase") \ .getOrCreate() # 从CSV文件读取数据 df = spark.read.csv("path/to/your/csv/file.csv", header=True) # 处理数据,转换为(key, value)对,这里假设key是第一列,value是其他列 rdd = df.rdd.map(lambda row: (row[0], {col: row[col] for col in df.columns if col != df.columns[0]})) ``` 2. **数据转换为HBase所需格式** 将处理好的数据转换为 `KeyValue` 或 `Put` 对象,以便后续生成HFile。 ```python import org.apache.hadoop.hbase.client.Put import org.apache.hadoop.hbase.io.ImmutableBytesWritable import org.apache.hadoop.hbase.util.Bytes # 将RDD转换为适合HBase BulkLoad的格式 hbaseRDD = rdd.map(lambda (key, value): ( new ImmutableBytesWritable(Bytes.toBytes(key)), value_to_put(key, value) )) def value_to_put(key, value): put = Put(Bytes.toBytes(key)) for column, val in value.items(): put.addColumn(Bytes.toBytes("cf"), Bytes.toBytes(column), Bytes.toBytes(str(val))) return put ``` 3. **生成HFile** 使用 `HFileOutputFormat2` 生成HFile,HFile是HBase存储数据的底层文件格式。 ```python from org.apache.hadoop.hbase.mapreduce import HFileOutputFormat2 from org.apache.hadoop.hbase.client import ConnectionFactory from org.apache.hadoop.hbase import HBaseConfiguration # 配置HBase conf = HBaseConfiguration.create() conf.set("hbase.zookeeper.quorum", "localhost") conf.set("hbase.zookeeper.property.clientPort", "2181") conf.set("hbase.rootdir", "hdfs://localhost:9000/hbase") # 创建HBase连接 conn = ConnectionFactory.createConnection(conf) table = conn.getTable(TableName.valueOf("your_table_name")) regionLocator = conn.getRegionLocator(TableName.valueOf("your_table_name")) # 配置输出路径 outputPath = "hdfs://localhost:9000/path/to/output/hfile" # 配置HFileOutputFormat2 HFileOutputFormat2.configureIncrementalLoad(hbaseRDD.toJavaRDD(), table, regionLocator) # 保存为HFile hbaseRDD.saveAsNewAPIHadoopFile( outputPath, ImmutableBytesWritable, Put, HFileOutputFormat2, conf ) ``` 4. **执行BulkLoad** 将生成的HFile加载到HBase表中。 ```python from org.apache.hadoop.hbase.mapreduce import LoadIncrementalHFiles # 创建LoadIncrementalHFiles对象 bulkLoader = LoadIncrementalHFiles(conf) # 执行BulkLoad bulkLoader.doBulkLoad(Path(outputPath), table) ``` #### 批量读 1. **配置HBase连接** 与批量写类似,首先需要配置HBase连接。 ```python # 配置HBase conf = HBaseConfiguration.create() conf.set("hbase.zookeeper.quorum", "localhost") conf.set("hbase.zookeeper.property.clientPort", "2181") conf.set("hbase.rootdir", "hdfs://localhost:9000/hbase") ``` 2. **创建HBase表扫描器** 使用 `Scan` 对象指定要扫描的HBase表和列族。 ```python from org.apache.hadoop.hbase.client import Scan from org.apache.hadoop.hbase.filter import FirstKeyOnlyFilter # 创建Scan对象 scan = Scan() scan.addFamily(Bytes.toBytes("cf")) scan.setFilter(FirstKeyOnlyFilter()) ``` 3. **读取数据** 使用 `NewHadoopRDD` 从HBase表中读取数据。 ```python from org.apache.hadoop.hbase.mapreduce import TableInputFormat # 设置表名 conf.set(TableInputFormat.INPUT_TABLE, "your_table_name") # 将Scan对象转换为字节数组并设置到配置中 scanBytes = ProtobufUtil.toScan(scan).toByteArray() conf.set(TableInputFormat.SCAN, Base64.encodeBytes(scanBytes)) # 读取数据 hbaseRDD = spark.sparkContext.newAPIHadoopRDD( conf, TableInputFormat, ImmutableBytesWritable, Result ) # 处理读取的数据 resultRDD = hbaseRDD.map(lambda (key, result): { "rowkey": Bytes.toString(key.get()), "data": { Bytes.toString(columnFamily): { Bytes.toString(qualifier): Bytes.toString(result.getValue(columnFamily, qualifier)) for qualifier in result.getFamilyMap(columnFamily).keySet() } for columnFamily in result.getMap().keySet() } }) ``` ### 相关技术 - **HFile**:HBase的底层存储文件格式,BulkLoad通过生成HFile并直接加载到HBase中,避免了传统写入方式的一些开销,提高了写入性能。 - **RegionServer**:HBase的分布式存储节点,负责存储和管理数据。BulkLoad过程中,HFile会被分发到各个RegionServer上。 - **ZooKeeper**:用于协调HBase集群的元数据管理和节点状态信息,Spark BulkLoad需要通过ZooKeeper找到HBase集群的元数据。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值