由于历史原因,公司内部还在使用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中的路径不能存在乱码,最后就用字母数字下划线这些比较通用的字符来做路径