点击上方 "大数据肌肉猿"关注, 星标一起成长
后台回复【加群】,进入高质量学习交流群
一、阅读 HDFS 源码的缘由
HDFS 是大数据的最基础的设施了,几乎所有的离线存储都在 HDFS 上。
但是在大规模 HDFS 集群中,下面的问题通常会让我们无比头疼:
元数据的量级超过亿级之后,NameNode 的内存也会变得非常巨大,启动和维护都变的异常困难;
如何保障 HDFS 的高可用?
NameNode 里面发生长时间的 GC 之后,导致 NameNode 进程退出,该如何解决?
如何优化 DataNode 的锁粒度,让其性能更高效?
以上种种问题,都需要我们阅读源码,甚至要修改它的源码才能解决。
所以虽然阅读源码非常痛苦,但是这个坎还是得过的。
二、如何阅读 Hadoop 这样的百万行代码的开源项目
首先 hadoop 是用 java 写的,所以一些 java 基础知识必不可少,比如锁,线程,设计模式,java 虚拟机,java io,不求很深入,基础得知道。
其次,不能一行行的读代码,这样很容易迷失在无边际的代码中,逃不出来,最后很容易就放弃了。
最后,以具体场景来驱动代码阅读。比如本文就是以 NameNode 的启动过程,来驱动代码阅读的。并且把关键的流程节点和类用流程图记录下来。
三、源码走读
找到 NameNode 类
从 main 方法开始,创建 NameNode
![](https://i-blog.csdnimg.cn/blog_migrate/36db560f5a15388355864f796645d407.png)
进入这个方法:
![](https://i-blog.csdnimg.cn/blog_migrate/09a56d7d15f15e1cf34ee92e32de4264.png)
有很多的 switch case ,由于我们启动命令是 hadoop-daemon.sh start namenode,所以直接运行到 default 里面:
![](https://i-blog.csdnimg.cn/blog_migrate/cd9f2294b26ce4b1d3623277aa54830a.png)
这是个比较重要的方法,实例化:
![](https://i-blog.csdnimg.cn/blog_migrate/6020bc7ca2d5885ce938090e8154ac72.png)
实例化方法里,有个比较重要的操作,startHttpServer(conf),启动 50070 端口,就是我们 50070 那个界面:
![](https://i-blog.csdnimg.cn/blog_migrate/1fbb05444254aa97541812e5b21bc615.png)
进去,使用 ip 和 端口创建了 HttpServer
![](https://i-blog.csdnimg.cn/blog_migrate/fd6c5a08a12056a4e0874901c140ecc6.png)
再来看这个 start 方法
![](https://i-blog.csdnimg.cn/blog_migrate/f49f29bddaa69c656597d3585c0537f4.png)
进入 start 方法,发现创建了一个 HttpServer2,这是 Hadoop 自己封装的服务
![](https://i-blog.csdnimg.cn/blog_migrate/a2da5787771cd003cd16d8e17942c622.png)
然后绑定了很多 Servlet,每个 Servlet 都是一个功能
![](https://i-blog.csdnimg.cn/blog_migrate/071c4a2c88040e5dbcdc57055fdfd49b.png)
可以看到画红线的,就是每个功能的地址。如果想看每个功能怎么实现的,可以点进去 Servlet,看它的 doGet 或者 doPost 或者 doPut 方法。
![](https://i-blog.csdnimg.cn/blog_migrate/2b61b327da3ac7f1a475ff869c23fd5d.png)
看完了这个,再退回到 NameNode 类中,然后就是去加载元数据(先不细看)
![](https://i-blog.csdnimg.cn/blog_migrate/131337ba0e45c094009ed10422be7d23.png)
然后是创建 RPC 服务端,启动一个服务端,给别的组件调用
![](https://i-blog.csdnimg.cn/blog_migrate/ff9a6a4382a8a0eea547e3e6c47ee724.png)
![](https://i-blog.csdnimg.cn/blog_migrate/d8a068195de9859b1a30fa08fff995a0.png)
看这个方法:
![](https://i-blog.csdnimg.cn/blog_migrate/dc8db46907bc322cf2aeb7c69e86fa13.png)
看这个地方,这个就是使用 hadoop 的 RPC ,来创建一个 RPC 服务端了。此时我们在 NameNodeRpcServer 类中。
![](https://i-blog.csdnimg.cn/blog_migrate/7eb54cc8bcf9cf074d2d7a055b4ec064.png)
创建了之后,再回到 NameNode 中,发现这个 NameNodeRpcServer 是 NameNode 类的一个属性,意思是,NameNode 委托这个类去启动了 NameNodeRpcServer,在设计模式中,属于组合。
![](https://i-blog.csdnimg.cn/blog_migrate/8642a08930ce62695c6573d4ec59649e.png)
然后我们再来看这个 NameNodeRpcServer ,实现了很多的协议:
![](https://i-blog.csdnimg.cn/blog_migrate/d743922103c8d8563cc8d742b3efaa7d.png)
![](https://i-blog.csdnimg.cn/blog_migrate/9a3888883469523bee74d9b412bdde78.png)
我们尝试在 ClientProtocol 中,找一找是否有创建目录的方法。
![](https://i-blog.csdnimg.cn/blog_migrate/e9f790bf7a1576106202e2418c164420.png)
发现是有的,所以 NameNodeRpcServer 会去实现这个方法的。
到时候 NameNode 启动之后,就会往外提供服务了。
然后我们再回到 NameNode 类,看最后一些功能:
![](https://i-blog.csdnimg.cn/blog_migrate/cc0e0d0ad9bbed2d7a86e9867bc919b3.png)
startCommonServices,主要做了两件事情,进行资源监察,检查磁盘够不够;检查是否可以退出安全模式。
![](https://i-blog.csdnimg.cn/blog_migrate/fd6e1a608e54a002582bb09c637a7fb4.png)
这个方法会检查配置文件中 fsimage 的目录 和 editlog 的目录磁盘资源是否充足。
最终会把磁盘是否足够的布尔值赋值给这个变量。
private volatile boolean hasResourcesAvailable = false;
然后会判断是否进入安全模式:
![](https://i-blog.csdnimg.cn/blog_migrate/8f34c8e29efef555467041fe4870a23c.png)
进入这个方法中:
![](https://i-blog.csdnimg.cn/blog_migrate/41bc1ea824fb19dceed5b5c30e62e93a.png)
这个 getCompleteBlocksTotal() 返回的是能正常使用的 Block 个数。
这个是怎么算的呢?
![](https://i-blog.csdnimg.cn/blog_migrate/21b51a8becdbf54f312c9cfc740f5e8e.png)
在 HDFS 中,存在两种类型的 Block,一种是 Complete 类型,即为正常可用的 Block;另一种是 underconstruction 类型,处于正在构建的 Block,相减,就是正常可用的 Block 个数了。
然后进入 checkMode 方法
![](https://i-blog.csdnimg.cn/blog_migrate/f266d7d86f7ae663cbbadec43b93ef3d.png)
进去之后,有一个 needEnter() 方法,这个方法里,判断了是否进入安全模式
![](https://i-blog.csdnimg.cn/blog_migrate/e9d404c67b31bc48d8c77ef170bfc636.png)
这里面有三个进入安全模式的条件:
![](https://i-blog.csdnimg.cn/blog_migrate/7aafc740b6ffa52dbbdb24660eb937e6.png)
我们来看第一个条件:
threshold != 0 && blockSafe < blockThreshold
threshold 默认配置是 0.999 ,不等于0;
blockSafe 可以在当前类中搜索一下。
![](https://i-blog.csdnimg.cn/blog_migrate/40de1bf337751c8a67d7db502ae9e928.png)
这个就表示,DataNode 每次心跳都要和 NameNode 汇报 自己的 Block 个数,每次汇报,这个值都会加 1.
如果 DataNode 汇报上来的 Block 个数小于所有可用的 Block 个数,就进入安全模式。
再来看第二个条件:
datanodeThreshold != 0 && getNumLiveDataNodes() < datanodeThreshold
这个条件表示,所有可用的 DataNode 小于配置的个数,则进入安全模式。
但是 datanodeThreshold 的默认配置值是 0,所以这个条件不启用。
第三个条件:
!nameNodeHasResourcesAvailable()
这个方法就是我们前面检查资源时,赋值出来的变量:
![](https://i-blog.csdnimg.cn/blog_migrate/803fbbd5ed5633774fd9df749c8e94a1.png)
如果 NameNode 的 images 和 editlogs 所在的目录,磁盘空间不足,则进入安全模式。
四、流程图
最后我们用一个流程图来总结一下 NameNode 启动流程:
![](https://i-blog.csdnimg.cn/blog_migrate/853c89efa3ad0f84a88cc1ad4098f3dc.png)
五、小结
主要有三大块:
1、启动 HttpServer ,可以查看 50070 端口;
2、管理和加载元数据;
3、启动 RPCServer,使其他组件可以调用;
4、检查磁盘空间;
5、判断是否进入安全模式;
--end--
扫描下方二维码
添加好友,备注【交流】
可私聊交流,也可进资源丰富学习群
更文不易,点个“在看”支持一下????