最近实习项目需要使用到kylin建立数据立方体,kylin是一个基于hive的OLAP系统,主要功能是可以通过预计算的方式将存储在hdfs上的文件设定为多维立方体缓存到HBase中,将需要的查询结果作为数据立方体预先存储在hbase中,是一种以空间换时间的快速查询方法。
甲方公司目前使用的是mysql存储数据,并且数据会定期删除。目前需要考虑使用工具将mysql中的数据导入到Hive中,同时考虑到数据定期删除,还需要设置定时任务以及增量导入等问题。
sqoop是一款apache旗下的“hadoop和关系型数据库数据传输”工具。主要功能涉及mysql,Oracle数据导入到HDFS、Hive、Hbase等数据存储系统。简单来说,是一个命令行工具,使用java语言编写,源码使用ant编译。
本质上,sqoop是将sql查询结果转换为MR程序存储至HDFS。
1、官网下载sqoop
http://sqoop.apache.org/
目前只有sqoop1 版本支持直接将mysql、Oracle数据导入hive。
sqoop2中仅支持将关系型数据库中的数据上传至hdfs,再由hive等命令将hdfs上的文件load进入hive中。
所以目前我们仍然使用旧版本的sqoop
2、解压后修改sqoop-env.sh文件
export HADOOP_COMMON_HOME=/home/hadoop/apps/hadoop-2.7.5
#Set path to where hadoop-*-core.jar is available
export HADOOP_MAPRED_HOME=/home/hadoop/apps/hadoop-2.7.5
#set the path to where bin/hbase is available
export HBASE_HOME=/home/hadoop/apps/hbase-1.2.6
#Set the path to where bin/hive is available
export HIVE_HOME=/home/hadoop/apps/apache-hive-2.3.3-bin
#Set the path for where zookeper config dir is
export ZOOCFGDIR=/home/hadoop/apps/zookeeper-3.4.10/conf
3、sqoop/lib目录下加入mysql驱动包
这里我们使用mysql5.1-connect-jdbc.jar
4、配置系统环境变量
#Sqoop
export SQOOP_HOME=/home/hadoop/apps/sqoop-1.4.7
export PATH=$PATH:$SQOOP_HOME/bin
由于我需要使用sqoop将mysql的数据导入到hive中,所以我主要使用的命令为sqoop -import
sqoop import \
--connect jdbc:mysql://hadoop-master:3306/mysql-hive \
--username root \
--password gzudb604 \
--table mysql_hive_test \
--fields-terminated-by "\t" \
--lines-terminated-by "\n" \
--hive-import --hive-partition-key DATA_DATE --hive-partition-value 2018-04-12\
--hive-overwrite \
--create-hive-table \
--delete-target-dir \
--hive-database adm \
--hive-table MYSQL_HIVE_TEST_2 \
-m 1
若已创建好hive表只需要将create-hive-table一行去掉即可
将分区变量和值使用逗号分隔符分开
sqoop import \
--connect jdbc:mysql://hadoop-master:3306/mysql-hive \
--username root \
--password gzudb604 \
--table mysql_hive_test \
--fields-terminated-by "\t" \
--lines-terminated-by "\n" \
--hive-import --hive-partition-key DATA_DATE,DATA_SRC_ORG \
--hive-partition-value 2018-04-12,pub \
--hive-overwrite \
--create-hive-table \
--delete-target-dir \
--hive-database adm \
--hive-table MYSQL_HIVE_TEST_mut \
-m 1
增量导入需要使用一个自增id键或一个日期字段来记录每一次最后导入数据的时间
sqoop import
–connect jdbc:mysql://192.168.xxx.xxx:3316/testdb
–username root
–password 123456
–query “select order_id, name from order_table where $CONDITIONS”
–target-dir /user/root/orders_all \
–split-by order_id
-m 6
–incremental append
–check-column order_id
–last-value 5201314
–incremental append 基于递增列的增量导入(将递增列值大于阈值的所有数据增量导入Hadoop)
–check-column 递增列(int)
–last-value 阈值(int)
sqoop import
–connect jdbc:mysql://192.168.xxx.xxx:3316/testdb
–username root
–password transwarp
–query “select order_id, name from order_table where $CONDITIONS”
–target-dir /user/root/order_all \
–split-by id
-m 4
–incremental lastmodified
–merge-key order_id
–check-column time
–last-value “2014-11-09 21:00:00”
将sqoop/lib下的hsqldb-1.8.0.10.jar删除,解决问题
hive 最后移动数据的时候,需要调用hadoop-distcp-X.X.X.jar,
解决方法:只需要把$HADOOP_HOME/share/hadoop/tools/lib/hadoop-distcp-x.x.x.jar 拷贝 $HIVE_HOME/lib下面,重启hive即可
此处有一个惊天大BUG,由于需要对存储在hive中的数据进行分区,并且分区字段为两个,而sqoop仅仅只支持单维度分区。即上文使用的多维度分区命令是无法使用的(修改源码后可以使用)。
解决上述问题有两个思路,
1-使用sqoop命令将mysql数据上传至hdfs,再使用hive的load命令带分区地把数据存入hive中。
2-修改sqoop源码,让sqoop命令支持多维度分区导入
由于本人比较懒,若是能够自动化搞定的事情,绝不想将事情搞复杂。所以选择了第二种方案,修改sqoop源码,重新编译。
想法来源于名为taisenki的CSDN博主,网页链接如下
https://blog.csdn.net/taisenki/article/details/78974121
由于某些地方有问题,我在此处做了一定源码改动
官网下载了sqoop1.4.7源码编译包(注意一定要下载源码编译包,不然会编译失败)
查看了sqoop源码发现,sqoop再向hive中提交数据时的代码如下
public String getLoadDataStmt() throws IOException {
Path finalPath = getFinalPath();
StringBuilder sb = new StringBuilder();
sb.append("LOAD DATA INPATH '");
sb.append(finalPath.toString() + "'");
if (options.doOverwriteHiveTable()) {
sb.append(" OVERWRITE");
}
sb.append(" INTO TABLE `");
if(options.getHiveDatabaseName() != null) {
sb.append(options.getHiveDatabaseName()).append("`.`");
}
sb.append(outputTableName);
sb.append('`');
/** 这部分是重点 **/
if (options.getHivePartitionKey() != null) {
sb.append(" PARTITION (")
.append(options.getHivePartitionKey())
.append("='").append(options.getHivePartitionValue())
.append("')");
}
LOG.debug("Load statement: " + sb.toString());
return sb.toString();
}
可以看出,sqoop的partition直接将单个partition直接赋予一个变量使用,我们可以将此处的单个变量修改为数组,在hive导入数据时便可以使用多维分区了。
修改源码如下:
/**
* @return the LOAD DATA statement to import the data in HDFS into hive.
*/
public String getLoadDataStmt() throws IOException {
Path finalPath = getFinalPath();
StringBuilder sb = new StringBuilder();
sb.append("LOAD DATA INPATH '");
sb.append(finalPath.toString() + "'");
if (options.doOverwriteHiveTable()) {
sb.append(" OVERWRITE");
}
sb.append(" INTO TABLE `");
if(options.getHiveDatabaseName() != null) {
sb.append(options.getHiveDatabaseName()).append("`.`");
}
sb.append(outputTableName);
sb.append('`');
/** 修改为通过‘,’分割的多分区传参形式 **/
if (options.getHivePartitionKey() != null) {
String partitionKeys = options.getHivePartitionKey();
String partitionValues = options.getHivePartitionValue();
String[] pks = partitionKeys.split(",");
String[] pvs = partitionValues.split(",");
sb.append(" PARTITION (");
for (int i = 0; i < pks.length; i++) {
if (i != 0) {
sb.append(" , ");
}
sb.append(pks[i]).append("='").append(pvs[i]).append("'");
}
sb.append(")");
}
LOG.debug("Load statement: " + sb.toString());
return sb.toString();
}
为保证sqoop中create-hive-table的可用性,需同步对以下方法进行修改,内容如下:
/**
* @return the CREATE TABLE statement for the table to load into hive.
*/
public String getCreateTableStmt() throws IOException {
……
boolean first = true;
String partitionKeys = options.getHivePartitionKey();
for (String col : colNames) {
if (partitionKeys != null) {
// 修正排除每个分区列
String[] pks = partitionKeys.split(",");
for (String pk : pks) {
if (col.equals(pk)) {
throw new IllegalArgumentException("Partition key " + col + " cannot "
+ "be a column to import.");
}
}
}
if (!first) {
sb.append(", ");
}
first = false;
Integer colType = columnTypes.get(col);
String hiveColType = userMapping.getProperty(col);
if (hiveColType == null) {
hiveColType = connManager.toHiveType(inputTableName, col, colType);
}
if (null == hiveColType) {
throw new IOException("Hive does not support the SQL type for column "
+ col);
}
sb.append('`').append(col).append("` ").append(hiveColType);
if (HiveTypes.isHiveTypeImprovised(colType)) {
LOG.warn(
"Column " + col + " had to be cast to a less precise type in Hive");
}
}
sb.append(") ");
if (commentsEnabled) {
DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
String curDateStr = dateFormat.format(new Date());
sb.append("COMMENT 'Imported by sqoop on " + curDateStr + "' ");
}
if (partitionKeys != null) {
sb.append("PARTITIONED BY (");
// 修正拼接每个分区列
String[] pks= partitionKeys.split(",");
for (int i = 0; i < pks.length; i++){
if (i != 0) {
sb.append(" , ");
}
sb.append(pks[i])
.append(" STRING ");
}
sb.setLength(sb.length()-1);
sb.append(")");
}
sb.append("ROW FORMAT DELIMITED FIELDS TERMINATED BY '");
sb.append(getHiveOctalCharCode((int) options.getOutputFieldDelim()));
sb.append("' LINES TERMINATED BY '");
sb.append(getHiveOctalCharCode((int) options.getOutputRecordDelim()));
……
LOG.debug("Create statement: " + sb.toString());
return sb.toString();
}
修改完成后,便可以使用上文提到的多维分区命令。
目前已经完成了将mysql数据导入hive中的解决方案探索。
若是需要增量导入,则要求mysql表中存在自增id,或存在time类型的字段,可以将每次导入的最后一条数据的time字段记录后,每次导入任务开始时便使用该字段作为–last-value的值即可。
由于需要导入的表中不存在上述两种条件,所以估计需要考虑自己编写java程序,使用Quartz定时调度方法来实现数据的增量导入。