HBase行锁RowLock源码分析(1)

来源:互联网 发布:淘宝sele是正品吗 编辑:程序博客网 时间:2024/06/07 06:13

概论

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。
以上我们看了一下加锁的实现,至于释放锁,基本上就是一个反向过程,这里就不描述了。