最近在hadoop實際使用中有以下幾個小細節分享: i=m5M]Ef
1 中文問題
從url中解析出中文,但hadoop中打印出來仍是亂碼?我們曾經以為hadoop是不支持中文的,后來經過查看源代碼,發現hadoop僅僅是不支持以gbk格式輸出中文而己。
這是TextOutputFormat.class中的代碼,hadoop默認的輸出都是繼承自FileOutputFormat來的,FileOutputFormat的兩個子類一個是基于二進制流的輸出,一個就是基于文本的輸出TextOutputFormat。
public class TextOutputFormat<K, V> extends FileOutputFormat<K, V> {
protected static class LineRecordWriter<K, V> &E{CQ#k
implements RecordWriter<K, V> {
private static final String utf8 = “UTF-8″;//這里被寫死成了utf-8 2 kP0//
private static final byte[] newline; kTC'`xv
static { :htz]
try { 0 _!')+
newline = “/n”.getBytes(utf8); Ry$zF~[
} catch (UnsupportedEncodingException uee) {
throw new IllegalArgumentException(”can’t find ” + utf8 + ” encoding”);
}
}
… k-:wM`C
public LineRecordWriter(DataOutputStream out, String keyValueSeparator) {
this.out = out;
try {
this.keyValueSeparator = keyValueSeparator.getBytes(utf8);
} catch (UnsupportedEncodingException uee) { @r.w+E=
throw new IllegalArgumentException(”can’t find ” + utf8 + ” encoding”);
}
} ab}Kt($
…
private void writeObject(Object o) throws IOException {
if (o instanceof Text) {
Text to = (Text) o;
out.write(to.getBytes(), 0, to.getLength());//這里也需要修改 q&DM*!Jq
} else { 5 O't-'
out.write(o.toString().getBytes(utf8));
}
}
… qxQuXF>:#
} |3bCq(ZR/P
可以看出hadoop默認的輸出寫死為utf-8,因此如果decode中文正確,那么將Linux客戶端的character設為utf-8是可以看到中文的。因為hadoop用utf-8的格式輸出了中文。
因為大多數數據庫是用gbk來定義字段的,如果想讓hadoop用gbk格式輸出中文以兼容數據庫怎么辦? _.{I1*6Y2
我們可以定義一個新的類: .c5)`
public class GbkOutputFormat<K, V> extends FileOutputFormat<K, V> { sTS Nu+
protected static class LineRecordWriter<K, V>
implements RecordWriter<K, V> {
//寫成gbk即可 F"ua`ercI
private static final String gbk = “gbk”;
private static final byte[] newline;
static {
try {
newline = “/n”.getBytes(gbk);
} catch (UnsupportedEncodingException uee) { @}<b42
throw new IllegalArgumentException(”can’t find ” + gbk + ” encoding”);
}
}
… SjL&/),
public LineRecordWriter(DataOutputStream out, String keyValueSeparator) { P?o|N<46
this.out = out; X-<l+WP
try { 0,]m.)ws
this.keyValueSeparator = keyValueSeparator.getBytes(gbk); Js'j}w
} catch (UnsupportedEncodingException uee) {
throw new IllegalArgumentException(”can’t find ” + gbk + ” encoding”);
}
} J|aU}Z8m
… /(&UDG$
private void writeObject(Object o) throws IOException {
if (o instanceof Text) {
// Text to = (Text) o;
// out.write(to.getBytes(), 0, to.getLength()); +A-z>T(
// } else { @h,3"2W{Ev
out.write(o.toString().getBytes(gbk));
}
} isU4D
… eL_Il.:
}
然后在mapreduce代碼中加入conf1.setOutputFormat(GbkOutputFormat.class)
即可以gbk格式輸出中文。
2 關于計算過程中的壓縮和效率的對比問題 hf//2Vl
之前曾經介紹過對輸入文件采用壓縮可以提高部分計算效率。現在作更進一步的說明。
為什么壓縮會提高計算速度?這是因為mapreduce計算會將數據文件分散拷貝到所有datanode上,壓縮可以減少數據浪費在帶寬上的時間,當這些時間大于壓縮/解壓縮本身的時間時,計算速度就會提高了。
hadoop的壓縮除了將輸入文件進行壓縮外,hadoop本身還可以在計算過程中將map輸出以及將reduce輸出進行壓縮。這種計算當中的壓縮又有什么樣的效果呢?
測試環境:35臺節點的hadoop cluster,單機2 CPU,8 core,8G內存,redhat 2.6.9, 其中namenode和second namenode各一臺,namenode和second namenode不作datanode
輸入文件大小為2.5G不壓縮,records約為3600萬條。mapreduce程序分為兩個job: ;R]~9Aan
job1:map將record按user字段作key拆分,reduce中作外連接。這樣最后reduce輸出為87億records,大小540G
job2:map讀入這87億條數據并輸出,reduce進行簡單統計,最后的records為2.5億條,大小16G
計算耗時54min
僅對第二個階段的map作壓縮(第一個階段的map輸出并不大,沒有壓縮的必要),測試結果:計算耗時39min
可見時間上節約了15min,注意以下參數的不同。 U&W/Nj
不壓縮時:
Local bytes read=1923047905109 :3[;9xCHj
Local bytes written=1685607947227 "j8`)XXa(
壓縮時: /U>|^$4 #5
Local bytes read=770579526349 |RL/2j|
Local bytes written=245469534966
本地讀寫的的數量大大降低了
至于對reduce輸出的壓縮,很遺憾經過測試基本沒有提高速度的效果。可能是因為第一個job的輸出大多數是在本地機上進行map,不經過網絡傳輸的原因。
附:對map輸出進行壓縮,只需要添加 jobConf.setMapOutputCompressorClass(DefaultCodec.class)
3 關于reduce的數量設置問題
reduce數量究竟多少是適合的。目前測試認為reduce數量約等于cluster中datanode的總cores的一半比較合適,比如 cluster中有32臺datanode,每臺8 core,那么reduce設置為128速度最快。因為每臺機器8 core,4個作map,4個作reduce計算,正好合適。 u/(>a
附小測試:對同一個程序 j&[u$P*K
reduce num=32,reduce time = 6 min
reduce num=128, reduce time = 2 min
reduce num=320, reduce time = 5min
4某次正常運行mapreduce實例時,拋出錯誤
java.io.IOException: All datanodes xxx.xxx.xxx.xxx:xxx are bad. Aborting…
at org.apache.hadoop.dfs.DFSClient$DFSOutputStream.processDatanodeError(DFSClient.java:2158)
at org.apache.hadoop.dfs.DFSClient$DFSOutputStream.access$1400(DFSClient.java:1735)
at org.apache.hadoop.dfs.DFSClient$DFSOutputStream$DataStreamer.run(DFSClient.java:1889)
java.io.IOException: Could not get block locations. Aborting…
at org.apache.hadoop.dfs.DFSClient$DFSOutputStream.processDatanodeError(DFSClient.java:2143)
at org.apache.hadoop.dfs.DFSClient$DFSOutputStream.access$1400(DFSClient.java:1735)
at org.apache.hadoop.dfs.DFSClient$DFSOutputStream$DataStreamer.run(DFSClient.java:1889)
經查明,問題原因是linux機器打開了過多的文件導致。用命令ulimit -n可以發現linux默認的文件打開數目為1024,修改/ect/security/limit.conf,增加hadoop soft 65535
再重新運行程序(最好所有的datanode都修改),問題解決
P.S:據說hadoop dfs不能管理總數超過100M個文件,有待查證
5 運行一段時間后hadoop不能stop-all.sh的問題,顯示報錯
no tasktracker to stop ,no datanode to stop
問題的原因是hadoop在stop的時候依據的是datanode上的mapred和dfs進程號。而默認的進程號保存在/tmp下,linux 默認會每隔一段時間(一般是一個月或者7天左右)去刪除這個目錄下的文件。因此刪掉hadoop-hadoop-jobtracker.pid和 hadoop-hadoop-namenode.pid兩個文件后,namenode自然就找不到datanode上的這兩個進程了。
在配置文件中的export HADOOP_PID_DIR可以解決這個問題