
# 批量加载HBase数据并生成本地二级索引
#### 操作场景
在HBase数据导入场景中，当需要将大量结构化数据（如CSV文件、日志文件）批量导入HBase时，传统的Put方式逐条写入效率低下，无法满足大规模数据导入的性能要求。同时，如果业务需要对非RowKey列进行高效查询，还需要在数据导入时同步生成本地二级索引。如何在保证数据导入性能的同时，自动完成索引数据的生成？HIndexImportTsv工具正是为解决这一问题而设计的。该工具继承了HBase批量加载数据工具ImportTsv的所有功能，支持将HDFS中的结构化数据批量转换为HFile格式并加载到HBase表中，同时在数据导入过程中自动生成本地二级索引数据，实现数据导入与索引构建的一体化操作，大幅提升大规模数据导入的效率。本文介绍如何使用HIndexImportTsv工具批量加载HBase数据并生成本地二级索引。
HBase本身提供了ImportTsv＆LoadIncremental工具来批量加载用户数据。当前提供了HIndexImportTsv来支持加载用户数据的同时可以完成对索引数据的批量加载。HIndexImportTsv继承了HBase批量加载数据工具ImportTsv的所有功能。此外，如果在执行HIndexImportTsv工具之前未建表，直接运行该工具，将会在创建表时创建索引，并在生成用户数据的同时生成索引数据。
- **本地二级索引（Local Secondary Index，LSI）**：HBase提供的索引功能，索引数据与主表数据存储在同一Region中，索引的RowKey包含主表的RowKey。本地二级索引适合读多写少的场景，查询时可以直接从索引定位到主表数据。
- **BulkLoad**：HBase的批量数据加载机制，通过将数据预先转换为HFile格式（HBase的底层存储格式），然后直接将HFile文件移动到HBase的存储目录中，避免通过Write Ahead Log和MemStore的写入路径，大幅提升数据导入性能。
- **HFile**：HBase的底层存储文件格式，采用LSM-Tree结构，数据按RowKey有序排列。HFile支持压缩、编码等优化，是HBase高效读写的基础。
 
#### 前提条件
- 已安装客户端，具体请参考[安装客户端](https://support.huaweicloud.com/usermanual-mrs/mrs_01_0089.html)章节。
- 已根据业务需要创建具有相应权限的组件业务用户。"机机"用户需要下载keytab文件，"人机"用户第一次登录时需修改密码。
 
#### 使用HIndexImportTsv批量生成HBase本地二级索引数据
1. 以客户端安装用户登录安装了客户端的节点。
2. 执行以下命令配置环境变量并认证用户： 切换至客户端安装目录：
   ```
   cd 客户端安装目录
   ```
   配置环境变量：
   ```
   source bigdata_env
   ```
   认证用户，集群未启用Kerberos认证（普通模式）请跳过该操作：
   ```
   kinit 组件业务用户
   ```
   
3. 将数据导入到HDFS中。 创建HDFS目录：
   ```
   hdfs dfs -mkdir <inputdir>
   ```
   将数据文件上传至HDFS中：
   ```
   hdfs dfs -put <local_data_file> <inputdir>
   ```
   例如定义数据文件"data.txt"，内容如下：
   ```
   12005000201,Zhang San,Male,19,City a, Province a
   12005000202,Li Wanting,Female,23,City b, Province b
   12005000203,Wang Ming,Male,26,City c, Province c
   12005000204,Li Gang,Male,18,City d, Province d
   12005000205,Zhao Enru,Female,21,City e, Province e
   12005000206,Chen Long,Male,32,City f, Province f
   12005000207,Zhou Wei,Female,29,City g, Province g
   12005000208,Yang Yiwen,Female,30,City h, Province h
   12005000209,Xu Bing,Male,26,City i, Province i
   12005000210,Xiao Kai,Male,25,City j, Province j
   ```
   执行以下命令上传"data.txt"文件：
   创建"datadirImport"目录：
   ```
   hdfs dfs -mkdir /datadirImport
   ```
   上传"data.txt"文件至HDFS的"datadirImport"目录中：
   ```
   hdfs dfs -put data.txt /datadirImport
   ```
   
4. 执行以下命令创建表**bulkTable** ：
   1. 登录HBase客户端：
      ```
      hbase shell
      ```
      
   
   2. 创建表**bulkTable** ：
      ```
      create 'bulkTable', {NAME => 'info',COMPRESSION => 'SNAPPY', DATA_BLOCK_ENCODING => 'FAST_DIFF'},{NAME=>'address'}
      ```
      
   
   3. 命令执行完成后，再执行以下命令退出**hbase shell** ：
      ```
      !quit
      ```
      
    
5. 执行如下命令，生成HFile文件（StoreFiles）：
   ```
   hbase org.apache.hadoop.hbase.hindex.mapreduce.HIndexImportTsv -Dimporttsv.separator=<separator> -Dimporttsv.bulk.output=</path/for/output> -Dindexspecs.to.add=<indexspecs>  -Dimporttsv.columns=<columns> tableName <inputdir>
   ```
   表1参数介绍 
   | 参数                   | 参数说明                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
   |:---|:---|
   | -Dimport.separator   | 该参数用于指定数据文件中字段间的分隔符。例如-Dimporttsv.separator=','表示字段间以逗号分隔。                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
   | -Dimport.bulk.output | 该参数用于指定HFile文件的输出路径，需指定一个不存在的HDFS路径。生成的HFile文件将保存在该路径下。                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
   | -Dindexspecs.to.add  | 表示索引名与列的映射，例如**-Dindexspecs.to.add=** *'index_bulk=\>info:\[age-\>String\]'*。 其构成如下所示： *indexNameN* =\>*familyN* :\[*columnQualifierN* -\> *columnQualifierDataType* \], \[*columnQualifierM* -\> *columnQualifierDataType* \];*familyM* : \[*columnQualifierO* -\> *columnQualifierDataType* \]# *indexNameN* =\> *familyM* : \[*columnQualifierO* -\> *columnQualifierDataType*\] 其中： - 列限定符用逗号（,）分隔，例如： index1 =\> f1:\[c1-\> String\], \[c2-\> String\]   - 列族用分号（;）分隔，例如： index1 =\> f1:\[c1-\> String\], \[c2-\> String\]; f2:\[c3-\> Long\]   - 多个索引用**#** 号分隔，例如： index1 =\> f1:\[c1-\> String\], \[c2-\> String\]; f2:\[c3-\> Long\]#index2 =\> f2:\[c3-\> Long\]   - 列限定的数据类型 支持的数据类型包括STRING，INTEGER，FLOAT，LONG，DOUBLE，SHORT，BYTE，CHAR。   - 数据类型也可以用小写传递。   |
   | -Dimporttsv.columns  | 该参数用于指定导入数据在表中的列映射关系。格式为：HBASE_ROW_KEY,列族:列限定符,列族:列限定符...。 例如-Dimporttsv.columns=HBASE_ROW_KEY,info:name,info:gender,info:age,address:city,address:province表示第一列作为RowKey，后续列分别映射到info和address列族。                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
   | *tableName*          | 该参数用于指定要操作的HBase表名称。                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
   | *\<inputdir\>*       | 该参数用于指定HDFS中待导入数据的目录路径。                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
      
   例如执行以下命令：
   ```
   hbase org.apache.hadoop.hbase.hindex.mapreduce.HIndexImportTsv -Dimporttsv.separator=',' -Dimporttsv.bulk.output=/dataOutput -Dindexspecs.to.add='index_bulk=>info:[age->String]' -Dimporttsv.columns=HBASE_ROW_KEY,info:name,info:gender,info:age,address:city,address:province bulkTable /datadirImport/data.txt
   ```
   命令执行后，输出以下内容：
   ```
   2018-05-08 21:29:16,059 INFO  [main] mapreduce.HFileOutputFormat2: Incremental table bulkTable output configured.
   2018-05-08 21:29:16,069 INFO  [main] client.ConnectionManager$HConnectionImplementation: Closing master protocol: MasterService
   2018-05-08 21:29:16,069 INFO  [main] client.ConnectionManager$HConnectionImplementation: Closing zookeeper sessionid=0x80007c2cb4fd5b4d
   2018-05-08 21:29:16,072 INFO  [main] zookeeper.ZooKeeper: Session: 0x80007c2cb4fd5b4d closed
   2018-05-08 21:29:16,072 INFO  [main-EventThread] zookeeper.ClientCnxn: EventThread shut down for session: 0x80007c2cb4fd5b4d
   2018-05-08 21:29:16,379 INFO  [main] client.ConfiguredRMFailoverProxyProvider: Failing over to 147
   2018-05-08 21:29:17,328 INFO  [main] input.FileInputFormat: Total input files to process : 1
   2018-05-08 21:29:17,413 INFO  [main] mapreduce.JobSubmitter: number of splits:1
   2018-05-08 21:29:17,430 INFO  [main] Configuration.deprecation: io.bytes.per.checksum is deprecated. Instead, use dfs.bytes-per-checksum
   2018-05-08 21:29:17,687 INFO  [main] mapreduce.JobSubmitter: Submitting tokens for job: job_1525338489458_0002
   2018-05-08 21:29:18,100 INFO  [main] impl.YarnClientImpl: Submitted application application_1525338489458_0002
   2018-05-08 21:29:18,136 INFO  [main] mapreduce.Job: The url to track the job: http://shap000000407:8088/proxy/application_1525338489458_0002/
   2018-05-08 21:29:18,136 INFO  [main] mapreduce.Job: Running job: job_1525338489458_0002
   2018-05-08 21:29:28,248 INFO  [main] mapreduce.Job: Job job_1525338489458_0002 running in uber mode : false
   2018-05-08 21:29:28,249 INFO  [main] mapreduce.Job:  map 0% reduce 0%
   2018-05-08 21:29:38,344 INFO  [main] mapreduce.Job:  map 100% reduce 0%
   2018-05-08 21:29:51,421 INFO  [main] mapreduce.Job:  map 100% reduce 100%
   2018-05-08 21:29:51,428 INFO  [main] mapreduce.Job: Job job_1525338489458_0002 completed successfully
   2018-05-08 21:29:51,523 INFO  [main] mapreduce.Job: Counters: 50
   ```
   
6. 执行如下命令将生成的HFile导入HBase中：
   ```
   hbase org.apache.hadoop.hbase.mapreduce.LoadIncrementalHFiles </path/for/output> <tablename>
   ```
   例如执行以下命令：
   ```
   hbase org.apache.hadoop.hbase.mapreduce.LoadIncrementalHFiles /dataOutput bulkTable
   ```
   命令执行后，输出以下内容：
   ```
   2018-05-08 21:30:01,398 WARN  [main] mapreduce.LoadIncrementalHFiles: Skipping non-directory hdfs://hacluster/dataOutput/_SUCCESS
   2018-05-08 21:30:02,006 INFO  [LoadIncrementalHFiles-0] hfile.CacheConfig: Created cacheConfig: CacheConfig:disabled
   2018-05-08 21:30:02,006 INFO  [LoadIncrementalHFiles-2] hfile.CacheConfig: Created cacheConfig: CacheConfig:disabled
   2018-05-08 21:30:02,006 INFO  [LoadIncrementalHFiles-1] hfile.CacheConfig: Created cacheConfig: CacheConfig:disabled
   2018-05-08 21:30:02,085 INFO  [LoadIncrementalHFiles-2] compress.CodecPool: Got brand-new decompressor [.snappy]
   2018-05-08 21:30:02,120 INFO  [LoadIncrementalHFiles-0] mapreduce.LoadIncrementalHFiles: Trying to load hfile=hdfs://hacluster/dataOutput/address/042426c252f74e859858c7877b95e510 first=12005000201 last=12005000210
   2018-05-08 21:30:02,120 INFO  [LoadIncrementalHFiles-2] mapreduce.LoadIncrementalHFiles: Trying to load hfile=hdfs://hacluster/dataOutput/info/f3995920ae0247a88182f637aa031c49 first=12005000201 last=12005000210
   2018-05-08 21:30:02,128 INFO  [LoadIncrementalHFiles-1] mapreduce.LoadIncrementalHFiles: Trying to load hfile=hdfs://hacluster/dataOutput/d/c53b252248af42779f29442ab84f86b8 first=\x00index_bulk\x00\x00\x00\x00\x00\x00\x00\x0018\x00\x0012005000204 last=\x00index_bulk\x00\x00\x00\x00\x00\x00\x00\x0032\x00\x0012005000206
   2018-05-08 21:30:02,231 INFO  [main] client.ConnectionManager$HConnectionImplementation: Closing master protocol: MasterService
   2018-05-08 21:30:02,231 INFO  [main] client.ConnectionManager$HConnectionImplementation: Closing zookeeper sessionid=0x81007c2cf0f55cc5
   2018-05-08 21:30:02,235 INFO  [main] zookeeper.ZooKeeper: Session: 0x81007c2cf0f55cc5 closed
   2018-05-08 21:30:02,235 INFO  [main-EventThread] zookeeper.ClientCnxn: EventThread shut down for session: 0x81007c2cf0f55cc5
   ```
   
**验证操作：**
数据加载完成后，可通过以下方式验证数据和索引是否成功导入：
1. 在hbase shell中执行scan命令查看表数据：
   ```
   scan 'bulkTable'
   ```
   
2. 执行查询语句验证索引是否生效（如果创建了基于age列的索引）。 使用索引列进行查询，如果查询能够快速返回，说明索引已生效。
   
3. 在HDFS中检查输出目录，确认HFile文件已生成：
   ```
   hdfs dfs -ls /dataOutput
   ```
   
 
#### 常见问题
- **HIndexImportTsv任务执行失败**
  HIndexImportTsv任务执行失败的常见原因包括：HDFS输入路径不存在或无读取权限、输出路径已存在（需指定不存在的路径）、表不存在且未指定建表参数、数据文件格式与列映射定义不匹配、Yarn集群资源不足导致MapReduce任务无法提交等。
  
- **LoadIncrementalHFiles加载HFile失败**
  LoadIncrementalHFiles加载HFile失败的常见原因包括：HFile输出路径不存在或为空、HFile格式不正确、表结构发生变化导致HFile与表不兼容、Region正在分裂或合并导致加载冲突、用户没有表的写入权限等。
  
 
#### 相关文档
- 了解HBase本地二级索引的原理和特性，请参考[HBase本地二级索引介绍](https://support.huaweicloud.com/cmpntguide-lts-mrs/mrs_01_0506.html)。
- 了解如何使用TableIndexer工具生成HBase本地二级索引，请参考[使用TableIndexer工具生成HBase本地二级索引](https://support.huaweicloud.com/cmpntguide-lts-mrs/mrs_01_0508.html)。
- 了解HBase数据操作，请参考[HBase数据操作](https://support.huaweicloud.com/cmpntguide-lts-mrs/mrs_01_300249.html)。
 
