使用BulkLoad 向 HBase 中批量導(dǎo)入數(shù)據(jù)

1 使用 BulkLoad 向 HBase 中批量導(dǎo)入數(shù)據(jù)

2 背景介紹

2.1 概述

我們經(jīng)常面臨向 HBase 中導(dǎo)入大量數(shù)據(jù)的情景。

往 HBase 中批量加載數(shù)據(jù)的方式有很多種,最直接方式是調(diào)用 HBase 的 API 用 put 方法插入數(shù)據(jù);另外一種是用 MapReduce 的方式從 hdfs 上加載數(shù)據(jù),調(diào)用 TableOutputFormat 類在 reduce 中直接生成 put 對(duì)象寫入 HBase(這種方式可以看作多線程的調(diào)用 hbase API 方式);但是這兩種方式效率都不是很高,因?yàn)?HBase 會(huì)頻繁的進(jìn)行 flush、compact、split 操作,需要消耗較大的 CPU 和網(wǎng)絡(luò)資源,并且 region Server 壓力也比較大。

BulkLoad 方式調(diào)用 MapReduce 的 job 直接將數(shù)據(jù)輸出成 HBase table 內(nèi)部的存儲(chǔ)格式的文件 HFile,然后將生成的 StoreFiles 加載到集群的相應(yīng)節(jié)點(diǎn)。這種方式無需進(jìn)行 flush、compact、split 等過程,不占用 region 資源,不會(huì)產(chǎn)生巨量的寫入 I/O,所以需要較少的 CPU 和網(wǎng)絡(luò)資源。在首次數(shù)據(jù)加載時(shí),能極大的提高寫入效率,并降低對(duì) Region Server 節(jié)點(diǎn)的寫入壓力。

2.2 Hbase API 方式數(shù)據(jù)導(dǎo)入流程

了解調(diào)用 Hbase API**方式導(dǎo)入流程之前,我們先來看一張 hbase**的架構(gòu)圖。

220225w7jjubcffppjek9w.png

? Region Server 管理一系列的 region,每個(gè) region 又由多個(gè) Store 組成;

? 每個(gè) Store 對(duì)應(yīng) hbase 的一個(gè)列族(Column Family),每個(gè) Store 又由 MemStore 和 StoreFile 兩部分組成;

? MemStore 是 sorted Memory Buffer,MemStore 滿之后會(huì) flush 到 StoreFile 中,每個(gè) StoreFile 對(duì)應(yīng)一個(gè)實(shí)際的 HFile 文件。

? HLog 記錄每個(gè) RegionServer 的數(shù)據(jù)操作日志,防止 RegionServer 突然宕機(jī)導(dǎo)致 MemStore 中的數(shù)據(jù)丟失,如果 regionServer 突然宕機(jī),HLog 將對(duì)丟失的數(shù)據(jù)進(jìn)行恢復(fù);HLog 中的數(shù)據(jù)會(huì)定期滾動(dòng)更新,刪除已經(jīng)持久化到 StoreFile 的舊文件。

下面看調(diào)用 HBase API**往 Hbase**中插入數(shù)據(jù)的流程。

220225kp3iuxjf13gdqpss.png

? client 端寫入操作實(shí)際上都是 RPC 請(qǐng)求,數(shù)據(jù)傳到 Region Server 中,默認(rèn)首先會(huì)寫入到 WAL(Write Ahead Log)中,也就是 HLog 中,然后才將數(shù)據(jù)寫入到對(duì)應(yīng) region 的 memStore 中,memStore 滿了之后,flush 到 HFile 中,這種情況的 flush 操作會(huì)引起瞬間堵塞用戶的寫操作。

? 當(dāng) StoreFile 數(shù)量達(dá)到一定的閾值,會(huì)觸發(fā) compact 合并操作,將多個(gè) storeFile 合并成一個(gè)大的 StoreFile,這一過程包含大量的硬盤 I/O 操作以及網(wǎng)絡(luò)數(shù)據(jù)通信。單個(gè) StoreFile 過大超過閾值之后會(huì)觸發(fā) region 的 split 操作,并將大的 StoreFile 一分為二。

該方式在大數(shù)據(jù)量寫入時(shí)效率低下(頻繁進(jìn)行 flush,split,compact 耗費(fèi)磁盤 I/O),還會(huì)對(duì)影響 HBase 節(jié)點(diǎn)的穩(wěn)定性造(GC 時(shí)間過長(zhǎng),響應(yīng)變慢,導(dǎo)致節(jié)點(diǎn)超時(shí)退出,并引起一系列連鎖反應(yīng))。

2.3 BulkLoad 導(dǎo)入流程

BulkLoad 涉及兩個(gè)過程:

  1. Transform 階段:使用 MapReduce 將 HDFS 上的數(shù)據(jù)生成成 HBase 的底層 Hfile 數(shù)據(jù)。

  2. Load 階段:根據(jù)生成的目標(biāo) HFile,利用 HBase 提供的 BulkLoad 工具將 HFile Load 到 HBase 的 region 中。

220226uo64rixbpiib27qh.png

在這里需要注意,在 bulkLoading 執(zhí)行之前要提前把數(shù)據(jù)導(dǎo)入到 hdfs 上,因?yàn)?mapreduce 只能讀取 HDFS 上的數(shù)據(jù);如果原始數(shù)據(jù)在 hdfs 上占用 100G 大小的空間,那么 hdfs 上的預(yù)留的空間大小要大于 200G,因?yàn)閿?shù)據(jù)要首先生成 hfile 也是放在 hdfs 臨時(shí)目錄下。

2.4 bulkload 和 put 適合的場(chǎng)景:

? bulkload 適合的場(chǎng)景:

– 大量數(shù)據(jù)一次性加載到 HBase。

– 對(duì)數(shù)據(jù)加載到 HBase 可靠性要求不高,不需要生成 WAL 文件。

– 使用 put 加載大量數(shù)據(jù)到 HBase 速度變慢,且查詢速度變慢時(shí)。

– 加載到 HBase 新生成的單個(gè) HFile 文件大小接近 HDFS block 大小。

? put 適合的場(chǎng)景:

– 每次加載到單個(gè) Region 的數(shù)據(jù)大小小于 HDFS block 大小的一半。

– 數(shù)據(jù)需要實(shí)時(shí)加載。

– 加載數(shù)據(jù)過程不會(huì)造成用戶查詢速度急劇下降。

2.5 Bulkload 批量導(dǎo)入數(shù)據(jù) shell 操作步驟:

1.將數(shù)據(jù)導(dǎo)入到 HDFS 中

2.建表并創(chuàng)建導(dǎo)入模板文件

3.執(zhí)行命令,生成 HFile 文件

4.執(zhí)行命令將 HFile 導(dǎo)入 HBase

3 示例

3.1 場(chǎng)景描述:

陜西省西安市 2018 年全市戶籍總?cè)丝?905.68 萬人,公安系統(tǒng)現(xiàn)在需要把這些人口信息從原來的數(shù)據(jù)存儲(chǔ)庫遷移至 HBase 中。

3.2 原始數(shù)據(jù):

原始數(shù)據(jù) person_information.txt 文件中人口信息按行分別為:

身份證號(hào),姓名,出生年月,性別,戶籍所在區(qū)縣

610122000000001001,陳精業(yè),19940524,男,藍(lán)田縣  
610102000000001002,王軍林,19870402,男,新城區(qū)  
610111000000001003,田心亞,19681103,女,灞橋區(qū)  
610125000000001004,王朝輝,19970608,男,鄠邑區(qū)  
610112000000001005,馮詩雨,19900801,女,未央?yún)^(qū)  
610112000000001006,黃秋野,19990505,男,未央?yún)^(qū)  
610122000000001007,彭云超,20011205,男,藍(lán)田縣  
610116000000001008,尤麗雯,19981123,女,長(zhǎng)安區(qū)  
610104000000001009,龔小剛,20050817,男,蓮湖區(qū)  
610124000000001010,蘭春元,19870609,女,周至縣  
610115000000001011,王蘇濤,19881107,男,臨潼區(qū)  
610114000000001012,周東來,19761028,男,閻良區(qū)  
610103000000001013,邱維堅(jiān),19770929,男,碑林區(qū)  
610116000000001014,卓鵬宇,19730726,男,長(zhǎng)安區(qū)  
610104000000001015,尤麗雯,19690317,女,蓮湖區(qū)  
610102000000001016,張雪紅,19820109,女,新城區(qū)  
610104000000001017,趙靜靜,19660527,女,蓮湖區(qū)  
610124000000001018,曹笑天,19980616,女,周至縣  
610112000000001019,李曉萍,19851114,女,未央?yún)^(qū)  
610115000000001020,牛紅藝,19930520,女,臨潼區(qū)

3.3 shell 操作步驟 :

3.3.1 1.將數(shù)據(jù)導(dǎo)入到 HDFS 中

HBase 不管理數(shù)據(jù)提取這部分過程。

通常需要導(dǎo)入的外部數(shù)據(jù)都是存儲(chǔ)在其它的關(guān)系型數(shù)據(jù)庫或一些文本文件中,我們需要將數(shù)據(jù)提取出來并放置于 HDFS 中。也借助 ETL 工具可以解決大多數(shù)關(guān)系型數(shù)據(jù)庫向 HDFS 遷移數(shù)據(jù)的問題。

例如,我們可以在一個(gè)表上運(yùn)行 mysqldump(mysql 數(shù)據(jù)庫中備份工具,用于將 MySQL 服務(wù)器中的數(shù)據(jù)庫以標(biāo)準(zhǔn)的 sql 語言的方式導(dǎo)出保存到文件中。)并將結(jié)果文件上傳到 HDFS。

執(zhí)行命令:

  • 在 HDFS 上創(chuàng)建一個(gè)目錄/testBulkload
hdfs dfs -mkdir /testBulkload  
  • 把 linux 本地/opt 目錄下 person_information.txt 文件傳到 HDFS 上/testBulkload 目錄下
hdfs dfs -put /opt/person_information.txt /testBulkload   
  • 可使用下面命令查看一下
hdfs dfs -cat /testBulkload/person_information.txt

結(jié)果展示

上傳源數(shù)據(jù)文件:

220226kll4er5hdqim4qzn.png

3.3.2 2.建表并自定義導(dǎo)入模板文件

建表:

需要根據(jù)導(dǎo)入數(shù)據(jù),設(shè)計(jì)好 HBase 數(shù)據(jù)表的表名、rowkey、列族、列,考慮好 row key 分配在創(chuàng)建表時(shí)進(jìn)行預(yù)分割。

根據(jù)源數(shù)據(jù)和業(yè)務(wù)設(shè)計(jì)表,在 hbase shell 下執(zhí)行命令:

  • person_information_table:表名
  • NAME => 'base':列族名稱。
  • COMPRESSION:壓縮方式
  • DATA_BLOCK_ENCODING:編碼算法
  • SPLITS:預(yù)分 region
create  'person_information_table', {NAME => 'base',COMPRESSION => 'SNAPPY',  DATA_BLOCK_ENCODING => 'FAST_DIFF'},SPLITS => \['1','2','3','4','5','6','7','8'\]

建表結(jié)果展示:

建表成功:

220227ywmr5m92z9wrm292.png

模板文件示例說明:

模板文件可以參考“${client path}/HBase/hbase/conf/index_import.xml.template”文件進(jìn)行編輯。

模板文件示例說明:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    
    <!-- column_nmu: 要和源數(shù)據(jù)文件中的列的數(shù)量對(duì)應(yīng) -->
    <!-- id: 無關(guān)緊要,只是一個(gè)ID標(biāo)識(shí),可不修改 -->
    <import id="first" column_num="4">
       <!-- index: 原始數(shù)據(jù)中的列坐標(biāo) SMS_ADDRESS -->
       <!-- type: 列數(shù)據(jù)類型 -->
        <!-- SMS_ADDRESS: 原始數(shù)據(jù)列臨時(shí)字段名 -->
       <columns> 
           <column index="1" type="int">SMS_ID</column> 
           <column index="2" type="string">SMS_NAME</column>
           <column index="3" type="boolean">SMS_ADDRESS</column>
           <column index="4" type="long">SMS_SERIAL</column>
       </columns>
 
        <!--根據(jù)自己數(shù)據(jù)和業(yè)務(wù)設(shè)計(jì)rowkey  --> 
       <rowkey>
           SMS_ID+'_'+substring(SMS_NAME,1,4)+'_'+reverse(SMS_SERIAL)
       </rowkey>
 
       <!-- 定義HTable列 -->
       <!-- family: 列族columns family -->
       <!-- column: 對(duì)應(yīng)于原始數(shù)據(jù)中的列 -->
       <qualifiers>
            <!-- column: 定義普通列 -->
           <normal family="NF">
                <!-- H_ID: HBase表列的字段名 -->
              <qualifier column="SMS_ID">H_ID</qualifier>
              <qualifier column="SMS_NAME">H_NAME</qualifier>
              <qualifier column="SMS_ADDRESS">H_ADDRESS</qualifier>
              <qualifier column="SMS_SERIAL">H_SERIAL</qualifier>
           </normal>
 
           <!-- 定義組合列   (可選)  -->
           <composite family="CF1">
              <!-- H_COMBINE_1:組合列名 -->
              <qualifier class="com.huawei.H_COMBINE_1">H_COMBINE_1</qualifier>
              <columns>
                    <!-- 用如下三個(gè)列去組合 -->
                  <column>SMS_SERIAL</column>
                  <column>SMS_ADDRESS</column>
                  <column>SMS_NAME</column>
              </columns>
           </composite>
       </qualifiers>
        
       <!-- 定義二級(jí)索引  (可選) -->
       <indices>
           <index name="IDX1">
              <index_column family="NF">
              <!-- 支持的類型只有String,Int,F(xiàn)loat,Long,Double,Short,Byte,Char  -->
              <!-- 不支持將復(fù)合列定義為索引 -->
                <!-- 索引類型的首字母需要大寫,例如“type”=“String” -->
                  <qualifier type="String" length="10">H_NAME</qualifier>
                  <qualifier type="String" length="20">H_ADDRESS</qualifier>
              </index_column>
           </index>
           <index name="IDX2">
              <index_column family="CF2">
                    <!-- “l(fā)ength="30"”指索引列“H_SERIAL”的列值不能超過30個(gè)字符。-->
                  <qualifier type="String" length="30">H_SERIAL</qualifier>
              </index_column>
           </index>
       </indices>
 
       <!-- 定義要過濾的數(shù)據(jù)行規(guī)則 (可選)  -->
        <badlines>SMS_ID &lt; 7000 &amp;&amp; SMS_NAME == 'HBase'</badlines>
    </import>
</configuration>

注意:

因 bulkload 本身的限制,自定義時(shí)關(guān)注如下幾點(diǎn):

? 列的名稱不能包含特殊字符,只能由字母、數(shù)字和下劃線組成。

? 當(dāng)將列的類型設(shè)置為 string 時(shí),不能設(shè)置其長(zhǎng)度。例如“<column index="1" type="string" length="1" >COLOUMN_1”,此類型不支持。

? 當(dāng)將列的類型設(shè)置為 date 時(shí),不能設(shè)置其日期格式。例如“<column index="13" type="date" format="yyyy-MM-dd hh:mm:ss">COLOUMN_13”,此類型不支持。

? 不能針對(duì)組合列建立二級(jí)索引。

根據(jù)業(yè)務(wù)編輯模板文件:

編輯好后放到/opt 目錄下

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <!--column_num要和數(shù)據(jù)文件(person_information.txt)中的列的數(shù)量對(duì)應(yīng):5列-->
    <import id="first" column_num="5">
        <columns>
            <column index="1" type="string">P_ID</column>
            <column index="2" type="string">P_NAME</column>
            <column index="3" type="string">P_BIRTH</column>
            <column index="4" type="string">P_GENDER</column>
            <column index="5" type="string">P_DISTRICT</column>
        </columns>

        <!--reverse(P_BIRTH):反轉(zhuǎn)出生年月避免熱點(diǎn)-->
        <!--substring(P_NAME,0,1):截取姓  -->
        <!--substring(P_ID,0,6):截身份證前六位  -->
        <rowkey>
            reverse(P_BIRTH)+'_'+substring(P_NAME,0,1)+'_'+substring(P_ID,0,6)
        </rowkey>

        <qualifiers>
            <!--family的指定要和表的列族名稱對(duì)應(yīng)。-->
            <normal family="base">
                <qualifier column="P_ID">H_ID</qualifier>
                <qualifier column="P_NAME">H_NAME</qualifier>
                <qualifier column="P_BIRTH">H_BIRTH</qualifier>
                <qualifier column="P_GENDER">H_GENDER</qualifier>
                <qualifier column="P_DISTRICT">H_DISTRICT</qualifier>
            </normal>

        </qualifiers>
    </import>
</configuration>

3.3.3 3.執(zhí)行如下命令,生成 HFile 文件。

命令示例:

hbase com.huawei.hadoop.hbase.tools.bulkload.IndexImportData -Dimport.skip.bad.lines=true -Dimport.separator=<separator> -Dimport.bad.lines.output=</path/badlines/output> -Dimport.hfile.output=</path/for/output> <configuration xmlfile> <tablename> <inputdir>

? -Dimport.separator:分隔符。例如,-Dimport.separator=','

? -Dimport.skip.bad.lines:指定值為 false,表示遇到不適用的行則停止執(zhí)行。指定值為 true,表示遇到不適用的數(shù)據(jù)行則跳過該行繼續(xù)執(zhí)行,如果沒有在 configuration.xml 中定義不適用行,該參數(shù)不需要添加。

? -Dimport.bad.lines.output=</path/badlines/output>:指的是不適用的數(shù)據(jù)行輸出路徑,如果沒有在 configuration.xml 中定義不適用行,該參數(shù)不需要添加。

? -Dimport.hfile.output=< /path/for/output>:指的是執(zhí)行結(jié)果輸出路徑。

? < configuration xmlfile>:指向 configuration 配置文件。

? < tablename>:指的是要操作的表名。

? < inputdir>:指的是要批量上傳的數(shù)據(jù)目錄。

根據(jù)場(chǎng)景業(yè)務(wù)執(zhí)行命令:

hbase com.huawei.hadoop.hbase.tools.bulkload.IndexImportData  -Dimport.separator=',' -Dimport.hfile.output=/testBulkload/hfile /opt/configuration_index.xml person_information_table /testBulkload/person_information.txt

結(jié)果展示:

base 列族數(shù)據(jù)生成 hfile

220227okw33f65kuozi6uf.png

3.3.4 4.執(zhí)行如下命令將 HFile 導(dǎo)入 HBase。

執(zhí)行命令:

  • /testBulkload/hfile :hfile 在 HDFS 上的位置
  • person_information_table : hbase 表名
hbase org.apache.hadoop.hbase.mapreduce.LoadIncrementalHFiles /testBulkload/hfile person_information_table

結(jié)果展示:

表中部分?jǐn)?shù)據(jù):

220228b4h89xxrv19hxq8x.png

4 異常處理

4.1 1、使用 HBase bulkload 導(dǎo)入數(shù)據(jù)成功,執(zhí)行相同的查詢時(shí)卻可能返回不同的結(jié)果

問題

在使用 HBase bulkload 導(dǎo)入數(shù)據(jù)時(shí),如果導(dǎo)入的數(shù)據(jù)存在相同的 rowkey 值,數(shù)據(jù)可以導(dǎo)入成功,但是執(zhí)行相同的查詢時(shí)可能返回不同的結(jié)果。

回答

正常情況下,相同 rowkey 值的數(shù)據(jù)加載到 HBase 是有先后順序的,HBase 以最近的時(shí)間戳的數(shù)據(jù)為最新數(shù)據(jù),一般的默認(rèn)查詢中,沒有指定時(shí)間戳的,就會(huì)對(duì)相同 rowkey 值的數(shù)據(jù)僅返回最新數(shù)據(jù)。

使用 bulkload 加載數(shù)據(jù),由于數(shù)據(jù)在內(nèi)存中處理生成 HFile,速度是很快的,很可能出現(xiàn)相同 rowkey 值的數(shù)據(jù)具有相同時(shí)間戳,從而造成查詢結(jié)果混亂的情況。

建議在建表和數(shù)據(jù)加載時(shí),設(shè)計(jì)好 rowkey 值,盡量避免在同一個(gè)數(shù)據(jù)文件中存在相同 rowkey 值的情況。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容