学习hadoop源码的过程中,遇到这么一个问题,给大家分享如下。为什么说是“疑似”,相信大家在使用hadoop的时候,大部分情况不会遇到这个问题,但是在我的实验环境中确实存在这个问题。
问题描述:
hadoop版本:1.0.3
在实验环境下,格式化namenode后,启动namenode(运行时参数 -regular)报错:
2013-01-07 16:13:04,567 ERROR - FSNamesystem.问题分析:(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. (FSNamesystem.java:346) at com.test.hadoop.hdfs.server.namenode.NameNode.initialize(NameNode.java:264) at com.test.hadoop.hdfs.server.namenode.NameNode. (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. (FSNamesystem.java:346) at com.test.hadoop.hdfs.server.namenode.NameNode.initialize(NameNode.java:264) at com.test.hadoop.hdfs.server.namenode.NameNode. (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