源码参考hadoop-3.3.0,主要流程做解释,欢迎指正。
1 hadoop租约概述
本文书接上文,在完成创建INodeFile的过程中,会进行租约的添加(过程中是给指定文件添加一个租约),在FSDirWriteFileOp.startFile方法中:
// leaseManager是LeaseManager的实例
fsn.leaseManager.addLease(
newNode.getFileUnderConstructionFeature().getClientName(),
newNode.getId());
而后会在DFSClient#create方法中在DFSOutputStream对象创建完成之后开启租约或者进行租约的跟新
// 开启文件租约或者续约,这个过程中会使用rpc访问NameNodeRpcServer,
// 最终使用LeaseManager完成续约
beginFileLease(result.getFileId(), result);
在此之前,先了解一下hadoop的租约。
租约是Namenode给予租约持有者(LeaseHolder,一般是客户端)在规定时间内拥有文件权限(写文件)的合同。
HDFS文件是write-once-read-many, 并且不支持客户端的并行写操作。 HDFS提供了租约(Lease) 机制保证对HDFS文件的互斥操作来实现这个功能,
在HDFS中, 客户端写文件时需要先从租约管理器(LeaseManager) 申请一个租约,成功申请租约之后客户端就成为了租约持有者, 也就拥有了对该HDFS文件的独占权限,其他客户端在该租约有效时无法打开这个HDFS文件进行操作。 Namenode的租约管理器保存了HDFS文件与租约、 租约与租约持有者的对应关系, 租约管理器还会定期检查它维护的所有租约是否过期。 租约管理器会强制收回过期的租约, 所以租约持有者需要定期更新租约(renew), 维护对该文件的独占锁定。 当客户端完成了对文件的写操作, 关闭文件时, 必须在租约管理器中释放租。
2 LeaseManager
在hadoop之中,所有的租约通过LeaseManager进行管理。
它不仅仅保存了HDFS中所有租约的信息, 提供租约的增、 删、 改、 查方法, 同时还维护了一个Monitor线程定期检查租约是否超时, 对于长时间没有更新租约的文件(超过硬限制时间) , LeaseManager会触发约恢复机制, 然后关闭文件。
在LeaseManager中使用数据结构leases、sortedLeases以及sortedLeasesByPath三个字段保存Namenode中的所有租约;使用Imthread字段保存租约检查线程;使用softLimit字段保存软限制时间(默认是60秒, 不可以配置) ; 使用hadrLimit字段保存硬限制时间(默认是20分钟, 可以配置)
2.1 变量与构造函数
上文提及的变量如下:
private final FSNamesystem fsnamesystem;
// 软限制,默认60s
private long softLimit = HdfsConstants.LEASE_SOFTLIMIT_PERIOD;
// 硬限制,在构造函数中初始化为20min
private long hardLimit;
static final int INODE_FILTER_WORKER_COUNT_MAX = 4;
static final int INODE_FILTER_WORKER_TASK_MIN = 512;
// 租约持有者更新时间
private long lastHolderUpdateTime;
private String internalLeaseHolder;
// Used for handling lock-leases
// Mapping: leaseHolder -> Lease
// 排序map,key为租约持有这holder,value为租约
private final SortedMap<String, Lease> leases = new TreeMap<>();
// Set of: Lease,排序租约,根据租约的最后更新时间进行排序,
// 如果更新时间相同, 则按照租约持有者的字典序保存
private final NavigableSet<Lease> sortedLeases = new TreeSet<>(
new Comparator<Lease>() {
@Override
public int compare(Lease o1, Lease o2) {
if (o1.getLastUpdate() != o2.getLastUpdate()) {
return Long.signum(o1.getLastUpdate() - o2.getLastUpdate());
} else {
return o1.holder.compareTo(o2.holder);
}
}
});
// 根据inodeId->Lease保存租约
// INodeID -> Lease
private final TreeMap<Long, Lease> leasesById = new TreeMap<>();
// 租约检查线程
private Daemon lmthread;
private volatile boolean shouldRunMonitor;
构造函数:最初调用在FSNamesystem对象之中
LeaseManager(FSNamesystem fsnamesystem) {
Configuration conf = new Configuration();
this.fsnamesystem = fsnamesystem;
this.hardLimit = conf.getLong(DFSConfigKeys.DFS_LEASE_HARDLIMIT_KEY,
DFSConfigKeys.DFS_LEASE_HARDLIMIT_DEFAULT) * 1000;
// 更新内部租约的时间戳
updateInternalLeaseHolder();
}
2.2 添加租约
当客户端创建或者追加写入一个文件时,会添加一个租约到租约管理器上,调用LeaseManager.addLease()为该客户端在HDFS文件上添加一个租约。 addLease()方法有两个参数, 其中holder参数保存租约的持有者信息,inodeId 代表文件的id, addLease()方法的实现也非常简单, 先是通过getLease()方法构造租约, 然后在LeaseManager定义的leasesById中添加这个租约的信息。
这个方法是同步[synchronized ]的方法
/**
* Adds (or re-adds) the lease for the specified file.
*/
synchronized Lease addLease(String holder, long inodeId) {
// 获取租约
Lease lease = getLease(holder);
// 如果租约不存在,则重新创建,而后添加到sortedLeases和leases中
// 如果租约已存在,则对其进行更新
if (lease == null) {
lease = new Lease(holder);
leases.put(holder, lease);
sortedLeases.add(lease);
} else {
renewLease(lease);
}
leasesById.put(inodeId, lease);
// 往该租约管理的文件中将增加的这个文件加入其中
lease.files.add(inodeId);
return lease;
}
添加租约除了在添加文件时会被调用,在fsimage和editlog加载时也会调用,即从fsimage将inode信息添加到文件目录树中以及从editlog中将OP_ADD操作添加文件。
2.3 检查租约
检查租约主要通过FsNamesystem.checkLease()实现
INodeFile checkLease(INodesInPath iip, String holder, long fileId)
throws LeaseExpiredException, FileNotFoundException {
String src = iip.getPath();
INode inode = iip.getLas