zoukankan      html  css  js  c++  java
  • Hbase事务

    原文:http://hbasefly.com/2017/07/26/transaction-2/

    1、关于hbase事务

    HBase目前只支持行级事务;
    可以保证行级数据的原子性、一致性、隔离性以及持久性,即通常所说的ACID特性。
    为了实现事务特性,HBase采用了各种并发控制策略,包括各种锁机制、MVCC机制等。


    2、hbase事务原子性

    hbase写数据:HBase数据会首先写入WAL,再写入Memstore。
    
    写入Memstore异常很容易可以回滚,因此保证写入/更新原子性只需要保证写入WAL的原子性即可。HBase 0.98之前版本需要
    保证WAL写入的原子性并不容易,这由WAL的结构决定。假设一个行级事务更新R行中的3列(c1, c2, c3),来看看之前版本
    和当前版本的WAL结构:
    
    1. 之前版本WAL结构:
    <logseq1-for-edit1>:<KeyValue-for-edit-c1>
    
    <logseq2-for-edit2>:<KeyValue-for-edit-c2>
    
    <logseq3-for-edit3>:<KeyValue-for-edit-c3>
    
    每个KV都会形成一个WAL单元,这样一行事务更新多少列就会产生多少个WAL单元。在将这些WAL单元append到日志文件的时候,一旦出现宕机或其他异常,
    就会出现部分写入成功的情况,原子性更新就无法保证。
    
    2. 当前版本WAL结构:
    <logseq#-for-entire-txn>:<WALEdit-for-entire-txn>
    
    <logseq#-for-entire-txn>:<-1, 3, <Keyvalue-for-edit-c1>, <KeyValue-for-edit-c2>, <KeyValue-for-edit-c3>>
    
    通过这种结构,每个事务只会产生一个WAL单元。这样就可以保证WAL写入时候的原子性。


    3、hbase事务强一致性保证

    * 写写并发控制
    * 批量写入多行的写写并发
    * 读写并发控制


    为什么需要写写并发控制 :

    现在假设有两个并发写入请求同时进来,分别对同一行数据进行写入。下图所示RowKey为Greg,现在分别更新列族info下的Company列和Role列:

    image


    如果没有任何并发控制策略的话,写入数据(先写WAL,再写memstore)可能会出现不同KV写入”交叉”现象,如下图所示:

    image


    这样的话,用户最终读取到的数据就会产生不一致,如下:

    image


    如何实现写写并发控制:

    实现写写并发其实很简单,只需要在写入(或更新)之前先获取行锁,如果获取不到,说明已经有其他线程拿了该锁,就需要
    不断重试等待或者自旋等待,直至其他线程释放该锁。拿到锁之后开始写入数据,写入完成之后释放行锁即可。这种行锁机制
    是实现写写并发控制最常用的手段,后面可以看到MySQL也是使用行锁来实现写写并发的。


    如何实现批量写入多行的写写并发:

    HBase支持批量写入(或批量更新),即一个线程同时更新同一个Region中的多行记录。那如何保证当前事务中的批量写入与
    其他事务中的批量写入的并发控制呢?思路还是一样的,使用行锁。但这里需要注意的是必须使用两阶段锁协议,即:
    
    (1) 获取所有待写入(更新)行记录的行锁
    
    (2) 开始执行写入(更新)操作
    
    (3) 写入完成之后再统一释放所有行记录的行锁
    
    不能更新一行锁定(释放)一行,多个事务之间容易形成死锁。两阶段锁协议就是为了避免死锁,MySQL事务写写并发控制同
    样使用两阶段锁协议。


    4、读写并发控制

    为什么需要读写并发控制:

    现在我们通过在写入更新之前加锁、写入更新之后释放锁实现写写并发控制,那读写之间是不是也需要一定的并发控制呢?

    如果不加并发控制,会出现什么现象呢?接着看下图:

    image


    上图分别是两个事务更新同一行数据,现在假设第一个事务已经更新完成,在第二个事务更新到一半的时候进来一个读请求,

    如果没有任何并发控制的话,读请求就会读到不一致的数据,Company列为Restaurant,Role列为Engineer,如下图所示:

    image

    可见,读写之间也需要一种并发控制来保证读取的数据总能够保持一致性,不会出现各种诡异的不一致现象。


    如何实现读写并发控制:

    实现读写并发最简单的方法就是仿照写写并发控制 – 加锁。但几乎所有数据库都不会这么做,性能太差,对于读多写少的应用
    来说必然不可接受。那还有其他方法吗?
    
    当然,这就是今天要重点提到的MVCC机制 – Mutil Version Concurrent Control。HBase中MVCC机制实现主要分为两步:
    
    (1) 为每一个写(更新)事务分配一个Region级别自增的序列号
    
    (2) 为每一个读请求分配一个已完成的最大写事务序列号

    示意图如下所示:

    image

    上图中两个写事务分别分配了序列号1和序列号2,读请求进来的时候事务1已经完成,事务2还未完成,因此分配事务1对应的序列号1给读请求。

    此时序列号1对本次读可见,序列号2对本次读不可见,读到的数据是:

    image


    具体实现中,所有的事务都会生成一个Region级别的自增序列,并添加到队列中,如下图最左侧队列,其中最底端为已经提交
    的事务,队列中的事务为未提交事务。现假设当前事务编号为15,并且写入完成(中间队列红色框框),但之前的写入事务还
    未完成(序列号为12、13、14的事务还未完成),此时当前事务必须等待,而且对读并不可见,直至之前所有事务完成之后才
    会对读可见(即读请求才能读取到该事务写入的数据)。如最右侧图,15号事务之前的所有事务都成功完成,此时Read Point
    就会移动到15号事务处,表示15号事务之前的所有改动都可见。

    image


    可能有朋友有疑问:如果这两个自增序列是同一个序列,那是不是这个队列的顺序必须与事务写入WAL的顺序一致?
    如果不一致有什么问题?如果要求一致的话怎么才能实现?
    
    千万不要查看1.1.2的代码,会把你彻底搞混的!建议阅读更高版本的相关代码!
    
    所以,MVCC的精髓是写入的时候分配递增版本信息(SequenceId),读取的时候分配唯一的版本用于读取可见,比之大的版
    本不可见。这里需要注意版本必须递增,而且版本递增的范围一定程度上决定了事务是什么事务,比如HBase是Region级别的
    递增版本,那么事务就是region级别事务。MySQL中版本是单机递增版本,那么MySQL事务就支持单机跨行事务。Percolator
    中版本是集群递增版本,那么Percolator事务就是分布式事务。


    5、hbase事务持久性保证

    HBase事务持久化可以理解为WAL持久化,
    目前实现了多种持久化策略:SKIP_WAL,ASYNC_WAL,SYNC_WAL,FSYNC_WAL。
    SKIP_WAL表示不写WAL,这样写入更新性能最好,但在RegionServer宕机的时候有可能会丢失部分数据;
    ASYNC_WAL表示异步将WAL持久化到硬盘,因为是异步操作所以在异常的情况下也有可能丢失少量数据;
    SYNC_WAL表示同步将WAL持久化到操作系统缓存,再由操作系统将数据异步持久化到磁盘,这种场景下RS宕掉并不会丢失
    数据,当操作系统宕掉会导致部分数据丢失;
    FSYNC_WAL表示WAL写入之后立马落盘,性能相对最差。目前实现中FSYNC_WAL并没有实现!用户可以根据业务对数据丢
    失的敏感性在客户端配置相应的持久化策略。






  • 相关阅读:
    001.Git简介与安装
    004.MySQL主库手动复制至从库
    001.MySQL高可用主从复制简介
    SQL Server之索引解析(一)
    设计模式之简单工厂模式
    设计模式之总体介绍
    .NET Framework与.NET Core
    【python opencv】二维直方图
    【python opencv】直方图均衡
    【python opencv】直方图查找、绘制和分析
  • 原文地址:https://www.cnblogs.com/weiyiming007/p/12201082.html
Copyright © 2011-2022 走看看