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