本文主要是介绍【Alluxio】文件系统锁模型之LockPool,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
Alluxio设计LockPool的主要目的是:
作为保存锁的一个资源池、不让任何正在使用的锁entries被evict掉、超过某个配置的高水位时后台线程evict那些没有使用的资源。
这个池子设计的很好,我们从这个LockPool的设计里也可以学习到如何自己设计一个资源池。
关键词:
存储Lock的池子(底层是个Map)、负载因子、evict线程、低水位、高水位。
定义LockPool里存储的实体资源类:Resource。
看到Resource类里有两个关键成员变量:一个是可重入的读写锁、另外一个是原子整数类型的引用次数。
其中读写锁是资源的实体,引用次数代表了当前实体被多少个线程使用。只有引用次数为0时资源才能被清理线程清理掉。
/*** Resource containing the lock and other information to be stored in the pool.*/private static final class Resource {private final ReentrantReadWriteLock mLock;private volatile boolean mIsAccessed;private AtomicInteger mRefCount;private Resource(ReentrantReadWriteLock lock) {mLock = lock;mIsAccessed = false;mRefCount = new AtomicInteger(1);}}
LockPool存储Resource实体的数据结构定义
public class LockPool<K> implements Closeable {// 存储Resource的Map,根据key获取对应的锁资源,会被初始化成ConcurrentHashMap类型,线程安全private final Map<K, Resource> mPool;// 通过key对应的Resource在mPool里不存在,使用这个mDefaultLoader去生成Resource对象放进mPool映射里.private final Function<? super K, ? extends ReentrantReadWriteLock> mDefaultLoader;// 低水位private final int mLowWatermark;// 高水位private final int mHighWatermark;// 下面的成员变量都跟evictor相关,清理无用Resource private final Lock mEvictLock = new ReentrantLock();private final Condition mOverHighWatermark = mEvictLock.newCondition();private final ExecutorService mEvictor;private final Future<?> mEvictorTask;
}
上面是成员变量。看下依据key(一般是inode id)获取Resource的源码,见LockPool#getResource方法:
// 参数是泛型,一般外层传的是Long类型的inode id,可以理解成文件idprivate Resource getResource(K key) {// key不允许为nullPreconditions.checkNotNull(key, "key can not be null");// 使用Map接口的compute方法对(k,v)进行重映射。// 1、如果key存在于mPool中,则置资源对象的mIsAccessed为true,并把mRefCount自增1后返回。// 2、如果mPool中不存在key,则new Resource返回。 关于mDefaultLoader的逻辑我们后面看Resource resource = mPool.compute(key, (k, v) -> {if (v != null && v.mRefCount.incrementAndGet() > 0) {// If the entry is to be removed, ref count will be INT_MIN, so incrementAndGet will < 0.v.mIsAccessed = true;return v;}return new Resource(mDefaultLoader.apply(k));});// 如果当前池子里的资源数超过了高水位if (mPool.size() > mHighWatermark) {if (mEvictLock.tryLock()) {try {// Condition对象发出信号,唤醒await的evictor线程,进行清理工作。mOverHighWatermark.signal();} finally {mEvictLock.unlock();}}}// 返回当前资源。return resource;}
看下mDefaultLoader的逻辑,也就是说怎么把一个(k,v)添加到mPool里的:
mDefaultLoader是个Function, 第一个泛型是输入类型,第二个泛型是期望的输出类型。
private final Function<? super K, ? extends ReentrantReadWriteLock> mDefaultLoader;
在InodeLockManager类里,初始化LockPool时,会传入mDefaultLoader参数。如下所示:
private final LockPool<Long> mInodeLocks =new LockPool<>((key) -> new ReentrantReadWriteLock(),Configuration.getInt(PropertyKey.MASTER_LOCK_POOL_INITSIZE),Configuration.getInt(PropertyKey.MASTER_LOCK_POOL_LOW_WATERMARK),Configuration.getInt(PropertyKey.MASTER_LOCK_POOL_HIGH_WATERMARK),Configuration.getInt(PropertyKey.MASTER_LOCK_POOL_CONCURRENCY_LEVEL));
因此mDefaultLoader是:
(key) -> new ReentrantReadWriteLock()
结合Map的compute函数,当mPool里不存在key时,就用key初始化一个ReentrantReadWriteLock,然后put(key, new ReentrantReadWriteLock())。
常用方法:
get
tryGet
getRawReadWriteLock
LockPool的使用
MetadataSyncLockManager、InodeLockManager。
这里我们先只关注InodeLockManager。
InodeLockManager主要职责就是负责管理inode locking相关事项。
其内部有两个LockPool,一个是用于inode lock的LockPool<Long> mInodeLocks
、
另一个是用于edge locks的LockPool<Edge> mEdgeLocks
。
看下加锁逻辑:InodeLockManager#lockInode。
返回的是个RWLockResource类对象。
// LockMode有读、写两种模式public RWLockResource lockInode(InodeView inode, LockMode mode, boolean useTryLock) {// 从LockPool里根据inode id去get锁资源。return mInodeLocks.get(inode.getId(), mode, useTryLock);}
这篇关于【Alluxio】文件系统锁模型之LockPool的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!