原文地址:http://wangneng-168.iteye.com/blog/1983657
博文说明:1、研究版本hbase0.94.12;2、贴出的源代码可能会有删减,只保留关键的代码。
hbase的锁是采用jdk的ReentrantReadWriteLock类实现
一、HRegion有两种锁:lock、updatesLock,这两种锁均是ReentrantReadWriteLock类的实例,基本上所有的region操作均需要获取lock的read共享锁,在获取了lock的read锁后,如果是增加或者删除等影响数据内容的操作则还需要获取updatesLock的read锁。
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方法需要持有updatesLock的write锁,由此可见,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锁,该锁涉及如下的方法:
其中snapshot和clearSnapshot方法要求持有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比较均衡时,可能单个region的memstore均没有达到阀值而全局的memstore以及达到阀值,这会导致整个regionservice的写、删等修改memstore内存的操作阻塞,并且可能会多生成一些相对较小的storeFile文件,从而又导致增加compact的次数,compact也是比较消耗资源的操作。