• HBase行锁


    1 行锁简介

    在事务特性方面,hbase只支持单row的事务,不能保证跨row(cross-row)的事务。hbase通过行锁来实现单row事务。客户端进行操作时,可以显式对某一个行加锁,但是大部分情况下是没有必要的,因为如果没有显式的加行锁,hbase在内部会默认的加行锁。为了描述方便,用户显式加的行锁简称为显式行锁。

       

    源码1:客户端使用显式行锁的示例代码

    HTable table = new HTable(config, tablename);

    RowLock lock = table.lockRow(rowkey);//首先获得rowkey对应的行锁,可能阻塞

    Put p1 = new Put(rowkey,lock);    

    p1.add(cf, qua,value); //在行rowkey上,写数据

    table.put(p1);

    table.unlockRow(lock);//解锁

       

    hbase不推荐客户端显式加行锁,因为可能造成regionserver死锁。并且现在正就"客户端不支持显式加行锁"进行讨论。

    引自hbase官网:

    RowLocks are still in the client APIhowever they are discouraged because if not managed properly thesecan lock up the RegionServers. There is an oustanding ticketHBASE-2332 to remove this feature from the client.

       

    2 实现行锁

    如下图所示,客户端看到的行锁由两部分组成:rowkey和外部锁id。外部锁id是向regionserver申请行锁时,由regionserver返回的。外部锁id在对应的regionserver上全局唯一。

    图1 行锁的定义

      如图2所示。一个regionserver对应一个HRegionServer实例,一个分片对应一个HRegion实例。

    在HregionServer中提供两个接口lockRow和unlockRow,供客户端显式加锁和解锁。rowlocks存储了<客户端所见的锁id,内部锁id>的映射关系。因为内部锁id在分片内部是唯一的,但是在regionserver上不唯一,所以需要这个映射关系。

     在HRegion中,lockedRows记录了所有已经加锁的行,lockIds记录了映射关系<内部锁id,rowkey>。

    图2

       

    为了处理客户端忘记释放锁的情况,显式行锁有一个租约(即有效期,默认是60秒,可配置,对应的配置项是hbase.regionserver.lease.period),如果显式行锁超过了租约时间,regionserver会自动释放行锁。在租约时间内,如果又收到了在这个行上的写请求,会延长租约时长。

     

    加显式行锁的算法

    1 判断目标行是否已经加锁,如果已经加锁,则等待至解锁。

    while (lockedRows.contains(row))

        lockedRows.wait();

    2 分配内部锁id,记为lockId。并把映射关系<lockIds,row>加入至lockIds。lockId在region内是唯一的。

        保证内部锁id唯一性的常规的解决办法是:找出一个没有使用过的id,然后分配给行锁。hbase采用的方法比较有意思:在内部维护一个id生成器,每次分配时,把id生成器的值分配给行锁,并且id生成器自增1。如果没有出现冲突(冲突:当前分配的id不唯一),分配完成。 如果出现冲突,为id生成器赋一随机值,然后重复的上面的流程,直到没有冲突,见源码2。

        方法对比:如果使用常规方法,必然要使用到锁来进行并发控制,锁的开销还是比较大的。hbase认为出现冲突的可能性非常小,这种方法的开销应该远小于常规方法。这种提供性能的方法值得学习。

       

    源码2:分配内部锁id。 见HRegion. internalObtainRowLock()

    byte[] prev = null;

    Integer lockId = null;

    do

    {

    lockId = new Integer(lockIdGenerator++);//生成锁id

    prev = lockIds.put(lockId, row);

    if (prev != null) //如果lockId已经分配了,即发生冲突

    {

    lockIds.put(lockId, prev);// 恢复被破坏的映射关系

    lockIdGenerator = rand.nextInt();//生成新的锁id

    }

    } while (prev != null);

       

    源码:添加映射关系

    lockIds.put(lockId, row)

       

    3 把目标行加入至lockedRows。

    lockedRows.add(row);

    4 为行锁分配外部锁id,这个id在regionserver内是唯一的。

        lockId = rand.nextLong();//这种方法会给hbase带来问题,见第三节

    5 在行锁上添加行锁监听器。

    行锁监听器的主要功能是处理行锁的租约,租约过期时,自动释放行锁,收到新的写请求延长租约时长。

       

    下图是实现行锁的类图。

    图3 实现行锁类图

    3 设计缺陷

    在加显式行锁算法的第4步中,因为Random对象可能产生重复的值,所以分配的外部锁id可能不唯一。

    通过修改regionserver的代码,模拟生成重复锁id的情况,测试流程如下:

    1修改regionserver的代码使得每次分配的外部锁id都为999

    2线程1为rowkey1申请行锁;线程2为rowkey2申请行锁。线程1和线程2同时并发运行。

    测试发现只要只要rowkey1对应的行锁不释放,线程2会永远阻塞。

    修正方法:保证外部锁id的唯一性。

  • 相关阅读:
    JBuilder链接sql server数据库
    各种数据库连接代码(java)
    各种数据库连接代码的测试类(java)
    简单数据查询语句
    Oracle卸载
    Java字符串转换
    静态类示例
    授权对象的检查
    BAPI
    clear、REFRESH、free区别
  • 原文地址:https://www.cnblogs.com/cl1024cl/p/6205173.html
Copyright © 2020-2023  润新知