每创建一个文件后,会为每个文件创建一个契约(lease)
先引入一个小的背景,假如多个客户端同时要并发的写Hadoop HDFS上的一个文件,这个事儿能成吗? 明显不可以接受啊,因为HDFS上的文件是不允许并发写的,比如并发的追加一些数据什么。 所以HDFS里有一个机制,叫做文件契约机制。 也就是说,同一时间只能有一个客户端获取NameNode上面一个文件的契约,然后才可以向获取契约的文件写 入数据。 此时如果其他客户端尝试获取文件契约的时候,就获取不到,只能干等着。 通过这个机制,可以保证同一时间只有一个客户端在写一个文件。 在获取到了文件契约之后,在写文件的过程期间,那个客户端需要开启一个线程,不停的发送请求给 NameNode进行文件续约,告诉NameNode: NameNode大哥,我还在写文件啊,你给我一直保留那个契约好吗? 而NameNode内部有一个专门的后台线程,负责监控各个契约的续约时间。 如果某个契约很长时间没续约了,此时就自动过期掉这个契约,让别的客户端来写。
1. 添加契约
-
FSNamesystem.startFileInternal()
,可以看到leaseManager.addLease()
就是开始添加契约代码。
-
LeaseManager.addLease()
,
-
1
:这里如果没有lease
,那就new一个,然后把这个lease
存储到一个TreeMap
和一个TreeSet
里,如果已经有lease
对象了,那么就调用renewLease()
; -
2
:sortedLeasesByPath
是个TreeMap
,这里就是把刚才获得的lease
和src
变量都put到这个map中(这个src应该就是文件的路径); -
3
:paths
是个TreeSet
,就是把src存储到一个set中。 - 后面让我们看看
renewLease()
方法
-
LeaseManager.renewLease()
;1
:先把lease
从set中移除;2
:然后调用lease.renew()
,3
:最后又把lease
添加到set中。直接看2
。
-
Lease.renew()
,1
这里就是把更新了lease
的lastUpdate
;2
这里简单注意下Lease
是LeaseManager
的内部类。
在第2步这里我们说到,使用到了TreeMap
那么肯定就是要对Lease
进行排序了。那么是根据什么排序的呢?我们看看Lease
的实现。
-
Lease
,可以看到Lease
实现了Comparable
接口,实现了compareTo
方法,并且进行比较的字段是lastUpdate
到此我们可以总结下了,所谓文件的契约其实就是创建一个Lease
对象,然后把这个对象存储到一个TreeMap
和一个TreeSet
中。使用集合的原因是,因为有很多契约,但是对应的每个文件就一个契约,而使用TreeMap
和TreeSet
的原因是因为要为契约按照最后更新时间进行升序排序。
- 那么这些代码都执行完之后,会从第3步的代码继续执行,我们继续看看后面有什么。再后面,就是又进行Editlog日志同步了。如果进去看看就是
Editlog
的双缓冲机制了。我们之前讲过就不看了。
2. 契约的续约-类似心跳
- 那么这些都执行完了,客户端创建文件的流程也就结束了。不过我们还得看下客户端那边的代码。可以看到客户端这里再创建完文件后,又创建了一个
DFSOutputStream
对象,然后又调用了DFSOutputStream
的start方法。
-
DFSOutputStream.start()
,这里有调用了DataStreamer.start()
,而DataStreamer
其实是个线程。DFSOutputStream
与DataStreamer
这两类,是写数据的核心类。我们后面在详细聊。
- 以上所有的代码,都是
DFSOutputStream.newStreamForCreate()
所调用的。而后面又执行了beginFileLease()
方法。
-
DFSClient.beginFileLease()
,这里其实就是续约
-
LeaseRenewer.put()
,这里启动了一个线程,run方法里调用了LeaseRenewer.this.run(id);
-
LeaseRenewer.run()
,这里就是调用了renew();
然后再后面就获得了新的契约时间。那肯定renew()
就是续约了。
-
LeaseRenewer.renew()
,这边的续约代码写的比较隐蔽。那就是在if那里,就是c.renewLease()
。这里有个for循环,可以看到是遍历的DFSClient
对象。也就是说这里应该是给所有客户端续约了。
- 一看到下面的代码,应该就想到要去
NameNodeRpcServer
里去找了。
-
这里的代码就很简答了。其实后面的代码跟上面我们说的创建契约的代码是一样的。
3. 契约续约的监控
客户端写文件,总不能一直写,总会写完吧。所以光创建契约和续约怎么行呢。还得有个线程监控写完了移除契约的。LeaseManager
有个内部类Monitor
就是做这事情的。
-
Monitor
也是个线程,所以,肯定执行的是run方法了。然后是个for循环,这里就是类似while(true),循环里关键的代码就是这么一行needSync = checkLeases();
,最后有个sleep
,sleep时间是2秒。
-
checkLeases();
,1
:这里是获得最老的契约;2
:这是判断如果最老的契约没有超时,那么直接break了;这个超时时间是1小时。
-
如果超时了,那么就执行移除契约的操作
removeLease(leaseToCheck, p);
。具体就不看了,肯定是从刚才那个treemap
和treeset
里移除这个契约呗。
-
到此我们画个图,简单总结下契约这事: