Hadoop教程

深入了解导出

Sqoop导出功能的架构与其导入功能的非常相似,参见图15-4。在执行导出操作之 前,Sqoop会根据数据库连接字符串来选择一个导出方法。对于大多数系统来说,Sqoop都会选择JDBC。然后,Sqoop根据目标表的定义生成一个Java类。这个生 成的类能够从文本文件中解析记录,并能够向表中插入类型合适的值(除了能够读 取ResultSet中的列)。接着会启动一个MapReduce作业,从HDFS中读取源数 据文件,使用生成的类解析记录,并且执行选定的导出方法。

图15-4.使用MapReduce并行执行导出

基于JDBC的导出方法会产生一批INSERT语句,每条语句会向目标表中插入多条 记录。在大部分的数据库系统中,通过一条语句插入多条记录的执行效率要高于多 次执行插入单条记录的INSERT语句。多个单独的线程被用于从HDFS读取数据并 与数据库进行通信,以确保涉及不同系统的I/O操作能够尽量重叠执行。对于MySQL数据库来说,Sqoop可以采取使用mysqlimport的直接模式方法。 每个map任务会生成一个mysqlimport进程,该进程通过本地文件系统上的一个命名FIFO通道进行通信。

然后,数据通过这个FIFO通道流入mysqlimport,然后被写入数据库。

虽然从HDFS读取数据的MapReduce作业大多根据所处理文件的数量和大小来选 择并行度(map任务的数量),但Sqoop的导出工具允许用户明确设定任务的数量。 由于导出性能会受并行的数据库写入线程数量的影响,所以Sqoop使用 CombineFileInputFormat类将输入文件分组分配给少数几个map任务去执行。

导出与事务

进程的并行特性,导致导出操作往往不是原子操作。Sqoop会生成多个并行执行的 任务,分别导出数据的一部分。这些任务的完成时间各不相同,即使在每个任务内 部都使用事务,不同任务的执行结果也不可能同时提交。此外,数据库系统经常使 用固定大小的缓冲区来存储事务数据,这使一个任务中的所有操作不可能在一个事 务中完成。Sqoop每导入几千条记录便执行一次提交,以确保不会出现内存不足的 情况。在导出操作进行过程中,提交过的中间结果都是可见的。因此在导出过程完 成前,不要启动那些要使用导出结果的应用程序,否则这些应用会看到不完整的导 出结果。

更有问题的是,如果任务失败(由于网络问题或其他问题),它们会从头开始重新导 入自己负责的那部分数据,因此可能会插入重复的记录。在写作本章时,Sqoop3 不能避免这种可能性。在启动导出作业之前,应当在数据库中设置必要的约束(例 如,定义一个主键列)以保证数据行的唯一性。虽然将来版本的Sqoop可能会使用 更好的恢复逻辑,但现在还没有做到。

导出和 SequenceFile

之前的导出示例是从一个Hive表中读取源数据,该Hive表以分隔文本文件形式保 存在HDFS中。Sqoop也可以从非Hive表的分隔文本文件中导出数据。例如, Sqoop可以导出MapReduce作业结果的文本文件。

Sqoop还可以将存储在SequenceFile中的记录导出到输出表,不过有一些限制。 SequenceFile中可以保存任意类型的记录。Sqoop的导出工具从SequenceFile中读 取对象,然后直接发送到OutputCollector,由它将这些对象传递给数据库导出 OutputFormat。为了能让Sqoop使用,记录必须被保存在SequenceFile键/值对 格式的“值”部分,并且必须继承抽象类coicloudera.sqoop.lib. SqoopRecord(像Sqoop生成的所有类那样)。

如果基于导出目标表,使用codegen工具(Sqoop-codegen)为记录生成一个SqoopRecord的实现,那你就可以写一个MapReduce程序,填充这个类的实例并 将它们写入SequenceFile。接着,sqoop-export就可以将这些SequenceFile文件 导出到表中。还有另外一种方法,即将数据放入SqoopRecord实例,然后保存到 SequenceFile中。如果数据是从数据库表导入HDFS的,那么在经过某种形式的修 改后,可以将结果保存在持有相同数据类型记录的SequenceHle中。

在这种情况下,Sqoop应当利用现有的类定义从SequenceFile中读取数据,而不是 像导出文本记录时所做的那样,为执行导出生成一个新的(临时的)记录容器类。通 过为Sqoop提供--class-name和--jar-file参数,可以禁止它生成代码,而使 用现有的记录类和jar包。在导出记录时,Sqoop将使用指定jar包中指定的类。

在下面的例子中,我们将重新导入widgets表到SequenceFile中,然后再将其导 出到另外一个数据库表:

% sqoop import --connect jdbc:mysql://localhost/hadoopguide \>--table widgets -m 1 --class-name WidgetHolder --as-sequencefile \>--target-dir widget_sequence_files --bindir ....10/07/05 17:09:13 INFO mapreduce.Import3obBase: Retrieved 3 records.% mysql hadoopguidemysql> CREATE TABLE widgets2(id INT, widget_name VARCHAR(100),    -> price DOUBLE, designed DATE, version INT, notes VARCHAR(260));Query OK, 0 rows affected (0.03 sec)mysql> exit;% sqoop export --connect jdbc:mysql://localhost/hadoopguide \>--table widgets2 -m 1 --class-name WidgetHolder \>--jar-file widgets.jar --export-dir widget_sequence_files...10/07/05 17:26:44 INFO mapreduce.Export3obBase: Exported 3 records.

在导入过程中,我们指定使用SequenceFile格式,并且将jar文件放入当前目录(使用--bindir),这样便可以重用它。否则,它会被保存在一个临时目录中。然后我 们创建一个用于导出的目标表,该表在模式上稍有不同但与源数据兼容。在接下来 的导出操作中,我们使用现有的生成代码从SequenceFile中读取记录并将它们写入 数据库。

关注微信获取最新动态