項目數據導入前提:
整個用戶畫像(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文件移動完畢!~~~");
}
}
}