任务描述
本关任务:使用 HBase 原子性操作完成要求的数据更新操作。
相关知识
为了完成本关任务,你需要掌握:
1.HBase 事务;
2.原子性操作。
开始任务前请在命令行中进入HBase Shell中创建本实训需要用到的表和数据,具体操作如下所述(别忘了开启 Hadoop 集群和 Zookeeper 集群和 HBase 集群):
进入 HBase 的安装目录:
cd /app/hbase
进入 HBase Shell:
hbase shell
创建表及插入数据:
create 'province','info'
put 'province','1001','info:address','hn'
put 'province','1002','info:address','tj'
put 'province','1003','info:address','wh'
put 'province','1004','info:address','nj'
HBase 事务
有关 HBase 事务的详情如下所述:
事务的基本概念:
一个多个操作的业务被事务管理,这些操作要么同时成功,要么同时失败。
事务的四大特征(ACID):
一致性:事务操作后,数据总量不变。
原子性:是不可分割的最小操作单位,要么同时成功,要么同时失败。
隔离性:多个事务之间,相互独立。
持久性:当事务提交或回滚时,数据库会持久化的保存数据。
HBase 对同一行数据的操作提供 ACID 保证,那么,HBase 中的 ACID 是如何工作的呢?
HBase 采用 MVCC 来实现 ACID,同时 HBase 中没有混合读写事务。简单说来,HBase 的 ACID 就是在各个 RegionServer 上维护一个“严格单调递增事务号”(strictly monotonically increasing transaction numbers)。当一个写事务(put,delete)开始时将获取到下一个最高事务号,HBase 将这个号称为“写入号”(WriteNumber);当一个读事务(scan,get)开始时将获取到上一次提交成功的事务号,这个号被称为“读取点”(ReadPoint)。每个创建的 KeyValue 对都会被标记上它的事务写入号,HBase 中将这个标签称为“memstore 时间戳”。
HBase 中标准的读写事务流程如下所述:
写事务流程:
锁行,拒绝对相同行的并发写。
获取当前的写入号。
将修改写入“写前日志”WAL(Write Ahead Log)。
将修改写入 Memstore ,同时用获取到的写入号标记 KeyValue 对。
提交事务,即尝试将读取点滚到获取到的写入号(这样变更就可以对所有新的 Scan 可见)。
打开行锁。
读事务流程:
打开 Scanner。
获取当前读取点(ReadPoint)。
用获取的标记memstore timestamp即 ReadPoint 过滤所有扫描到的 KeyValues,只看 ReadPoint 之前的。
关闭 Scanner。
还有几点需要注意的点如下所述:
为了不拖延正在等待提交的事务,即使事务失败了,读取点依然会向前滚。
每当变更写入 WAL 时,就会产生一条记录,并没有单独的提交记录。
一台 RegionServer 挂了之后,WAL 记录写成功的但未执行的事务最终会在另外一台 RegionServer 上重新执行,WAL 记录未写成功的则会被丢弃。
原子性操作
HBase 支持行级事务,即 HBase 对一行数据的操作能够保证原子性,详情如下所述:
HBase 对一行的操作(如 Put)能够保证原子性。一个 Put 操作会有一个返回值,成功、失败或者超时。如果请求超时,那么这个 Put 可能成功也可能失败,总而言之就是要么一行的所有列族所有列都 Put 成功,要么所有列族所有列全部失败。
HBase 不能保证多行的更新操作的原子性,如一个批量操作 Put a、b 和 c 三行数据,结果可能是 a、b 成功,c 失败。
CAS (Compare And Set) API (如 checkAndPut) 是一个原子操作。
HBase 事务的原子性是通过 WAL 来保证的,数据写入时会先写入 WAL, 再写入 MemStore。 写入 MemStore 异常可以通过 WAL 来回滚或者重做,因此只需要保证 WAL 的原子性。假设现在需要写入一行数据 R1,该行数据包含 a、b 和 c 这了 3 列,那么 WAL 事务单元的格式为:
<logseq#-for-entire-txn>:<-1,3,<Keyvalue-for-edit-a