hbase锁机制

博文说明:1、研究版本hbase0.94.12;2、贴出的源代码可能会有删减,只保留关键的代码。

 

  hbase的锁是采用jdkReentrantReadWriteLock类实现

 

  一、HRegion有两种锁:lockupdatesLock,这两种锁均是ReentrantReadWriteLock类的实例,基本上所有的region操作均需要获取lockread共享锁,在获取了lockread锁后,如果是增加或者删除等影响数据内容的操作则还需要获取updatesLockread锁。

 

  1、HRegion的lock锁影响如下的操作: 


  其中关闭region的
doClose方法需要持有lock的write锁,startBulkRegionOperation在进行跨列簇处理时也要求持有lock的writ锁,其它均只需持有lock的read锁,startBulkRegionOperation在使用工具LoadIncrementalHFiles装载通过HFileOutputFormat输出的HFile文件到一个已经存在的表时执行的方法,因此执行该操作最好是在该region空闲时执行。

lock锁影响的startRegionOperation()方法又影响如下操作:


   由以上两图可以分析出,一旦执行了region的关闭操作或者通过工具LoadIncrementalHFiles向已经存在的表装载跨列簇的数据时会阻塞,尤其是close方法,通过锁的实现可以确保当close发生时拒绝所有的region请求,避免出现关闭过程中再执行其它任何操作,而出现错误服务的问题。

 

2、HRegion的updatesLock影响如下操作: 


 

其中只有flush时执行的internalFlushcache方法需要持有updatesLockwrite锁,由此可见,flush方法会阻塞所有的可能改动memstore内容的操作。

put等需要改动memstore内容的操作均需要持有read和updatesLock锁,以put方法为例说明,源代码如下:

public void put(Put put, Integer lockid, boolean writeToWAL)throws IOException {

    startRegionOperation();  //获取lock

    this.writeRequestsCount.increment();

    this.opMetrics.setWriteRequestCountMetrics(this.writeRequestsCount.get());

    try {

      byte [] row = put.getRow();

Integer lid = getLock(lockid, row, true);

      try {

        internalPut(put, put.getClusterId(), writeToWAL);

      } finally {

        if(lockid == null) releaseRowLock(lid);

      }

    } finally {

      closeRegionOperation(); //释放lock

    }

  }

 

private void internalPut(Put put, UUID clusterId, boolean writeToWAL) throws IOException {

    lock(this.updatesLock.readLock()); //获取updatesLock

    try {

      checkFamilies(familyMap.keySet());

      checkTimestamps(familyMap, now);

      updateKVTimestamps(familyMap.values(), byteNow);

      if (writeToWAL) {

        addFamilyMapToWALEdit(familyMap, walEdit);

        walEdit.addClusterIds(put.getClusterIds());

        this.log.append(regionInfo, this.htableDescriptor.getName(),

            walEdit, clusterId, now, this.htableDescriptor);

      } else {

        recordPutWithoutWal(familyMap);

      }

 

      long addedSize = applyFamilyMapToMemstore(familyMap, null);

      flush = isFlushSize(this.addAndGetGlobalMemstoreSize(addedSize));

    } finally {

      this.updatesLock.readLock().unlock(); //释放updatesLock

    }

  }

 

二、MemStore有一个lock锁,该锁涉及如下的方法:


   其中snapshotclearSnapshot方法要求持有lock的write锁,这两个方法均是在flush memstore期间调用,这两个flush的子过程中会产生的write排它锁会影响到对memstore的所有读写操作,而从hbase读取数据中的第一步就是读取memstore,可见flush memstore是一个比较重的过程,影响读写,而一个flush操作至少会flush整个region,在flush期间,整个region的服务性能均会下降,因此有合适的flush次数和region的大小对性能提升会有所帮助。flush的次数主要受如下配置项影响:

hbase.regionserver.global.memstore.upperLimit

hbase.regionserver.global.memstore.lowerLimit

hbase.hregion.memstore.flush.size

  可以适当调大以上3项配置,但是需要注意的是并不是越大越好,如果全局的lowerLimit和upperLimit过大,则会影响读的性能,如果局部的flush.size过大则会导致一次flush的时间过长,而且当数据写入分散到每个region比较均衡时,可能单个regionmemstore均没有达到阀值而全局的memstore以及达到阀值,这会导致整个regionservice的写、删等修改memstore内存的操作阻塞,并且可能会多生成一些相对较小的storeFile文件,从而又导致增加compact的次数,compact也是比较消耗资源的操作。

猜你喜欢

转载自wangneng-168.iteye.com/blog/1983657
今日推荐