由于歷史原因,公司內部還在使用hadoop1.0.4版本的集群,而且上面運行的任務還挺多。當初開發這套系統的員工幾乎都離職了,所以這塊成了雞肋。撤掉吧,上面還有好的用戶;遷移吧,涉及的東西太多,影響比較大。所以這套系統就只能一直這樣維護著。
終于在一次集群的大事故中,讓大家認識到,再也不能用hadoop1.0集群了。
一、問題的原因
Hadoop1.0的HDFS元數據是存放在fsimage中的,編輯日志存放在edits;SecondaryNameNode節點負責把edits日志合并到fsimage中,用于數據恢復。當遇到edits日志中存在異常的時候,元數據不再往edits文件中寫,而是寫入edits.new文件中。當發現這個問題的時候,就需要在hdfs的安全模式下,使用以下命令進行恢復
hadoop dfsadmin -saveNamespace
但是在沒恢復之前,如果重啟namenode節點,問題就大了。我們這邊由于某個mr采用多路徑輸出,把中文輸出到文件路徑中了,導致在元數據中存放了亂碼,啟動namenode的時候,fsimage一直檢查不通過,異常如下:
ERROR org.apache.hadoop.hdfs.server.namenode.NameNode: java.io.IOException: Found lease for non-existent file /data/houraggregate/eventself_day/2017/05/01/14/output/_temporary/_attempt_201602020826_208092_r_000006_0/part-r-00006-DESelf_Coi#@$%^Pd
at org.apache.hadoop.hdfs.server.namenode.FSImage.loadFilesUnderConstruction(FSImage.java:1440)
at org.apache.hadoop.hdfs.server.namenode.FSImage.loadFSImage(FSImage.java:986)
at org.apache.hadoop.hdfs.server.namenode.FSImage.loadFSImage(FSImage.java:830)
at org.apache.hadoop.hdfs.server.namenode.FSImage.recoverTransitionRead(FSImage.java:377)
at org.apache.hadoop.hdfs.server.namenode.FSDirectory.loadFSImage(FSDirectory.java:100)
at org.apache.hadoop.hdfs.server.namenode.FSNamesystem.initialize(FSNamesystem.java:388)
at org.apache.hadoop.hdfs.server.namenode.FSNamesystem.<init>(FSNamesystem.java:362)
at org.apache.hadoop.hdfs.server.namenode.NameNode.initialize(NameNode.java:276)
at org.apache.hadoop.hdfs.server.namenode.NameNode.<init>(NameNode.java:496)
at org.apache.hadoop.hdfs.server.namenode.NameNode.createNameNode(NameNode.java:1279)
at org.apache.hadoop.hdfs.server.namenode.NameNode.main(NameNode.java:1288)
二、處理過程
既然是edit文件出異常,就應該修復該文件。查看資料是可以通過以下命令把edit這個二進制文件解析成xml文件的
hdfs oev -i edits -o edits.xml //解析edits文件
hdfs oiv -i fsimage -o fsimage.xml //解析fsimage文件
解析完成后通過以下命令重新轉回二進制文件
hdfs oev -i edits.xml -o edits -p binary //轉換xml文件成edits文件
hdfs oiv -i fsimage.xml-o fsimage -p binary //轉換xml文件成fsimage文件
由于系統是hadoop1.0.4所以不存在 hdfs 這個命令,只好把文件拷貝到hadoop2.6.0上進行操作。
打開轉換后的xml文件,把存在亂碼的Recode標簽內的記錄都刪除掉,然后在轉換為二進制文件。
當解析 image 文件的時候,發現解析不了,查看資料才發現,原來fsimage文件是與hadoop版本一一對應的。
所以上面所做的所有工作都是無效的。
后面想到,能不能通過查看hadoop寫fsimage文件的過程,然后反過來解析呢?但是這樣處理,會耗費大量的時間去閱讀源碼,實際情況是不允許的。轉念一想,能不能把判斷亂碼的代碼修改為,如果遇到就跳過呢?
通過查看異常的堆棧,找到是 FSImage 類的以下代碼報出的異常
for (int i = 0; i < size; i++) {
INodeFileUnderConstruction cons = readINodeUnderConstruction(in);
// verify that file exists in namespace
String path = cons.getLocalName();
INode old = fsDir.getFileINode(path);
if (old == null) {
// 從此處報出異常
throw new IOException("Found lease for non-existent file " + path);
}
if (old.isDirectory()) {
throw new IOException("Found lease for directory " + path);
}
INodeFile oldnode = (INodeFile) old;
fsDir.replaceNode(path, oldnode, cons);
fs.leaseManager.addLease(cons.clientName, path);
}
把代碼修改如下:
for (int i = 0; i < size; i++) {
INodeFileUnderConstruction cons = readINodeUnderConstruction(in);
// verify that file exists in namespace
String path = cons.getLocalName();
// 添加判斷,把存在異常的路徑過濾掉
if (path.contains("_temporary")){
continue;
}
INode old = fsDir.getFileINode(path);
if (old == null) {
throw new IOException("Found lease for non-existent file " + path);
}
if (old.isDirectory()) {
throw new IOException("Found lease for directory " + path);
}
INodeFile oldnode = (INodeFile) old;
fsDir.replaceNode(path, oldnode, cons);
fs.leaseManager.addLease(cons.clientName, path);
}
然后編譯FSImage類,打包到hadoop-core-1.0.4.jar中,放入hadoop1.0.4 namenode節點的對應目錄下,重啟namenode進程,終于能正常啟動了。
三、總結
1、不能在edits.new存在的情況下,重啟namenode進程
2、hdfs中的路徑不能存在亂碼,最后就用字母數字下劃線這些比較通用的字符來做路徑