zoukankan      html  css  js  c++  java
  • 细粒度锁的实现之分级锁的设计实现

    前言


    在分布式系统中,想必我们经常会看到锁的应用来保证操作的原子性,使用较简单的例如对象锁,单一锁等等,再高级一点的例如读写锁等等。但是不论是单一锁或者读写锁,在使用上都具有一定的互斥性。这里的互斥性指的是当某个锁持有者持有当前的锁之后,其它线程必须进行阻塞等待操作。这种行为在具有很高workload的系统中,代价还是比较高的。从更深层次来看待这种锁,它是一种单一粒度,较为粗粒度的锁设计模式。那么在实际的应用中,我们是否能够将这种单一锁进行优化呢,使之用起来能够更为的高效。本文笔者将要讲述的将是粗粒度锁的细粒度化改造,改造的实现方式为分级锁的实现。

    锁细粒度化改造的好处


    为什么我们这么强调锁的细粒度化改造呢?相比于粗粒度锁,它在使用上能够带给系统怎样的帮助呢?

    说到这里我们不得不谈到粗粒度锁在使用上的一些弊端,典型的一点比如它会阻塞一些毫无关联的请求操作的处理。比如某个存储系统在根目录下有A,B两个目录,为了保证系统处理请求操作的原子性,我们用锁来做其中的控制。如果我用1个锁来做,则会有一下两种情况发生:

    • 系统在执行A目录下的请求操作,持有锁状态,B目录的所有操作被block住。
    • 系统在执行B目录下的请求操作,持有锁状态,A目录的所有操作被block住。

    但其实上面的场景系统在持有锁的情况去保护A目录的并发修改却同样block住了B目录的操作,这其实是可以避免的,我们完全可以让这2个目录的相关操作并发地执行,然后再用2个对应锁去保证这2个目录空间下的请求操作。这样的话,系统的请求吞吐量将会上升很多。

    在上面的例子中从一个全局单一锁到2个命名空间目录单独锁的拆分,就是锁细粒化改造的一个简单例子。下面本文将要讲述的分级锁的设计部分也是采用了上述的思路,但是额外多了部分的优化改造,使之更适用于实际系统的使用。

    分级锁的设计和实现


    本节将要介绍的分级锁的主要特点在于它包含有多个层级的锁,在这里我们以两级锁为例,在此锁内,包含有2个级别锁:

    • Top锁
    • Child锁

    在分布锁中,遵守以下规则:

    在操作开始前,必须先申请得到Top锁来准备获取Child锁,在获取得到Child锁之后,可以再释放Top锁。这里的Child锁,可以理解为就是每个分区锁。这里Top锁的目的是为了保证获取各个分区锁的原子性。

    分级锁原型定义如下:

     /**
     * LatchLock controls two hierarchical Read/Write locks:
     * the topLock and the childLock.
     * Typically an operation starts with the topLock already acquired.
     * To acquire child lock LatchLock will
     * first acquire the childLock, and then release the topLock.
     */
    public abstract class LatchLock<C> {
      // Interfaces methods to be defined for subclasses
      /** @return true topLock is locked for read by any thread */
      protected abstract boolean isReadTopLocked();
      /** @return true topLock is locked for write by any thread */
      protected abstract boolean isWriteTopLocked();
      protected abstract void readTopdUnlock();
      protected abstract void writeTopUnlock();
    
      protected abstract boolean hasReadChildLock();
      protected abstract void readChildLock();
      protected abstract void readChildUnlock();
    
      protected abstract boolean hasWriteChildLock();
      protected abstract void writeChildLock();
      protected abstract void writeChildUnlock();
    
      protected abstract LatchLock<C> clone();
    
      // Public APIs to use with the class
      public void readLock() {
        // 在获取child锁后,可以释放top锁
        readChildLock();
        readTopdUnlock();
      }
    
      public void readUnlock() {
        readChildUnlock();
      }
    
      public void writeLock() {
        // 在获取child锁后,可以释放top锁
        writeChildLock();
        writeTopUnlock();
      }
    
      public void writeUnlock() {
        writeChildUnlock();
      }
    }
    

    在分级锁中,尽管Top锁会是同一个,但是假设我们获取的不同的Child锁,其实不会收到Top锁其它线程持有的情况。因为其它Child锁被lock之后,Top锁就释放了,这样的话其它分级锁的Child锁的获取就不会受到影响了。
    在这里Top锁扮演的还是之前全局同一锁的角色,但是所锁住的对象是每个分区的实例而不是每一个具体的操作了。

    这里我们以典型的HDFS FSN全局单一锁为例作为Top锁的分级锁实现:

     public class INodeMapLock extends LatchLock<ReentrantReadWriteLock> {
        Logger LOG = LoggerFactory.getLogger(INodeMapLock.class);
    
        private ReentrantReadWriteLock childLock;
    
        INodeMapLock() {
          this(null);
        }
    
        private INodeMapLock(ReentrantReadWriteLock childLock) {
          assert namesystem != null : "namesystem is null";
          this.childLock = childLock;
        }
    
        @Override
        protected boolean isReadTopLocked() {
          return namesystem.getFSLock().isReadLocked();
        }
    
        @Override
        protected boolean isWriteTopLocked() {
          return namesystem.getFSLock().isWriteLocked();
        }
    
        @Override
        protected void readTopdUnlock() {
          namesystem.getFSLock().readUnlock("INodeMap", false);
        }
    
        @Override
        protected void writeTopUnlock() {
          namesystem.getFSLock().writeUnlock("INodeMap", false, false);
        }
    
        @Override
        protected boolean hasReadChildLock() {
          return this.childLock.getReadHoldCount() > 0 || hasWriteChildLock();
        }
    
        @Override
        protected void readChildLock() {
          // LOG.info("readChildLock: thread = {}, {}", Thread.currentThread().getId(), Thread.currentThread().getName());
          this.childLock.readLock().lock();
          namesystem.getFSLock().addChildLock(this);
          // LOG.info("readChildLock: done");
        }
    
        @Override
        protected void readChildUnlock() {
          // LOG.info("readChildUnlock: thread = {}, {}", Thread.currentThread().getId(), Thread.currentThread().getName());
          this.childLock.readLock().unlock();
          // LOG.info("readChildUnlock: done");
        }
    
        @Override
        protected boolean hasWriteChildLock() {
          return this.childLock.isWriteLockedByCurrentThread();
        }
    
        @Override
        protected void writeChildLock() {
          // LOG.info("writeChildLock: thread = {}, {}", Thread.currentThread().getId(), Thread.currentThread().getName());
          this.childLock.writeLock().lock();
          namesystem.getFSLock().addChildLock(this);
          // LOG.info("writeChildLock: done");
        }
    
        @Override
        protected void writeChildUnlock() {
          // LOG.info("writeChildUnlock: thread = {}, {}", Thread.currentThread().getId(), Thread.currentThread().getName());
          this.childLock.writeLock().unlock();
          // LOG.info("writeChildUnlock: done");
        }
    
        @Override
        protected LatchLock<ReentrantReadWriteLock> clone() {
          return new INodeMapLock(new ReentrantReadWriteLock(false)); // not fair
        }
      }
    

    在使用分级锁时,如果遇到可能需要获取多分区(Child)锁时,则要进行多个分区Child锁的获取,之后再释放Top锁,操作方法如下:

      /**
       * 获取多Child锁,keys为write操作涉及到的相关分区实例
       */ 
      public void latchWriteLock(K[] keys) {
        LatchLock<?> pLock = null;
        for(K key : keys) {
          pLock = getPartition(key).partLock;
          pLock.writeChildLock();
        }
        assert pLock != null : "pLock is null";
        pLock.writeTopUnlock();
      }
    

    在上面的例子中,遵循的规则如下:

    每个partition对应一个partition锁(就是本文提到的分级锁),每个partition锁包含Child锁和Top锁,Top锁是所有partition锁共用的一个锁,Child锁则是每个Partition独有的。所以我们可看到,分级锁在多partition情况下可以很好地得到运用。

    本文阐述的分级锁的设计以及实现参考了目前Hadoop社区基于metadata partition的NN改造相关设计,感兴趣的同学可前往引用链接处继续学习了解。

    引用


    [1].https://issues.apache.org/jira/browse/HDFS-14703 . NameNode Fine-Grained Locking via Metadata Partitioning

  • 相关阅读:
    mybatis 入门基础
    spring学习总结
    综合练习:词频统计
    组合数据类型综合练习
    Python基础综合练习
    熟悉常用的Linux操作
    1.大数据概述
    C程序语法(无左递归)
    文法规则
    实验一词法分析报告
  • 原文地址:https://www.cnblogs.com/bianqi/p/12183517.html
Copyright © 2011-2022 走看看