sqoop数据ETL工具初探

研究背景

最近实习项目需要使用到kylin建立数据立方体,kylin是一个基于hive的OLAP系统,主要功能是可以通过预计算的方式将存储在hdfs上的文件设定为多维立方体缓存到HBase中,将需要的查询结果作为数据立方体预先存储在hbase中,是一种以空间换时间的快速查询方法。
甲方公司目前使用的是mysql存储数据,并且数据会定期删除。目前需要考虑使用工具将mysql中的数据导入到Hive中,同时考虑到数据定期删除,还需要设置定时任务以及增量导入等问题。

sqoop介绍

概念

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

5、验证安装是否成功
sqoop-version
sqoop数据ETL工具初探_第1张图片

Sqoop基本命令

由于我需要使用sqoop将mysql的数据导入到hive中,所以我主要使用的命令为sqoop -import

将mysql表导入hive(主要命令列举)
单个维度分区
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键或一个日期字段来记录每一次最后导入数据的时间

Append方式的全量数据导入

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数据ETL工具初探_第2张图片

将时间列大于等于阈值的数据增量导入HDFS

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”

↑remember this date↑

遇到的问题
1、sqoop遇到Exception in thread “main” java.lang.NoSuchMethodError

将sqoop/lib下的hsqldb-1.8.0.10.jar删除,解决问题

2、sqoop遇到ERROR metadata.Hive: Failed to move,NoClassDefFoundError: org/apache/hadoop/tools/DistCpOptions

hive 最后移动数据的时候,需要调用hadoop-distcp-X.X.X.jar,
解决方法:只需要把$HADOOP_HOME/share/hadoop/tools/lib/hadoop-distcp-x.x.x.jar 拷贝 $HIVE_HOME/lib下面,重启hive即可

sqoop多分区问题

此处有一个惊天大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数据ETL工具初探_第3张图片
查看了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定时调度方法来实现数据的增量导入。

你可能感兴趣的:(数据处理)