用户画像 --运用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文件移动完毕!~~~");
        }
    }

}



猜你喜欢

转载自blog.csdn.net/weixin_44036154/article/details/106472829