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點數據庫服務器做批量有關,后續要再做實驗判定下。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM