导入大对象
很多数据库都具有在一个字段中保存大量数据的能力。取决于数据是文本还是二进 制类型,通常将其保存在表中CLOB或BLOB类型的列中。数据库一般会对这些 “大对象”进行特殊处理。大多数的表在磁盘上的物理存储都如图15-2所示。通 过行扫描来确定哪些行匹配特定查询的条件时,通常需要从磁盘上读出每一行的所 有列。如果也是以这种方式“内联”存储大对象,它们会严重影响扫描的性能。因 此,一般将大对象与它们的行分开存储,如图15-3所示。在访问大对象时,需要 通过行中包含的引用来“打开”它。
图15-2.数据库通常以行数组的方式来存储表,行中所有列存储在相邻的位置
在数据库中使用大对象的困难表明,像Hadoop这样的系统更适合于处理大型的、 复杂的数据对象,也是存储此类信息的理想选择。Sqoop能够从表中抽取大对象数 据,并且将它们保存在HDFS中供进一步处理。
图15-3.大对象通常保存在单独的存储区域,行的主存储区域包含指向大对象的间接引用
同在数据库中一样,MapReduce在将每条记录传递给mapper之前一般要对其进行 “物化” (Materialize).如果单条记录真的很大,这将非常低效。
如前所示,Sqoop导入的记录在磁盘上的存储格式与数据库的内部数据结构非常相 似:一个将每条记录的所有字段放在一起的记录数组。当在导入的记录上运行一个 MapReduce程序时,每个map任务必须将读入记录的所有字段完全物化。如果 MapReduce程序仅有很小一部分输入记录的大对象字段的内容是有用的,那么将 所有记录完全物化将使程序效率低下。此外,从大对象的大小来看,在内存中进行 完全物化也许是不可能的。
为了克服这些困难,Sqoop将导入的大对象数据存储在LobFUe格式的单独文件 中。LobFile格式能够存储非常大的单条记录(使用了 64位的地址空间)。LobFile 文件中的每条记录保存一个大对象。LobFile格式允许客户端持有对记录的引用, 而不访问记录内容。可以通过java.io.InputStream(用于二进制对象)或 java.io.Reader(用于字符对象)来访问记录。
在导人一条记录时,所有的“正常”字段会在一个文本文件中一起物化,同时还生 成一个指向保存CLOB或BLOB列的LobHle文件的引用。例如,假设我们的 widgets表有一个名为schematic的BLOB字段,该字段用于保存每个部件的原 理图。
导入的记录可能像下面这样:
2,gizmo,4.00,2009-ll-30,4,null,externalLob(lf,lobfile0,100,5011714)
externalLob(...)是对外部存储大对象的一个引用,这个大对象以LobFile格式(lf)存 储在名为lobfile0的文件中,同时还给出了该对象在文件中的字节位移和长度。在使用这条记录时,Widget.get_schematic()方法会返回一个类型为 BlobRef 的对象,用于引用schematic列,但这个对象并不真正包含记录的内容。 BlobRef.getDataStream()方法实际会打开LobFile文件并返回一个 InputStream,用于访问schematic字段的内容。
在使用一个MapReduce作业来处理许多Widget记录时,可能你只需要访问少数 几条记录的schematic字段。单个原理图数据可能有几兆大小或更大,使用这种 方式时,只需要承担访问所需大对象的I/O开销。
在一个map任务中,BlobRef和ClobRef类会缓存对底层LobFile文件的引用。 如果你访问几个顺序排列记录的schematic字段,就可以利用现有文件指针来定 位下一条记录。