HBase源代码分析1 - Row Lock

  1. Row lock

概论


Google在那篇著名的Bigtable论文中提到,对Bigtable一行的读写是原子的(无论涉及到几个column),这样做的好处就是简化了并发机制,用户也能很容易理解。Bigtable本来也打算实现一个通用的transaction机制,但是最终他们发现大多数用户要的其实只是基于某一行的transaction,所以实现了RowLock,这大大的简化了系统的复杂度,提高了性能。由此可以看出,对用户的需求并不能笼统的接受,要善于发现需求背后的问题,多问一个为什么,那才是真正用户的渴望或者pain point.

由于特定的rowkey只可能属于某一个region server来处理,所以实现这样的锁,并不需要跨机器,只需要在单个JVM中实现,对系统的性能影响有限。

客户端

先从客户端来看看我们怎么用RowLock.

HTable table = new HTable(hbaseConfig, tableName);
//get a row lock from table object
RowLock rl = table. lockRow ("test".getBytes());
Random r = new Random();
    
List<Put> puts = new ArrayList<Put>();
for (int i = 1; i <= insertCount; ++i) {
    byte[] rowkey = new byte[32];
    r.nextBytes(rowkey);
    r.nextBytes(value);
    Put p = new Put(rowkey, rl ); //all the inserting will use the lock
    p.add(f1, null, value);
    p.add(f2, null, value);
    p.add(f3, null, value);
    p.add(f4, null, value);
    puts.add(p);
    if (i % 1000 == 0) {
        table.put(puts);
        puts.clear();
    }
}
table. unlockRow (rl);


这里我们首先调用了HTable的lockRow方法,然后把它传给了Put对象,这样在我们整个插入的过程就能使用我们定义的行锁。
如果没有传给put对象这个锁,put在插入每一行的时候都会生成一个RowLock。
很自然,我们想看看RowLock对象是什么样子的。

//Row Lock Defination
public class RowLock {
  private byte [] row = null;
  private long lockId = -1L;

  ...
}

没有任何特别的地方,只是一个Plain Object。含有目标rowid和一个long的lock ID.

服务器端

 


Row Lock是由具体的Region Server来负责,所以我们从HRegionServer类看起。

public class HRegionServer implements HConstants, HRegionInterface,
    HBaseRPCErrorHandler, Runnable, Watcher{
public long lockRow (byte [] regionName, byte [] row)
  throws IOException {
    //...
    try {
      HRegion region = getRegion(regionName);
      Integer r = region. obtainRowLock (row);
      long lockId = addRowLock (r,region);
      LOG.debug("Row lock " + lockId + " explicitly acquired by client");
      return lockId;
    } catch (Throwable t) {
      throw convertThrowableToIOE(cleanup(t,
        "Error obtaining row lock (fsOk: " + this.fsOk + ")"));
    }
  }
}

这里主要有两个主要的步骤,一个是调用HRegion的obtainRowLock生成这个锁,然后调用addRowLock记录这个锁和region关系。

// obtainRowLock
public class HRegion{
private final Map<Integer, byte []> locksToRows =  new ConcurrentHashMap<Integer, byte []>();
public Integer obtainRowLock(final byte [] row) throws IOException {
    checkRow(row);
    splitsAndClosesLock.readLock().lock();
    try {
      if (this.closed.get()) {
        throw new NotServingRegionException("Region " + this + " closed");
      }
      Integer key = Bytes.mapKey(row);
      synchronized (locksToRows) {
        while (locksToRows.containsKey(key)) {
          try {
            locksToRows.wait();
          } catch (InterruptedException ie) {
            // Empty
          }
        }
        locksToRows.put(key, row);
        locksToRows.notifyAll();
        return key;
      }

    } finally {
      splitsAndClosesLock.readLock().unlock();
    }
  }
}

以上主要做法就是使用一个Map:locksToRows 来记录已经加锁的row。如果已经有人拿到这个锁,其他用户就会在此等待。
注意这里的wait的标准用法

synchronized(object){
    while(some condition){
        object.wait();
    }
    //get the lock,do something now
}

接着我们看看addRowLock 的实现部分。

//Add row Lock
public class HRegionServer implements HConstants, HRegionInterface,
    HBaseRPCErrorHandler, Runnable, Watcher{
Map<String, Integer> rowlocks =
    new ConcurrentHashMap<String, Integer>();
private Leases leases ;

this.leases = new Leases(
        conf.getInt("hbase.regionserver.lease.period", 60 * 1000),
        this.threadWakeFrequency);

protected long addRowLock(Integer r, HRegion region) throws LeaseStillHeldException {
    long lockId = -1L;
    lockId = rand.nextLong();
    String lockName = String.valueOf(lockId);
    synchronized(rowlocks) {
      rowlocks.put(lockName, r);
    }
    this.leases.
      createLease(lockName, new RowLockListener (lockName, region));

    return lockId;
  }
}

这里的关键部分是创建一个lease对象,用于过期来执行某些操作。默认的timeout是60s,也就是说这个锁只能维持60s,过期会由RowLockListener来处理,这样就为了防止用户忘记释放这个锁,造成系统hang的问题,当然你可以通过设定hbase.regionserver.lease.period来改变这个值。接下来看看RowkeyListen干了什么

//RowLockListener
private class RowLockListener implements LeaseListener {
    private final String lockName;
    private final HRegion region;

    RowLockListener(final String lockName, final HRegion region) {
      this.lockName = lockName;
      this.region = region;
    }

    public void leaseExpired () {
      LOG.info("Row Lock " + this.lockName + " lease expired");
      Integer r = null;
      synchronized(rowlocks) {
        r = rowlocks.remove(this.lockName);
      }
      if(r != null) {
        region.releaseRowLock(r);
      }
    }
  }

可以看到RowLockListener实现了leaseExpired方法,用来在lease过期的时候来释放这个Row Lock。
以上我们看了一下加锁的实现,至于释放锁,基本上就是一个反向过程,这里就不描述了。

你可能感兴趣的:(header,hbase,Integer,byte,border,代码分析)