项目数据导入前提:
整个用户画像(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文件移动完毕!~~~");
}
}
}