用戶畫像 --運用sqoop導入數據 HBase ImportTSV HBase Bulkload MapReduce導入

項目數據導入前提:

整個用戶畫像(UserProfile)項目中,數據、業務及技術流程圖如下所示:

其中數據源存儲在業務系統數據庫:MySQL 數據庫中,採用SQOOP全量/增量將數據抽取到HDFS(Hive表中),通過轉換爲HFile文件加載到HBase表。
    1)、編寫MapReduce程序
    2)、編寫Spark程序(推薦時用Spark編程)

1)、爲什麼將訂單相關數據【訂單數據和訂單商品數據】存儲到HBase表中????
    特點:數據量比較大
    存儲HBase:存儲海量數據、查詢檢索
2)、實際項目來說【訪問行爲日誌】數據存儲到Hive表中
    數據倉庫分層:
        ODS層、DW層和APP層
        
3)、特殊:模擬的所有業務數據存儲在RDBMs表中,爲了簡化整個項目開發,重點在於標籤開發,將所有數據遷移到HBase表中。

 

sql導入數據

注:當數據過多過大時,需要修改設mysql 導入數據允許的最大包大小(僅當前連接有效):

 set global max_allowed_packet=1024*1024*32;

導入數據

source /opt/tags_dat.sql;

sqoop同步mysql表結構到hive

/export/servers/sqoop/bin/sqoop create-hive-table \
--connect jdbc:mysql://bd001:3306/tags_dat \
--table tbl_logs \
--username root \
--password 123456 \
--hive-table tags_dat2.tbl_logs \
--fields-terminated-by '\t' \
--lines-terminated-by '\n'

sqoop同步mysql表數據到hive

/export/servers/sqoop/bin/sqoop import \
--connect jdbc:mysql://bd001:3306/tags_dat \
--username root \
--password 123456 \
--table tbl_logs \
--direct \
--hive-overwrite \
--delete-target-dir \
--fields-terminated-by '\t' \
--lines-terminated-by '\n' \
--hive-table tags_dat2.tbl_logs \
--hive-import \
--num-mappers 20

sqoop同步mysql表數據到hbase

/export/servers/sqoop/bin/sqoop import \
-D sqoop.hbase.add.row.key=true \
--connect jdbc:mysql://bd001:3306/tags_dat \
--username root \
--password 123456 \
--table tbl_users \
--hbase-create-table \
--hbase-table tbl_users2 \
--column-family detail \
--hbase-row-key id \
--num-mappers 2

參數含義解釋:

1、-D sqoop.hbase.add.row.key=true 
    是否將rowkey相關字段寫入列族中,默認爲false,默認情況下你將在列族中看不到任何row key中的字段。注意,該參數必須放在import之後。

2、--hbase-create-table  如果hbase中該表不存在則創建

3、--hbase-table   對應的hbase表名

4、--hbase-row-key   hbase表中的rowkey,注意格式

5、--column-family   hbase表的列族

如何使用sqoop進行增量導入數據至HBase表,範例命令如下:

/export/servers/sqoop/bin/sqoop import \
-D sqoop.hbase.add.row.key=true \
--connect jdbc:mysql://bd001:3306/tags_dat \
--username root \
--password 123456 \
--table tbl_logs \
-- \
--hbase-create-table \
--hbase-table tag_logs \
--column-family detail \
--hbase-row-key id \
--num-mappers 20 \
--incremental lastmodified \
--check-column log_time \
--last-value '2019-08-13 00:00:00' \

相關增量導入參數說明:

1、--incremental lastmodified 增量導入支持兩種模式 append 遞增的列;lastmodified時間戳。

2、--check-column 增量導入時參考的列

3、--last-value 最小值,這個例子中表示導入2019-08-13 00:00:00到今天的值

 

使用SQOOP導入數據到HBase表中,有一個限制:

需要指定RDBMs表中的某個字段作爲HBase表的ROWKEY,如果HBase表的ROWKEY爲多個字段組合,就無法指定,所以此種方式有時候不能使用。(解決方法如下ImportTSV

 

HBase ImportTSV

ImportTSV功能描述:

將tsv(也可以是csv,每行數據中各個字段使用分隔符分割)格式文本數據,加載到HBase表中。
1)、採用Put方式加載導入
2)、採用BulkLoad方式批量加載導入

使用如下命令,查看HBase官方自帶工具類使用說明:

HADOOP_HOME=/export/servers/hadoop
HBASE_HOME=/export/servers/hbase
HADOOP_CLASSPATH=`${HBASE_HOME}/bin/hbase mapredcp`:${HBASE_HOME}/conf
${HADOOP_HOME}/bin/yarn jar ${HBASE_HOME}/lib/hbase-server-1.2.0-cdh5.14.0.jar

執行上述命令提示如下信息:

An example program must be given as the first argument.
Valid program names are:
  CellCounter: Count cells in HBase table.
  WALPlayer: Replay WAL files.
  completebulkload: Complete a bulk data load.
  copytable: Export a table from local cluster to peer cluster.
  export: Write table data to HDFS.
  exportsnapshot: Export the specific snapshot to a given FileSystem.
  import: Import data written by Export.
  importtsv: Import data in TSV format.
  rowcounter: Count rows in HBase table.
  verifyrep: Compare the data from tables in two different clusters. 

詳解:

必須給出一個示例程序作爲第一個參數。

有效的程序名是:

        CellCounter:對HBase表中的單元格進行計數。

        WALPlayer:重放WAL文件。

        completebulkload:完成批量數據加載。

        copytable:將表從本地羣集導出到對等羣集。

        export:將表數據寫入HDFS。

        exportsnapshot:將特定快照導出到給定的文件系統。

        import:導入通過導出寫入的數據。

        importtsv:導入TSV格式的數據。

        rowcounter:計算HBase表中的行數。

        verifyrep:比較兩個不同集羣中表中的數據。

其中importtsv就是將文本文件(比如CSV、TSV等格式)數據導入HBase表工具類,使用說明如下:

Usage: importtsv -Dimporttsv.columns=a,b,c <tablename> <inputdir>

The column names of the TSV data must be specified using the -Dimporttsv.columns
option. This option takes the form of comma-separated column names, where each
column name is either a simple column family, or a columnfamily:qualifier. The special column name HBASE_ROW_KEY is used to designate that this column should be used as the row key for each imported record. 

To instead generate HFiles of data to prepare for a bulk data load, pass the option:
-Dimporttsv.bulk.output=/path/for/output

'-Dimporttsv.separator=|' - eg separate on pipes instead of tabs

For performance consider the following options:
  -Dmapreduce.map.speculative=false
  -Dmapreduce.reduce.speculative=false

 

分別演示採用直接Put方式和HFile文件方式將數據導入HBase表,命令如下:

其一、直接導入Put方式

HADOOP_HOME=/export/servers/hadoop
HBASE_HOME=/export/servers/hbase
HADOOP_CLASSPATH=`${HBASE_HOME}/bin/hbase mapredcp`:${HBASE_HOME}/conf ${HADOOP_HOME}/bin/yarn jar ${HBASE_HOME}/lib/hbase-server-1.2.0-cdh5.14.0.jar \
importtsv \
-Dimporttsv.columns=HBASE_ROW_KEY,detail:log_id,detail:remote_ip,detail:site_global_ticket,detail:site_global_session,detail:global_user_id,detail:cookie_text,detail:user_agent,detail:ref_url,detail:loc_url,detail:log_time \
tbl_logs2 \
/user/hive/warehouse/tags_dat2.db/tbl_logs

上述命令本質上運行一個MapReduce應用程序,將文本文件中每行數據轉換封裝到Put對象,然後插入到HBase表中。

回顧一下:
    採用Put方式向HBase表中插入數據流程:
        Put
            -> WAL 預寫日誌
            -> MemStore(內存) ,當達到一定大寫Spill到磁盤上:StoreFile(HFile)
    思考:
        對海量數據插入,能否將數據直接保存爲HFile文件,然後加載到HBase表中

 

其二、轉換爲HFile文件,再加載至表

# 生成HFILES文件
HADOOP_HOME=/export/servers/hadoop
HBASE_HOME=/export/servers/hbase
HADOOP_CLASSPATH=`${HBASE_HOME}/bin/hbase mapredcp`:${HBASE_HOME}/conf ${HADOOP_HOME}/bin/yarn jar ${HBASE_HOME}/lib/hbase-server-1.2.0-cdh5.14.0.jar \
importtsv \
-Dimporttsv.bulk.output=hdfs://bd001:8020/datas/output_hfile/tbl_tag_logs \
-Dimporttsv.columns=HBASE_ROW_KEY,detail:log_id,detail:remote_ip,detail:site_global_ticket,detail:site_global_session,detail:global_user_id,detail:cookie_text,detail:user_agent,detail:ref_url,detail:loc_url,detail:log_time \
tbl_logs2 \
/user/hive/warehouse/tags_dat2.db/tbl_logs
# 將HFILE文件加載到表中
HADOOP_CLASSPATH=`${HBASE_HOME}/bin/hbase mapredcp`:${HBASE_HOME}/conf ${HADOOP_HOME}/bin/yarn jar \
${HBASE_HOME}/lib/hbase-server-1.2.0-cdh5.14.0.jar \
completebulkload \
hdfs://bd001:8020/datas/output_hfile/tbl_tag_logs \
tbl_logs2

缺點:

1)、ROWKEY不能是組合主鍵
    只能是某一個字段
2)、當表中列很多時,書寫-Dimporttsv.columns值時很麻煩,容易出錯

 

HBase Bulkload

在大量數據需要寫入HBase時,通常有put方式和bulkLoad兩種方式。

1、put方式爲單條插入,在put數據時會先將數據的更新操作信息和數據信息寫入WAL,在寫入到WAL後,數據就會被放到MemStore中,當MemStore滿後數據就會被flush到磁盤(即形成HFile文件),在這種寫操作過程會涉及到flush、split、compaction等操作,容易造成節點不穩定,數據導入慢,耗費資源等問題,在海量數據的導入過程極大的消耗了系統性能,避免這些問題最好的方法就是使用BulkLoad的方式來加載數據到HBase中。

val put = new Put(rowKeyByts)
put.addColumn(cf, column, value)
put.addColumn(cf, column, value)
put.addColumn(cf, column, value)
put.addColumn(cf, column, value)

table.put(put)

2、BulkLoader利用HBase數據按照HFile格式存儲在HDFS的原理,使用MapReduce直接批量生成HFile格式文件後,RegionServers再將HFile文件移動到相應的Region目錄下

1)、Extract,異構數據源數據導入到 HDFS 之上。
2)、Transform,通過用戶代碼,可以是 MR 或者 Spark 任務將數據轉化爲 HFile。
3)、Load,HFile 通過 loadIncrementalHFiles 調用將 HFile 放置到 Region 對應的 HDFS 目錄上,該過程可能涉及到文件切分。

1、不會觸發WAL預寫日誌,當表還沒有數據時進行數據導入不會產生Flush和Split。
2、減少接口調用的消耗,是一種快速寫入的優化方式。
Spark讀寫HBase之使用Spark自帶的API以及使用Bulk Load將大量數據導入HBase。

 

Bulkload過程主要包括三部分:

1、從數據源(通常是文本文件或其他的數據庫)提取數據並上傳到HDFS。
    抽取數據到HDFS。和Hbase並沒有關係,所以大家可以選用自己擅長的方式進行。

2、利用MapReduce作業處理事先準備的數據 。
    這一步需要一個MapReduce作業,並且大多數情況下還需要我們自己編寫Map函數,而Reduce函數不需要我們考慮,由HBase提供。
    該作業需要使用rowkey(行鍵)作爲輸出Key;
    KeyValue、Put或者Delete作爲輸出Value。
    MapReduce作業需要使用HFileOutputFormat2來生成HBase數據文件。
    爲了有效的導入數據,需要配置HFileOutputFormat2使得每一個輸出文件都在一個合適的區域中。爲了達到這個目的,MapReduce作業會使用Hadoop的TotalOrderPartitioner類根據表的key值將輸出分割開來。
    HFileOutputFormat2的方法configureIncrementalLoad()會自動的完成上面的工作。

3、告訴RegionServers數據的位置並導入數據。
    這一步是最簡單的,通常需要使用LoadIncrementalHFiles(更爲人所熟知是completebulkload工具),將文件在HDFS上的位置傳遞給它,它就會利用RegionServer將數據導入到相應的區域。

 

編寫MapReduce導入

將MySQL表的數據先導入到HDFS文件中(比如TSV格式),編寫MapReduce將文本文件數據轉換爲HFile文件,加載到HBase表中。

  • 第一步、Hive中創建表

    /export/servers/sqoop/bin/sqoop create-hive-table \
    --connect jdbc:mysql://bd001:3306/tags_dat \
    --table tbl_logs \
    --username root \
    --password 123456 \
    --hive-table tags_dat2.tbl_logs \
    --fields-terminated-by '\t' \
    --lines-terminated-by '\n'
    

     

  • 第二步、導入MySQL表數據到Hive表

    /export/servers/sqoop/bin/sqoop import \
    --connect jdbc:mysql://bd001:3306/tags_dat \
    --username root \
    --password 123456 \
    --table tbl_logs \
    --direct \
    --hive-overwrite \
    --delete-target-dir \
    --fields-terminated-by '\t' \
    --lines-terminated-by '\n' \
    --hive-table tags_dat2.tbl_logs \
    --hive-import \
    --num-mappers 20

     

  • 第三步、編寫MapReduce導入數據至HBase表

    • 其一、創建HBase 表,設置預分區

    create 'tbl_logs', 'detail', SPLITS => ['49394']
    • 其二、工具類Constants,定義常量值

    package cn.itcast.tags.etl.mr;
    ​
    import org.apache.hadoop.hbase.util.Bytes;
    ​
    import java.util.ArrayList;
    import java.util.List;
    ​
    /**
     * 定義常量
     */
    interface Constants {
        // hive表數據目錄
        String INPUT_PATH = "hdfs://bd001:8020/user/hive/warehouse/tags_dat.db/tbl_logs";
        // 生成的hfile目錄
        String HFILE_PATH = "hdfs://bd001:8020/datas/output_hfile/tbl_logs";
        // 表名
        String TABLE_NAME = "tbl_logs";
        // 列簇名稱
        byte[] COLUMN_FAMILY = Bytes.toBytes("detail");
    ​
        // 表字段
        List<byte[]> list = new ArrayList<byte[]>() {
            private static final long serialVersionUID = -6125158551837044300L;
    ​
            { //
                add(Bytes.toBytes("id"));
                add(Bytes.toBytes("log_id"));
                add(Bytes.toBytes("remote_ip"));
                add(Bytes.toBytes("site_global_ticket"));
                add(Bytes.toBytes("site_global_session"));
                add(Bytes.toBytes("global_user_id"));
                add(Bytes.toBytes("cookie_text"));
                add(Bytes.toBytes("user_agent"));
                add(Bytes.toBytes("ref_url"));
                add(Bytes.toBytes("loc_url"));
                add(Bytes.toBytes("log_time"));
            } //
        };
    ​
    }
    • 其三、MapReduce程序(本地運行)

      使用Java語言,編寫MapReduce程序,讀取Hive表中數據文件,使用HFileOutputFormat2輸出格式,保存數據至HFile文件,再加載到HBase表中。

package cn.itcast.tags.etl.mr;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
import org.apache.hadoop.hbase.mapreduce.HFileOutputFormat2;
import org.apache.hadoop.hbase.mapreduce.LoadIncrementalHFiles;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;

import java.io.IOException;

/**
 * 將Hive表數據轉換爲HFile文件並移動HFile到HBase
 */
public class LoadLogsToHBaseMapReduce
        extends Configured implements Tool {

    // 連接HBase Connection對象
    private static Connection connection = null ;

    /**
     * 定義Mapper類,讀取CSV格式數據,轉換爲Put對象,存儲HBase表
     */
    static class LoadLogsToHBase extends Mapper<LongWritable, Text, ImmutableBytesWritable, Put> {

        @Override
        protected void map(LongWritable key, Text value, Context context)
                throws IOException, InterruptedException {
            // 按照分隔符分割數據,分隔符爲 逗號
            String[] split = value.toString().split("\\t");
            if (split.length == Constants.list.size()) {
                // 構建Put對象,將每行數據轉換爲Put
                Put put = new Put(Bytes.toBytes(split[0]));
                for (int i = 1; i < Constants.list.size(); i++) {
                    put.addColumn(//
                            Constants.COLUMN_FAMILY, //
                            Constants.list.get(i), //
                            Bytes.toBytes(split[i]) //
                    );
                }
                // 將數據輸出
                context.write(new ImmutableBytesWritable(put.getRow()), put);
            }
        }

    }

    @Override
    public int run(String[] args) throws Exception {
        // a. 獲取配置信息對象
        Configuration configuration = super.getConf() ;

        // b. 構建Job對象Job
        Job job = Job.getInstance(configuration);
        job.setJobName(this.getClass().getSimpleName());
        job.setJarByClass(LoadLogsToHBaseMapReduce.class);

        // c. 設置Job
        FileInputFormat.addInputPath(job, new Path(Constants.INPUT_PATH));
        job.setMapperClass(LoadLogsToHBase.class);
        // TODO: 設置輸出格式爲HFileOutputFormat2
        job.setMapOutputKeyClass(ImmutableBytesWritable.class);
        job.setMapOutputValueClass(Put.class);
        job.setOutputFormatClass(HFileOutputFormat2.class);

        // TODO: 判斷輸出目錄是否存在,如果存在就刪除
        FileSystem hdfs = FileSystem.get(configuration) ;
        Path outputPath = new Path(Constants.HFILE_PATH) ;
        if(hdfs.exists(outputPath)){
            hdfs.delete(outputPath, true) ;
        }
        // d. 設置輸出路徑
        FileOutputFormat.setOutputPath(job, outputPath);

        // TODO:獲取HBase Table,對HFileOutputFormat2進行設置
        Table table = connection.getTable(TableName.valueOf(Constants.TABLE_NAME));
        HFileOutputFormat2.configureIncrementalLoad( //
                job, //
                table, //
                connection.getRegionLocator(TableName.valueOf(Constants.TABLE_NAME)) //
        );

        // 提交運行Job,返回是否執行成功
        boolean isSuccess = job.waitForCompletion(true);
        return isSuccess ? 0 : 1;
    }


    public static void main(String[] args) throws Exception {
        // 獲取Configuration對象,讀取配置信息
        Configuration configuration = HBaseConfiguration.create();
        // 獲取HBase 連接Connection對象
        connection = ConnectionFactory.createConnection(configuration);
        
        // 運行MapReduce將數據文件轉換爲HFile文件
        int status = ToolRunner.run(configuration, new LoadLogsToHBaseMapReduce(), args);
        System.out.println("HFile文件生成完畢!~~~");

        // TODO:運行成功時,加載HFile文件數據到HBase表中
        if (0 == status) {
            // 獲取HBase Table句柄
            Admin admin = connection.getAdmin();
            Table table = connection.getTable(TableName.valueOf(Constants.TABLE_NAME));
            // 加載數據到表中
            LoadIncrementalHFiles load = new LoadIncrementalHFiles(configuration);
            load.doBulkLoad(
                    new Path(Constants.HFILE_PATH), //
                    admin, //
                    table, //
                    connection.getRegionLocator(TableName.valueOf(Constants.TABLE_NAME)) //
            );
            System.out.println("HFile文件移動完畢!~~~");
        }
    }

}



 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章