LeaseRenewer源码浅析
根据https://issues.apache.org/jira/browse/HDFS-1870,找到了蛛丝马迹,LeaseRenewer在0.23.0版本前为DFSClient的内部类LeaseChecker;将LeaseChecker作为查找关键字,发现了更早的issue,即https://issues.apache.org/jira/browse/HADOOP-3169,该issue的发布版本为0.19.0,那么,就从0.18作为切入点。图1为0.18.3的源码
首先可以看出来LeaseChecker是一个线程类,run()方法整体比较好理解:1s轮询一次调用namenode.renewLeaase(clientName)方法,clientName是DFSClient的实例变量,被final修饰,如图2:
这说明一个DFSClient对象是跟一个clientName绑定的;同时,图2也可以看到DFSClient持有一个名为leaseChecker的Daemon类对象,结合图1LeaseChecker的源码以及图3中DFSClient的构造函数,可以看出,一个LeaseChecker对象跟一个DFSClient对象也是绑定的。
这样子会引发两个问题:
- DFSClient可以理解为文件读、写的入口,但是租约——Lease只有在写操作时才会涉及,因此当一个DFSClient对象只用来进行读操作时,会浪费一个线程。
- 在一个JVM中,每一个DFSClient对象都需要一个LeaseChecker线程,这实际上有些浪费资源,多个DFSClient对象可以共用一个LeaseChecker线程。
针对#1,https://issues.apache.org/jira/browse/HADOOP-3169提出了解决思路,图4为0.19.0版本的代码
主要有三个改动:
- DFSClient不再持有一个Daemon leaseChecker对象,而是改为持有一个LeaseChecker leasechecker对象;同时,DFSClient的构造函数不再负责调度leaseChecker.start(),而是推迟到DFSClient对象第一次进行写操作时。
- LeaseChecker类,增加了一个map用来缓存文件到输出流的映射关系。leaseChecker在周期性的执行renew()方法时,会进行判断,只有map非空时,才会真正的向NN发起RPC请求;当map为空时,调用renew()方法为空操作;
- LeaseChecker类,增加了一个Daemon对象和put()方法,这里需要提一句,put()方法是用synchronized进行修饰的,这个锁有两个用处:一是用来保护pendingCreates这个map;二是用来保护daemon。
至此,遗留了一个小问题:在LeaseChecker线程启动后,便无法停止,即便当前DFSClient对象已经不再进行写操作,所以https://issues.apache.org/jira/browse/HDFS-1840做了这个小优化,引入了一个int类型的currentId变量,当map从空变为非空状态时,currentId增1。在线程的run()方法中,循环退出条件为当前id值和currentId是否相等,如果不相等,退出循环,线程执行结束;如果map为空时,退出循环,线程执行结束。同时,为了防止DFSClient对象频繁的写操作(写操作耗时较短)导致频繁的restart leasechecker线程,新增了一个long类型的emptyTime变量,记录map为空后经过的时间,新增了一个long类型的gracePeriod变量,作为一个参照,含义为当map为空后经过gracePeriod时间后,才退出循环,从而结束线程。
这里有一个插曲,https://issues.apache.org/jira/browse/HDFS-1870开始,LeaseChecker类不再是DFSClient的内部类,改名为LeaseRenewer成为单独的一个类。
针对#2,https://issues.apache.org/jira/browse/HDFS-1865提出了解决思路,不同的DFSClient对象可以共用同一个LeaseRenewer对象。这里有一个很大的改动,担心自己无意中的文字加工,还是要截一下原文,如图5:
虽然在一个JVM中可以存在多个DFSClient对象,但是在LeaseRenew层面,刻画客户端这个概念用DFSClient对象进行标识是不合理的,可以将使用同一用户(e.g. hive_user)访问同一Namenode(e.g. Active Namenode1)的DFSClient对象视为同一个客户端。代码改动为在LeaseRenewer中新增了Key这个静态内部类,用来标识客户端这一个概念,在LeaseRenewer类中新增了一个Key到LeaseRenewer的map,如图6:
其中,authority用来表示Namenode,UserGroupInformation用来表示用户。
至此,LeaseRenewer类成长为当前版本的雏形。