请带着例如以下问题阅读本文。

1、什么是行锁?

2、HBase行锁的原理是什么?

3、HBase行锁是怎样实现的?

4、HBase行锁是怎样应用的?

一、什么是行锁?

我们知道。数据库中存在事务的概念。事务是作为单个逻辑工作单元运行的一系列操作,要么全然地运行,要么全然的不运行。

而事务的四大特点即原子性、一致性、分离性和持久性。当中,原子性首当其冲。那么在HBase内部实现其原子性的重要保证是什么呢?答案就是行锁。

什么是行锁呢?顾名思义。它就是加在行上的一把锁。在它未释放该行前。最起码其它訪问者是无法对该行做改动的,即要改动的话,必须得获得该行的锁才干拥有改动改行数据的权限,这就是行锁的含义。

二、HBase行锁实现原理

HBase行锁是利用Java并发包concurrent里的CountDownLatch(1)来实现的。

它的主要思想就是在server端每一个訪问者单独一个数据处理线程,每一个处理线程针对特定行数据改动时必须获得该行的行锁,而其它client线程想要改动数据的话,必须等待前面的线程释放锁后才被同意,这就利用了Java并发包中的CountDownLatch。CountDownLatch为Java中的一个同步辅助类。在完毕一组正在其它线程中进行的操作之前,它同意一个或多个线程一直等待。

这里,将线程数设置为1,十分巧妙的实现了独占锁的概念。

三、HBase行锁的实现

HBase的行锁主要是通过HRegion的两个内部类实现的,当中一个是RowLock,另外一个是RowLockContext。

我们首先看RowLock这个类。其定义例如以下:

  1. /**
  2. * Row lock held by a given thread.
  3. * One thread may acquire multiple locks on the same row simultaneously.
  4. * The locks must be released by calling release() from the same thread.
  5. *
  6. * 给定线程持有的行锁。
  7. * 一个线程能够同一时候获得同一行上的多个锁。
  8. * 锁必须被同样线程。通过调用release()释放。
  9.  
  10. */
  11. public static class RowLock {
  12. // 行锁上下文,持有锁定的行row、锁持有者线程thread、该行上锁的数目lockCount等内容
  13. @VisibleForTesting final RowLockContext context;
  14. // 行锁是否被释放
  15. private boolean released = false;
  16.  
  17. // 构造函数
  18. @VisibleForTesting RowLock(RowLockContext context) {
  19. this.context = context;
  20. }
  21.  
  22. /**
  23. * Release the given lock. If there are no remaining locks held by the current thread
  24. * then unlock the row and allow other threads to acquire the lock.
  25. * 释放给定的锁。
  26.  
  27. 假设当前线程不再持有不论什么锁,那么对该行解锁并同意其它线程获得锁。
  28. * @throws IllegalArgumentException if called by a different thread than the lock owning thread
  29. */
  30. public void release() {
  31. if (!released) {
  32. context.releaseLock();
  33. released = true;
  34. }
  35. }
  36. }

通过上述源代码我们能够看到。行锁RowLock有两个成员变量。RowLockContext类型的行锁上下文context和布尔类型的行锁是否释放released。当中,行锁上下文context持有锁定的行row、锁持有者线程thread、该行上锁的数目lockCount等内容。而且,利用java的concurrent并发包里的CountDownLatch(1)实现了线程对对象的独占锁。

RowLockContext的源代码例如以下:

  1. // 行锁上下文,包含指定的行row,同步计数器latch,锁的数目lockCount和线程thread
  2. @VisibleForTesting class RowLockContext {
  3. private final HashedBytes row;// 行
  4. private final CountDownLatch latch = new CountDownLatch(1);//
  5. private final Thread thread;
  6. private int lockCount = 0;
  7.  
  8. // 构造方法
  9. RowLockContext(HashedBytes row) {
  10. this.row = row;
  11. this.thread = Thread.currentThread();
  12. }
  13.  
  14. // 推断是否为当前线程相应的行锁上下文
  15. boolean ownedByCurrentThread() {
  16. return thread == Thread.currentThread();
  17. }
  18.  
  19. RowLock newLock() {
  20. lockCount++;
  21. return new RowLock(this);
  22. }
  23.  
  24. void releaseLock() {
  25. if (!ownedByCurrentThread()) {
  26. throw new IllegalArgumentException("Lock held by thread: " + thread
  27. + " cannot be released by different thread: " + Thread.currentThread());
  28. }
  29. lockCount--;
  30. if (lockCount == 0) {
  31. // no remaining locks by the thread, unlock and allow other threads to access
  32. RowLockContext existingContext = lockedRows.remove(row);
  33. if (existingContext != this) {
  34. throw new RuntimeException(
  35. "Internal row lock state inconsistent, should not happen, row: " + row);
  36. }
  37.  
  38. // 同步计数器减1
  39. latch.countDown();
  40. }
  41. }
  42. }

通过源代码我们能够看到,行锁的上下文信息,主要包含行锁相应的行row以及占用该行锁的线程thread。构造RowContext时,仅仅需传入行row就可以,占用的线程则通过Thread.currentThread()获得当前线程。

新加锁时,通过调用newLock()方法就可以实现。首先锁的计数器lockCount加1,然后返回由当前RowContext构造RowLock实例就可以。

释放锁时,通过调用releaseLock()方法就可以实现。首先通过ownedByCurrentThread()方法确保调用releaseLock()方法的当前线程是否和RowContext持有的线程一致,然后。锁的计数器lockCount减1,而且,假设lockCount为0的话。说明不再有操作占用该行,将row相应的行锁从数据结构lockedRows中删除,同意其它线程获得该行的行锁,最后,最重要的一步。latch.countDown(),就可完毕行锁的释放了。

四、HBase行锁的使用

以下,我们看下HBase行锁的使用。在涉及数据变更的操作。比方Put、Delete等中,在对一行数据操作之前。都会调用getRowLockInternal()方法,获得该行数据的行锁。代码例如以下:

  1. /**
  2. * A version of getRowLock(byte[], boolean) to use when a region operation has already been
  3. * started (the calling thread has already acquired the region-close-guard lock).
  4. */
  5. protected RowLock getRowLockInternal(byte[] row, boolean waitForLock) throws IOException {
  6.  
  7. // 构造HashedBytes类型表示的行,rowKey
  8. HashedBytes rowKey = new HashedBytes(row);
  9. // 创建行锁上下文实例,并制定为行rowkey和当前线程拥有
  10. RowLockContext rowLockContext = new RowLockContext(rowKey);
  11.  
  12. // loop until we acquire the row lock (unless !waitForLock)
  13. while (true) {
  14. // 将rowkey与行锁上下文的相应关系加入到Region的数据结构lockedRows中
  15. RowLockContext existingContext = lockedRows.putIfAbsent(rowKey, rowLockContext);
  16. if (existingContext == null) {
  17. // Row is not already locked by any thread, use newly created context.
  18. // 该行已经没有被不论什么线程锁住,使用这个新创建的上下文
  19. break;
  20. } else if (existingContext.ownedByCurrentThread()) {
  21. // Row is already locked by current thread, reuse existing context instead.
  22. // 该行已经被当前线程锁住。复用当前线程之前创建的行锁上下文实例
  23. rowLockContext = existingContext;
  24. break;
  25. } else {
  26. // 该行被其它线程锁住,假设不须要等待锁。直接返回null
  27. if (!waitForLock) {
  28. return null;
  29. }
  30.  
  31. TraceScope traceScope = null;
  32. try {
  33. if (Trace.isTracing()) {
  34. traceScope = Trace.startSpan("HRegion.getRowLockInternal");
  35. }
  36. // Row is already locked by some other thread, give up or wait for it
  37. // 行已经被其它线程锁住,放弃或者等待
  38. // 等待rowLockWaitDuration时间后。假设还未获得行锁。直接抛出异常
  39. if (!existingContext.latch.await(this.rowLockWaitDuration, TimeUnit.MILLISECONDS)) {
  40. if(traceScope != null) {
  41. traceScope.getSpan().addTimelineAnnotation("Failed to get row lock");
  42. }
  43. throw new IOException("Timed out waiting for lock for row: " + rowKey);
  44. }
  45. if (traceScope != null) traceScope.close();
  46. traceScope = null;
  47. } catch (InterruptedException ie) {
  48. LOG.warn("Thread interrupted waiting for lock on row: " + rowKey);
  49. InterruptedIOException iie = new InterruptedIOException();
  50. iie.initCause(ie);
  51. throw iie;
  52. } finally {
  53. if (traceScope != null) traceScope.close();
  54. }
  55. }
  56. }
  57.  
  58. // allocate new lock for this thread
  59. return rowLockContext.newLock();
  60. }

详细流程整理例如以下:

1、利用byte[]类型的入參row构造HashedBytes类型表示的行,即rowKey;

        2、利用rowKey创建行锁上下文实例,并指定为行rowKey和当前线程拥有。

        3、循环:

              3.1、将rowKey与行锁上下文的相应关系加入到Region的数据结构lockedRows中,可能出现下面几种情况:

                   3.1.1、假设lockedRows中之前不存在相应行rowKey的数据。说明该行当前没有被不论什么线程锁住,使用这个新创建的上下文rowLockContext,跳出循环并返回,说   明当前行可用。

                   3.1.2、假设该行已经被当前线程锁住,复用当前线程之前创建的行锁上下文实例,并赋值给rowLockContext,跳出循环并返回,说明当前行可用;

                   3.1.3、假设该行被其它线程锁住,假设入參确定不须要等待锁的获取,直接返回null,否则反复循环,直到等待rowLockWaitDuration时间后。假设还未获得行锁。   直接抛出异常。

至于都是哪些地方须要获取行锁,在以后各种数据读写流程中再做分析吧~

HBase行锁原理及实现的更多相关文章

  1. HBase行锁

    1 行锁简介 在事务特性方面,hbase只支持单row的事务,不能保证跨row(cross-row)的事务.hbase通过行锁来实现单row事务.客户端进行操作时,可以显式对某一个行加锁,但是大部分情 ...

  2. 利用多写Redis实现分布式锁原理与实现分析(转)

    利用多写Redis实现分布式锁原理与实现分析   一.关于分布式锁 关于分布式锁,可能绝大部分人都会或多或少涉及到. 我举二个例子:场景一:从前端界面发起一笔支付请求,如果前端没有做防重处理,那么可能 ...

  3. mysql的锁--行锁,表锁,乐观锁,悲观锁

    一 引言--为什么mysql提供了锁 最近看到了mysql有行锁和表锁两个概念,越想越疑惑.为什么mysql要提供锁机制,而且这种机制不是一个摆设,还有很多人在用.在现代数据库里几乎有事务机制,aci ...

  4. Hbase的架构原理、核心概念

    Hbase的架构原理.核心概念 1.Hbase的表.行.列.列族 2.核心组件: Table和region Table在行的方向上分割为多个HRegion, 一个region由[startkey,en ...

  5. Hbase架构与原理

    Hbase架构与原理 HBase是一个分布式的.面向列的开源数据库,该技术来源于 Fay Chang所撰写的Google论文"Bigtable:一个结构化数据的分布式存储系统".就 ...

  6. MySQL 行锁 表锁机制

    MySQL 表锁和行锁机制 行锁变表锁,是福还是坑?如果你不清楚MySQL加锁的原理,你会被它整的很惨!不知坑在何方?没事,我来给你们标记几个坑.遇到了可别乱踩.通过本章内容,带你学习MySQL的行锁 ...

  7. 关于分布式锁原理的一些学习与思考-redis分布式锁,zookeeper分布式锁

    首先分布式锁和我们平常讲到的锁原理基本一样,目的就是确保,在多个线程并发时,只有一个线程在同一刻操作这个业务或者说方法.变量. 在一个进程中,也就是一个jvm 或者说应用中,我们很容易去处理控制,在j ...

  8. [转]MySQL 表锁和行锁机制

    本文转自:http://www.cnblogs.com/itdragon/p/8194622.html MySQL 表锁和行锁机制 行锁变表锁,是福还是坑?如果你不清楚MySQL加锁的原理,你会被它整 ...

  9. HBase底层存储原理

    HBase底层存储原理——我靠,和cassandra本质上没有区别啊!都是kv 列存储,只是一个是p2p另一个是集中式而已! 首先HBase不同于一般的关系数据库, 它是一个适合于非结构化数据存储的数 ...

随机推荐

  1. cookie、json详解

    什么是cookie 1.cookie是存储于访问者计算机中的变量2.cookie是浏览器提供的一种机制3.可以由js控制(设置.读取.删除)4.cookie可以实现跨页面全局变量可以跨越同域名下多个网 ...

  2. 【译】x86程序员手册26-7.5任务切换

    7.5 Task Switching 任务切换 The 80386 switches execution to another task in any of four cases: 80386在以下四 ...

  3. HDU_3182_Hamburger Magi_状态压缩dp

    Hamburger Magi Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)To ...

  4. VS2010编译错误:fatal error C1189: #error : This file requires _WIN32_WINNT to be #defined at least to 0x

    下面是彻底解决方法:在工程的stdafx.h中添加(如有类似语句,需注释掉)#ifndef WINVER // Allow use of features specific to Windows 95 ...

  5. TWaver推智能手表挑战华为苹果

    2015年的春节刚过,苹果.华为.三星就紧锣密鼓的发布了各自新产品.华为.苹果的智能手表最吸引眼球.TWaver也不甘示弱,立刻连夜推出了更像传统奢侈豪华手表的TWaver Watch,予以反击.看来 ...

  6. php第二十八节课

    文件上传 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3 ...

  7. form表单传输多余参数

    1.使用post提交表单,同时在form的action属性后添加“?参数=参数值”,经验证,可行,但是在浏览器中看不到该参数在form参数中,如下图: 上图未出现courseId属性,form代码如下 ...

  8. JQurey---新尝试

    <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding= ...

  9. python环境配置以及基本知识

    python---一种解释型语言(脚本语言),具有代码简洁.入门简单.开发效率高的优点.当然不可避免的有着暴露源码.执行效率低的缺点,但毕竟瑕不掩瑜,在数据是无比宝贵的财富的当下,无疑是一门优秀的编成 ...

  10. Java 十二周总结