Sqoop 初识

Sqoop是什么?

Sqoop是一个用于在Hadoop和关系型数据库之间高效传输海量数据的工具,它可以把数据从关系型数据库中导入到HDFS中,也可以把HDFS中的数据导出到关系型数据库中。

工作机制

它是怎么在Hadoop和关系型数据库之间传输数据的呢?

导入机制

先来看下它是怎么把数据导入到HDFS中的,假设关系型数据库为MySQL。

首先,在Sqoop 导入开始之前,Sqoop需要和MySql数据库建立连接(通常使用JDBC的方式建立连接),然后获取要导入表的所有列和列的数据类型信息,并使用这些信息生成一个特定的Java类来存放从表中提取的记录,且该类实现了DBWritable接口readFiles方法,该方法反序列化ResulrtSet来填充该类的字段值。在生成发序列化的代码后,Sqoop会使用一个InputFormat来启动MapReduce Job(InputFormat用来分区查询表中的数据,默认为DataDrivenDBInputFormat,它可以将分区查询的任务分配给多个map task),然后Map Task执行查询并从ResultSet中反序列化行(rows)到生成的类的实例上,在写到HDFS之前,实例可以被直接写入到SequenceFiles,也可以转换以特定字符分割的文本。

Sqoop导入流程图如下:


控制导入

有时,我们不需要导入整个表的数据,这是可以通过在查询中指定WHERE子句来控制导入的范围。

导入一致性

数据在导入HDFS中时,需要确保导入后的数据和源数据时一致的,因为Map Task是运行一些并行且相互独立的进程从数据库中读数据的,它们不能共享一个单独的数据库事务。所以,最好的方式是在导入数据时禁止更新数据。

增量导入

做增量导入,需要检测出哪些是新增加的行,Sqoop提供了以下几种方式来实现增量导入:

1、基于某一列的值,适用于自动增长序列的列,如ID。需要使用--incremental append 和--last-value 参数指定

2、基于行的修改时间:适用于被修改的记录。需要--incremental lastmodified参数指定。

直接导入模式

直接导入就是通过不使用JDBC的方式来导入数据。例如MySQL的mysqldump。但是直接导入的方式不如JDBC的方式通用,因为直接导入的方式不能处理像BLOB、CLOB类型的大对象数据。

导入大对象

大多数数据库都提供了在单个字段里存储大量数据的能力,如存储CLOB、BLOB类型的数据,这些大对象的数据在磁盘的存储形式如下图:


可以看到,关系型数据库将大对象存储在了主行外部的一块独立的存储区域,而主行仅保留了对大对象数据的引用,只有在访问大对象的时候才打开它。

对于Hadoop MapReduce来说,库表中的每条记录在传递到mapper之前都要被物化,如果但条记录比较大,将会降低物化效率,并且,在内存中完全物化大对象数据也是不可能的。

为解决这些问题,Sqoop用一个单独的文件来存储大对象数据的记录(record),称作LobFile,在LobFile中的每个记录都包含一个单独的大对象,LobFile格式允许客户端持有一个记录的引用,而无需访问记录的内容。在访问记录时,将会使用java.io.InputStream或java.io.Reader来访问。

当导入表中记录时,“正常”的字段将会和一个LobFile(其存储的是CLOB或BLOB的列)的引用一起被物化。导入一个含有大对象的记录看起来就像下面的这样:

2,gizmo,4.00,2009-11-30,4,null,externalLob(lf,lobfile0,100,5011714)
externalLob(...)文本是一个指向存储在外部大对象的引用,以LobFile的格式(lf)存储在一个名为lobfile0的文件中,并且指定了其在文件中的偏移量(100)及长度(5011714)。

当读取这条记录的externalLob(...)时,将返回一个BlobRef类型的引用,它指向大对象列,但并不包含它的内容。BlobRef.getDataStream()方法才真正打开了LobFile文件并返回一个InputStream,这时你才可以访问大对象中的内容。这是一个很大的节省。

导出机制

再来看下Sqoop是怎样把数据从HDFS导出到数据库中的。

在执行导出之前,需要在库中建立目标表,虽然Sqoop可以根据Java的类型推断出适当的SQL数据类型,但这种转化效果并不是很好,例如,Java的String类型,可以转化为Char(64)、VarChar(200)等。所以,你必须决定哪种类型是最适合的。

Sqoop的导出和导入很相似。在执行导出前,Sqoop需要和数据库建立连接,通常使用JDBC,然后Sqoop根据目标表的定义生成一个Java类,该类具有从文本文件中解析记录(records)和插入适当类型值到表中的能力(除了从ResultSet读取列能力)。然后,启动MapReduce Job,从HDFS读取源数据文件(source datafiles),使用生成的Java类解析records,并执行所选择的导出策略。

基于JDBC的导出策略建立在批处理的INSERT语句上,每次将添加多条记录到目标表中。这将比单条插入要高效。

导出流程图如下:


导出和事务

因为Sqoop是使用多个task来并行导出数据切片(slices)的,这些并行导出并不是原子操作,它们会在不同的时间完成,意味着即使事务被用于task的内部,较早完成的任务的结果对另一个未完成任务可能是可见的。并且,数据库通常是使用固定大小的缓冲区存储事务,因此,一个事务并不一定能包含整个由一个task执行的所有操作。Sqoop是每几千行提交一次结果,来确保不会耗尽内存。

为解决这个问题,Sqoop会将数据导入到一个临时表中,然后在Job结束时,如果导出成功,会使用一个事务将临时表中的数据移动到目标表中。临时表必须已经存在,并且要和目标表一样的结构,并且还必须是空表,或者使用--clear-staging-table选项。

但是这样做会很慢,因为数据需要写两次:第一次写到临时表中,第二次写到目标中。并且导出进程需要占用更多的空间,因为在临时表中的数据移动到目标表中时,需要两份的数据。

导出和SequenceFiles

Sqoop也可以导出存储在SequenceFiles文件中的记录到外部的库表中。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章