hdfs dn锁拆分
一、当前实现及问题
HDFS社区版本中DN内部使用了一把全局锁,volume操作之间的并发能力受到制约。
当前使用的锁为AutoCloseableLock,实际上就是一个ReentrantLock
public class AutoCloseableLock implements AutoCloseable {
private final Lock lock;
public AutoCloseableLock() {
this(new ReentrantLock());
}
public AutoCloseableLock(Lock lock) {
this.lock = lock;
}
public AutoCloseableLock acquire() {
lock.lock();
return this;
}
public void release() {
lock.unlock();
}
@Override
public void close() {
release();
}
//...
涉及的类:
FsDatasetImpl、FsVolumeImpl、ProvidedVolumeImpl、DistBalancer、ReplicaMap、BPServiceActor等
高密度场景下的dn节点,volume越多,原版本带来的锁问题越大:
-
FsDataSet使用全局锁控制volume读写,增加volume个数无疑会增加锁竞争,且会拖慢并行写入的吞吐,降低单机的性能
-
volume增多之后,IBR以及FBR也会相应的增加,这对NN和DN通讯也会带来更大的压力
-
机器磁盘故障时,影响的数据量级比之前增加很多,需要做到快速恢复
二、优化方案
第一阶段 AutoCloseableLock 改为 ReentrantReadWriteLock
AutoCloseableLock直接使用的是ReentrantLock,改进后使用InstrumentedReadWriteLock,底层通过ReentrantReadWriteLock进行封装实现。对一些DN操作可以使用读锁来提高并发(共享资源被一个线程写入时,其他线程不能读取和写入;共享资源可以被多个线程同时读取)。
可使用读锁的相关功能:
1.Replicamap
该类维护副本的集合。一些方法会调用`replicmap .replicas()`(例如`getBlockReports`, `getFinalizedBlocks`, `deepCopyReplica`),并且只以只读方式使用,所以它们也可以切换到使用readLock。
2.Disk Balancer和Directory Scanner
目录扫描仪和磁盘均衡器,只需要读锁。
3.FsDatasetImpl
FsDatasetImpl中的方法getVolume、getStoredBlock、deepCopyReplica、getTmpInputStreams等都可以改用readLock
类图:
BlockPool-Volume加锁用的AutoCloseDataSetLock,继承了 AutoCloseableLock,通过try-with-resources调用,实现自动释放锁
第二阶段 拆锁
针对全局锁的问题,可以考虑做拆锁方案,减少FsDataSet读写数据时引起的锁竞争与锁等待。
从结构上来看,FsDataSet划分数据有两个层面上的聚合,第一个是BlockPool级别,第二个是Volume级别。
对于federation集群来说,一个集群会有大于一个的BlockPool,BlockPool本身会包含着多volume,结构划分如下:
对于这样的结构划分,考虑拆解为BlockPool级别的(父)锁以及Volume级别的(子)锁。
按照场景确定不同的加锁规则:
1.对于一个volume内的数据读写操作,我们需要对该操作加volume级别的读写锁和BlockPool级别的读锁(优化的关键,一个volume内的数据读写,本质上没有改变BlockPool级别的元数据逻辑,因此只需要加读锁)
2.对于多个volume间的数据读写操作,例如dn内部的数据迁移,则需要加BlockPool级别的写锁,此时则不能加volume级别的锁
3.对于BlockPool级别的元数据变更,例如BlockPool清理,BlockPool添加等,这个属于BlockPool级别改动,需要加BlockPool级别的写锁。部分场景下需要加dn进程级别的全局锁
三、社区现状
社区issue:[HDFS-15382] Split one FsDatasetImpl lock to volume grain locks. - ASF JIRA
一共包含 13个子任务,其中9个子任务已解决
done
-
目前社区已经基本完成锁的拆分,包括实现读写锁 AutoCloseDataSetLock、实现管理类 DataSetLockManager、将全局锁拆分成读写锁(其中锁的拆分在社区是逐步完成的,首先是将所有的锁替换成BLOCK_POOL类型的锁,然后再逐步将锁粒度细化成BLOCK_POOL+VOLUME),此外也解决了已知的死锁问题
-
当前已经将社区所有已完成的子任务及其相关依赖的pr合并,进行了读写性能测试。并且在逻辑层面梳理了所有改造相关的加锁流程,排除了死锁的可能
todo
[HDFS-16657] Changing pool-level lock to volume-level lock for invalidation of blocks - ASF JIRA 当一个非常繁忙的节点上出现block invalidation,这个时候DN正在尝试查找块文件的父路径,因此存在池级锁定中的磁盘 I/O。当磁盘变得非常繁忙且 io 等待时间长时,所有待处理线程都会被 pool 级锁阻塞,心跳时间高。这个issue有被讨论,下一步可能有意向去搞