Ozone基于Resource的细粒度化锁管理器实现

前言


众所周知在HDFS中,为了保证元数据更新的一致性,它所使用的是全局锁的模式。不过这在一定模式下会导致激烈的锁竞争的情况发生,尤其当集群规模日趋膨胀的时候,获取锁的这种代价就会变得越来越高。如果在不降低集群请求吞吐量的情况下,我们如何优化这一点呢?一种很自然的想法是将锁的粒度变细,锁粒度变细的一个原则前提是:某些不必要的场合,我们不一定非得要获取到全局锁的程度。本文笔者通过Ozone内部的锁控制器的实现来简单聊聊细粒度锁模式的设计与实现。

全局锁的细粒度拆分化


HDFS之所以采用全局锁的模式,是因为它的元数据是存储于其内部的一个类似map的映射结构中,更为关键的一点是,它并没有对着里面的数据进行partition的分片区分。简单来说,就是当NN在更新其内部元数据的时候,就要把它整个的metadata数据上锁。这个时候,其它打算修改metadata的操作请求都会被拒之门外。

但其实倘若NN能够对其维护的海量元数据进行简单的partition区分,比如根据INodeFile Id值或者文件Path的哈希值等等。那么对于不相关的metadata请求更新,则完全可以获取对应独立的锁,毫无疑问这种模式下集群的concurrency请求处理无疑会提高许多。

因此全局锁粒度的拆分前提的关键一点在于其所保护Resource的可拆分化,不同Resource之间的处理毫不受影响。锁只用来保护特定Resource内的并行请求处理。

OK,如果我们实现的系统内部锁保护Resource满足以上的特征,那么很好我们后面就可以进行基于Resource的锁管理器模型实现了。

Ozone内部基于Resource的锁管理器模型


下面本文以Ozone内部基于Resource的锁管理器实现为例子,聊聊这个细粒度锁的设计实现。

这里有个Ozone的背景,Ozone作为对象存储系统,它的metadata是基于Volume,Bucket,Key的形式进行组织的。简单来说,Ozone内部的保护的Resource其实是Volume和Bucket。我们只需要将锁申请到具体某个Resource(具体某个Volume,Bucket)下即可,Key的操作会被其所属的Bucket锁所保护。当然,如果我们还是采用HDFS全局单一锁模式来做Ozone metadata的一致性控制,也是完全没问题的。

基于上述的实现思路,Ozone内部使用了锁对象池的方式实现了锁管理器模型,锁管理器模型如下所示:
在这里插入图片描述
上面的锁申请过程其实比较简单:

1)Client端向Server端准备发起Resource访问行为,向锁管理器发出指定Resource的锁申请行为。
2)锁管理器向锁对象池申请可用的锁实例,并返回对应此Resource的当前可用的锁。
3)Client拿到这个Resource锁后,进行后续的数据访问操作行为。
4)Client Resource访问结束后,执行了释放锁操作,锁管理器将锁归还到锁池子内。

在这个过程中,对象池的运用是为了避免锁实例的重复创建和销毁行为,达到更高效的复用率。其次锁管理器在其内部会维护一个当前实时的资源与其资源锁的一个mapping映射。

Ozone内部锁管理器实现


上面我们了解了锁管理器的原理实现,下面我们来简单看看其中的代码实现逻辑。

首先Ozone实际创建使用的锁本质还是JDK自带的Reentrant读写锁,不过额外增加了多余锁的引用数:

/**
 * Lock implementation which also maintains counter.
 */
public final class ActiveLock {

  // ActiveLock锁的本质是Reentrant读写锁带上一个reference引用数
  private ReadWriteLock lock;
  private AtomicInteger count;

  /**
   * Use ActiveLock#newInstance to create instance.
   *
   * @param fairness - if true the lock uses a fair ordering policy, else
   * non-fair ordering.
   */
  private ActiveLock(boolean fairness) {
    this.lock = new ReentrantReadWriteLock(fairness);
    this.count = new AtomicInteger(0);
  }

  /**
   * Creates a new instance of ActiveLock.
   *
   * @return new ActiveLock
   */
  public static ActiveLock newInstance(boolean fairness) {
    return new ActiveLock(fairness);
  }

  void readLock() {
    lock.readLock().lock();
  }

  void readUnlock() {
    lock.readLock().unlock();
  }

  void writeLock() {
    lock.writeLock().lock();
  }

  void writeUnlock() {
    lock.writeLock().unlock();
  }

  /**
   * Increment the active count of the lock.
   */
  void incrementActiveCount() {
    count.incrementAndGet();
  }

  /**
   * Decrement the active count of the lock.
   */
  void decrementActiveCount() {
    count.decrementAndGet();
  }

  /**
   * Returns the active count on the lock.
   *
   * @return Number of active leases on the lock.
   */
  int getActiveLockCount() {
    return count.get();
  }

  /**
   * Resets the active count on the lock.
   */
  void resetCounter() {
    count.set(0);
  }

  @Override
  public String toString() {
    return lock.toString();
  }
}

然后这里使用了第三库的对象池方法,进行一个方法继承操作,

/**
 * Pool factory to create {@code ActiveLock} instances.
 */
public class PooledLockFactory extends BasePooledObjectFactory<ActiveLock> {

  private boolean fairness;

  PooledLockFactory(boolean fair) {
    this.fairness = fair;
  }
  @Override
  public ActiveLock create() throws Exception {
    return ActiveLock.newInstance(fairness);
  }

  @Override
  public PooledObject<ActiveLock> wrap(ActiveLock activeLock) {
    return new DefaultPooledObject<>(activeLock);
  }

  @Override
  public void activateObject(PooledObject<ActiveLock> pooledObject) {
    // 锁实例对象重新激活使用时,只需进行引用计数的重置即可
    pooledObject.getObject().resetCounter();
  }
}

下面是最终LockManager的实现,

/**
 * Manages the locks on a given resource. A new lock is created for each
 * and every unique resource. Uniqueness of resource depends on the
 * {@code equals} implementation of it.
 */
public class LockManager<R> {

  private static final Logger LOG = LoggerFactory.getLogger(LockManager.class);

  // 当前使用的活跃锁,对应其锁保护Resource的映射
  private final Map<R, ActiveLock> activeLocks = new ConcurrentHashMap<>();
  // 锁对象池
  private final GenericObjectPool<ActiveLock> lockPool;

  ...
  /**
   * Creates new LockManager instance with the given Configuration.
   *
   * @param conf Configuration object
   * @param fair - true to use fair lock ordering, else non-fair lock ordering.
   */
  public LockManager(final Configuration conf, boolean fair) {
    // 锁池子的初始化
    lockPool =
        new GenericObjectPool<>(new PooledLockFactory(fair));
    lockPool.setMaxTotal(-1);
  }
  ...
}

这里我们重点来看锁的申请和释放的过程,

public class LockManager<R> {
...
   public void writeLock(final R resource) {
    acquire(resource, ActiveLock::writeLock);
  }
 
   private void acquire(final R resource, final Consumer<ActiveLock> lockFn) {
    lockFn.accept(getLockForLocking(resource));
  }

  /**
   * Returns {@link ActiveLock} instance for the given resource,
   * on which the lock can be acquired.
   *
   * @param resource on which the lock has to be acquired
   * @return {@link ActiveLock} instance
   */
  private ActiveLock getLockForLocking(final R resource) {
    /*
     * While getting a lock object for locking we should
     * atomically increment the active count of the lock.
     *
     * This is to avoid cases where the selected lock could
     * be removed from the activeLocks map and returned to
     * the object pool.
     */
    return activeLocks.compute(resource, (k, v) -> {
      final ActiveLock lock;
      try {
        if (v == null) {
          // 从锁池子中借锁,如果当前对应Resource目前没有锁
          lock = lockPool.borrowObject();
        } else {
          // 否则从当前的mapping中获取之前已经从锁池中借出的锁
          lock = v;
        }
        // 增加此锁的引用计数值
        lock.incrementActiveCount();
      } catch (Exception ex) {
        LOG.error("Unable to obtain lock.", ex);
        throw new RuntimeException(ex);
      }
      return lock;
    });
  }

  public void writeUnlock(final R resource)
      throws IllegalMonitorStateException {
    release(resource, ActiveLock::writeUnlock);
  }

  private void release(final R resource, final Consumer<ActiveLock> releaseFn)  {
    // 取出当前Resource对应的活跃锁实例
    final ActiveLock lock = getLockForReleasing(resource);
    // 执行锁对象的释放锁操作
    releaseFn.accept(lock);
    // 减少锁的引用数
    decrementActiveLockCount(resource);
  }
  
  private void decrementActiveLockCount(final R resource) {
    activeLocks.computeIfPresent(resource, (k, v) -> {
      v.decrementActiveCount();
      if (v.getActiveLockCount() != 0) {
        return v;
      }
      // 如果锁的当前引用数为0,则说明锁没有被其它Client所持有,进行锁对象的归还
      lockPool.returnObject(v);
      return null;
    });
  }
...
}

以上的LockManager实际的Resource可以根据具体使用场景做具体化的定义,不一定非得是具体某个类的实例,一个简单的字符串代表一个唯一的Resource也是没有问题的。

锁的多层级约束处理


在某些场景中,还会涉及到多层级锁的约束情况。比如在Ozone中,涉及到Bucket的创建操作的时候,需要事先拿到Bucket所属Volume的Volume锁。这是为了避免在创建过程中,其它请求对Volume进行操作,导致Bucket在Volume内的创建操作出现问题。又或者,比如未来支持Bucket的rename操作,为了保证rename操作在Volume下的原子性,我们就得有Volume锁这样高一级锁的约束了。

OK,以上就是本文所要阐述的内容了,感兴趣的同学可阅读引用链接,来了解Ozone LockManager全部代码实现的逻辑。

引用


[1].https://github.com/apache/hadoop-ozone/blob/master/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/lock/LockManager.java
[2].https://github.com/apache/hadoop-ozone/blob/master/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/lock/ActiveLock.java
[3].https://github.com/apache/hadoop-ozone/blob/master/hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/lock/PooledLockFactory.java

©️2020 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值