学习hadoop源码的过程中,遇到这么一个问题,给大家分享如下。为什么说是“疑似”,相信大家在使用hadoop的时候,大部分情况不会遇到这个问题,但是在我的实验环境中确实存在这个问题。
问题描述:
hadoop版本:1.0.3
在实验环境下,格式化namenode后,启动namenode(运行时参数 -regular)报错:
2013-01-07 16:13:04,567 ERROR - FSNamesystem.<init>(348) | FSNamesystem initialization failed.
java.io.IOException: Unexpected block size: -1
at com.test.hadoop.hdfs.protocol.Block.readFields(Block.java:128)
at com.test.hadoop.hdfs.server.namenode.FSImage.loadFSImage(FSImage.java:920)
at com.test.hadoop.hdfs.server.namenode.FSImage.loadFSImage(FSImage.java:811)
at com.test.hadoop.hdfs.server.namenode.FSImage.recoverTransitionRead(FSImage.java:358)
at com.test.hadoop.hdfs.server.namenode.FSDirectory.loadFSImage(FSDirectory.java:90)
at com.test.hadoop.hdfs.server.namenode.FSNamesystem.initialize(FSNamesystem.java:372)
at com.test.hadoop.hdfs.server.namenode.FSNamesystem.<init>(FSNamesystem.java:346)
at com.test.hadoop.hdfs.server.namenode.NameNode.initialize(NameNode.java:264)
at com.test.hadoop.hdfs.server.namenode.NameNode.<init>(NameNode.java:519)
at com.test.hadoop.hdfs.server.namenode.NameNode.createNameNode(NameNode.java:1307)
at com.test.hadoop.hdfs.server.namenode.NameNode.main(NameNode.java:1316)
2013-01-07 16:13:04,568 ERROR - NameNode.main(1320) | java.io.IOException: Unexpected block size: -1
at com.test.hadoop.hdfs.protocol.Block.readFields(Block.java:128)
at com.test.hadoop.hdfs.server.namenode.FSImage.loadFSImage(FSImage.java:920)
at com.test.hadoop.hdfs.server.namenode.FSImage.loadFSImage(FSImage.java:811)
at com.test.hadoop.hdfs.server.namenode.FSImage.recoverTransitionRead(FSImage.java:358)
at com.test.hadoop.hdfs.server.namenode.FSDirectory.loadFSImage(FSDirectory.java:90)
at com.test.hadoop.hdfs.server.namenode.FSNamesystem.initialize(FSNamesystem.java:372)
at com.test.hadoop.hdfs.server.namenode.FSNamesystem.<init>(FSNamesystem.java:346)
at com.test.hadoop.hdfs.server.namenode.NameNode.initialize(NameNode.java:264)
at com.test.hadoop.hdfs.server.namenode.NameNode.<init>(NameNode.java:519)
at com.test.hadoop.hdfs.server.namenode.NameNode.createNameNode(NameNode.java:1307)
at com.test.hadoop.hdfs.server.namenode.NameNode.main(NameNode.java:1316)
问题分析:
代码运行到Block.java:128这个地方就报错了,Block.readFields这个方法的作用是读取文件块的内容,同时,如果读取的字节数小于0,就会报错,这也是为什么我启动namenode的时候会报错。Block.readFields的方法给我们的提示信息仅仅就是没有读到块数据,没有多大的参考意义。检查FSImage.loadFSImage方法,在该方法会发现有这么一段代码:
if ((-9 <= imgVersion && numBlocks > 0) ||(imgVersion < -9 && numBlocks >= 0)) {
blocks = new Block[numBlocks];
for (int j = 0; j < numBlocks; j++) {
blocks[j] = new Block();
if (-14 < imgVersion) {
blocks[j].set(in.readLong(), in.readLong(),
Block.GRANDFATHER_GENERATION_STAMP);
} else {
blocks[j].readFields(in);
}
}
}
大致意思就是当满足
(-9 <= imgVersion && numBlocks > 0) || (imgVersion < -9 && numBlocks >= 0) 条件下才执行
blocks[j].readFields(in) 代码,而(-9 <= imgVersion && numBlocks > 0) || (imgVersion < -9 && numBlocks >= 0)的意图是为了保证有可读的块,综合这段分析,报错的原因很可能是因为,Inode所在的目录本身是没有block,由于某些异常原因,又执行了blocks[j].readFields(in)这段代码。因为我刚刚执行了namenode的格式化操作,Fsimage中目前只有rootDir根目录的信息,在格式化namenode的时候,根目录的block块数是-1,这意味着blocks[j].readFields(in) 这段代码理论上是不可能执行的,实际上他又执行了,这样的话numBlocks很可能出问题了。进一步检查变量numBlocks的值发现发现是16777215,而不是-1。
综合上面的分析,我怀疑是loadFSImage方法中,解析fsimage文件出错,下面例子更加证明了我的想法。
DataInputStream in = new DataInputStream(
new BufferedInputStream(new FileInputStream(
"E:\\data\\hadoop\\name\\current\\fsimage")));
// read image version: first appeared in version -1
int imgVersion = in.readInt();
// read namespaceID: first appeared in version -2
int namespaceID = in.readInt();
// read number of files
long numFiles = in.readLong();
// read in the last generation stamp.
long genstamp = in.readLong();
short namelength = in.readShort();
byte[] data = new byte[namelength];
in.readFully(data, 0, namelength);
String path = new String(data);
short replication = in.readShort();
long modificationTime = in.readLong();
long atime = in.readLong();
long blockSize = in.readLong();
int numBlocks = in.readInt(); // 实际值也是-1
long nsQuota = in.readLong();
long dsQuota = in.readLong();
上面这段代码直接读取fsimage文件,对应fsimage的持久化过程,读取fsimage中的值。在查看这些具体的值后,我确信fsimage文件是没有问题的。由于在loadFSImage方法和我的例子中,path变量的读取存在差异,然后替换过去,启动namenode,问题解决。
总结:
- 写short数据:DataOutputStream.writeShort(nameLen); 这里用了两个byte
- 读short数据:WritableUtils.readVLong(DataInput stream) ,这里读nameLen时,只用了一个byte