Sqoop自定义多字节列分隔符

Sqoop提供的--fields-terminated-by选项可以支持指定自定义的分隔符,但是它只支持单字节的分隔符,对于我们特殊的需求:希望使用双字节的“|!”,默认的是不支持的。

Sqoop在进行每一次的导出任务时,都会调用codegen,生成一个java文件,并编译打包成一个jar,供MapReduce使用。这个java文件包装了一系列的对导出数据的访问接口,我们可以尝试通过对这个java文件进行分析,找到指定双字节分隔符的方法。

一般地,如果是使用的--query用查询语句获取数据,生成的文件为QueryResult.java,QueryResult.jar,如果使用的是--table,则用指定的表名对相应文件命名。java文件生成在sqoop脚本的同一目录下。

对于下面的Sqoop任务,生成test_table.java。

1 sqoop import --connect jdbc:XXX/testdb --username user --password password --table test_table --split-by id --fields-terminated-by '#'

对于test_table.java做代码分析:

首先,看下分隔符的定义():

1 DelimiterSet(char field, char record, char enclose, char escape, boolean isEncloseRequired){……}//分隔符集定义
2 
3 private final DelimiterSet __outputDelimiters = new DelimiterSet((char) 35, (char) 10, (char) 0, (char) 0, false);// 根据用户输入信息,定义当前分隔符

第一行,分隔符集定义,依次是 fields-terminated-by lines-terminated-by enclosed-by escaped-by,最后一个如果为true,enclosed-by会应用到所有字段,如果为false,只对fields that embed delimiters生效。

注意上面DelimiterSet定义语句,使用的是ASCII对照表。

所以,根据ASCII码表,第二行就很明白了,35是我们指定的'#'的ASCII码值。这里在研究的时候,用到一个trick,就是自己指定一个‘#’作为标记,然后从java代码里面找这个‘#’,它在哪里,就是我们关注的“有效”代码段。

如果,不指定分隔符为‘#’的话,上面的__outputDelimiters 是下面的样子。

1 private final DelimiterSet __outputDelimiters = new DelimiterSet((char) 44, (char) 10, (char) 0, (char) 0, false);// 默认分隔符

44即ASCII表示的‘,’。默认的分隔符,字段用逗号“,”,行用回车“\r\n”。

分隔符的定义搞明白了,接下来看下输出。Sqoop毕竟只是个中间处理环节,最后要数据到指定的目的地。经过几个toString跳转之后,看下面的代码段:

 1 public String toString(DelimiterSet delimiters, boolean useRecordDelim) {
 2     StringBuilder __sb = new StringBuilder();
 3     char fieldDelim = delimiters.getFieldsTerminatedBy();
 4     __sb.append(FieldFormatter.escapeAndEnclose(id==null?"null":"" + id, delimiters));
 5     __sb.append(fieldDelim);
 6     __sb.append(FieldFormatter.escapeAndEnclose(app_no==null?"null":app_no, delimiters));
 7     __sb.append(fieldDelim);
 8 
 9 ………………
10 __sb.append(FieldFormatter.escapeAndEnclose(seq==null?"null":"" + seq, delimiters));
11     if (useRecordDelim) {
12       __sb.append(delimiters.getLinesTerminatedBy());
13     }
14     return __sb.toString();
15   }

这里fieldDelim从之前定义的分隔符集中获取了字段分隔符,然后拼字符串的方式,在每个字段值后面,附加一个字段分隔符。

看到这里,我们就有思路了,只要把这里fieldDelim的赋值做一下修改,赋值为“|!",就可以达到我们的目的。

于是,修改此处代码如下(也是唯一的一处修改):

1 public String toString(DelimiterSet delimiters, boolean useRecordDelim) {
2     StringBuilder __sb = new StringBuilder();
3     String fieldDelim = “|!”;
4     __sb.append(FieldFormatter.escapeAndEnclose(id==null?"null":"" + id, delimiters));
5     __sb.append(fieldDelim);
6     __sb.append(FieldFormatter.escapeAndEnclose(app_no==null?"null":app_no, delimiters));
7     __sb.append(fieldDelim);
8 …………
9 }

接下来,对修改的test_table.java进行编译。

关于如何编译的问题,通过跟踪sqoop import执行的时候的输出日志:

 1 14/05/23 10:55:07 WARN tool.BaseSqoopTool: Setting your password on the command-line is insecure. Consider using -P instead.
 2 14/05/23 10:55:07 WARN sqoop.ConnFactory: Parameter --driver is set to an explicit driver however appropriate connection manager is not being set (via --connection-manager). Sqoop is going to fall back to org.apache.sqoop.manager.GenericJdbcManager. Please specify explicitly which connection manager should be used next time.
 3 14/05/23 10:55:07 INFO manager.SqlManager: Using default fetchSize of 1000
 4 14/05/23 10:55:07 INFO tool.CodeGenTool: Beginning code generation
 5 14/05/23 10:55:08 INFO manager.SqlManager: Executing SQL statement: SELECT t.* FROM test_table AS t WHERE 1=0
 6 14/05/23 10:55:08 INFO manager.SqlManager: Executing SQL statement: SELECT t.* FROM test_table AS t WHERE 1=0
 7 14/05/23 10:55:08 INFO orm.CompilationManager: HADOOP_MAPRED_HOME is /usr/lib/hadoop
 8 14/05/23 10:55:08 INFO orm.CompilationManager: Found hadoop core jar at: /usr/lib/hadoop/hadoop-core.jar
9 14/05/23 10:55:09 INFO orm.CompilationManager: Writing jar file: /tmp/sqoop-root/compile/3c33c9978c6103e610bf0f4a26fd92fa/test_table.jar
10 14/05/23 10:55:09 INFO mapreduce.ImportJobBase: Beginning import of test_table

通过上面高亮区域提供的信息,得到修改后的java代码的编译及打包方法如下:

1 javac -cp ./:/usr/lib/hadoop/hadoop-core.jar:/usr/lib/sqoop/sqoop-1.4.3.jar test_table.java
2 jar -cf test_table.jar test_table.class

打包完毕后,需要在使用Sqoop进行数据导出的时候,进行jar包的指定,指定的方式如下:

1 sqoop import --connect jdbc:XXX/testdb --username user --password password --table test_table --split-by id --jar-file /path/test_table.jar --class-name test_table

使用的选项为--jar-file和--class-name,其中--jar-file指定了jar的全路径,--class-name指定了用到的包中的java类。

以下是使用修改后的java文件实现的双字节分隔符导出结果。

再谈一下效率。使用这种自定义分隔符,自己手动生成的jar包做数据导入,测试数据5000w:

第一次:17min21sec

第二次:15min42sec

如果不指定jar,默认地执行,按照之前侧过的数据,分别是12min58sec,14min19sec。

看来使用这样的方式,还是对效率有一定的影响。但是也有可能是晚上7点数据库服务器做批量有关,后续要再做实验判定下。

 

posted @ 2014-05-23 19:39  郝玉琨  阅读(13464)  评论(3编辑  收藏  举报