博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
《HBase权威指南》读书笔记 第三章:客户端API基础知识
阅读量:2190 次
发布时间:2019-05-02

本文共 7790 字,大约阅读时间需要 25 分钟。

基本操作

HTable提供了操作接口。建议应用程序初始化的2时候创建多个HTable,每个线程要有一个,或者使用HTablePool连接池。所有的修改只保证行级别的原子性。

以下是Java中操作HBase的例子,书中提供的代码已经deprecated,因此笔者用了最新的连接方式。这段代码运行一次要1分钟左右,主要耗时在连接部分,有配置可以跳过某些步骤没必要的步骤加快连接速度。

import org.apache.hadoop.conf.Configuration;import org.apache.hadoop.hbase.HBaseConfiguration;import org.apache.hadoop.hbase.TableName;import org.apache.hadoop.hbase.client.Connection;import org.apache.hadoop.hbase.client.ConnectionFactory;import org.apache.hadoop.hbase.client.Delete;import org.apache.hadoop.hbase.client.Get;import org.apache.hadoop.hbase.client.Put;import org.apache.hadoop.hbase.client.Result;import org.apache.hadoop.hbase.client.Table;import org.apache.hadoop.hbase.util.Bytes;import java.util.Map;public class TestHBase {
public static void main(String[] argv) throws Exception { // 建立连接 System.out.println("connecting..."); Configuration config = HBaseConfiguration.create(); Connection connection = ConnectionFactory.createConnection(config); Table table = connection.getTable(TableName.valueOf("test")); System.out.println("connect ok"); // 添加数据 Put one = new Put(Bytes.toBytes("Hello")); one.addColumn(Bytes.toBytes("test_family"), Bytes.toBytes("name"), Bytes.toBytes("Alice")); one.addColumn(Bytes.toBytes("test_family"), Bytes.toBytes("age"), Bytes.toBytes("20")); table.put(one); System.out.println("put ok"); // 查询 Get get = new Get(Bytes.toBytes("Hello")); Result result = table.get(get); Map
a = result.getFamilyMap(Bytes.toBytes("test_family")); String name = Bytes.toString(a.get(Bytes.toBytes("name"))); System.out.println("Get ok: " + name); // 删除 Delete delete = new Delete(Bytes.toBytes("Hello")); table.delete(delete); System.out.println("delete ok"); // 断开连接 table.close(); connection.close(); }}

KeyValue

KeyValue类,表示一项数据,包括列族名、列、行健、时间戳、数据内容。是HBase中最基本的类,从注释中可以看到这个类不建议用户使用,仅限HBase内部使用。但是用户可以通过这个类访问原始数据,避免额外的复制操作,并且支持基于字节比较,效率更高。

KeyValue类中有一些常用的比较器用于排序:

  • KeyComparator,依据getKey得到的结果进行比较。
  • KVComparator,与KeyComparator相同。
  • RowComparator,依据getRow得到的结果进行比较。
  • MetaKeyComparator,依据.META.条目的行健进行比较
  • MetaComparator
  • RootKeyComparator,依据-ROOT-条目的行健
  • RootComparator

注意到这里有getKeygetRow,这两个方法的返回值都是byte[],它们有什么区别呢?getRow得到的结果是行健,而getKey得到的结果是原始行健,在getRow之前还加上了点额外的数据。

KeyValue中的getTypeByte可获取操作类型。可能的类型有:

  • Put
  • Delete
  • DeleteColumn
  • DeleteFamily

所以从这里看看出,删除操作对于HBase而言也是往库里增加一条信息。

可以通过KeyValue.toString查看类型,它的输出格式为:<row-key>/<family>:<qualifier>/<version>/<type>/<value-length>

客户端写缓冲

如果写数据一条一条写,网络开销会比较大,因此HBase客户端提供了多种批量写的方式。

第一种是开启写缓冲,开启方法为table.setAutoFlush(false)。然后调用一批put操作,最后flushCommits批量提交请求。这样可以实现批量化。这种方式需要确保程序退出时必须调用flushCommits,否则数据可能丢失。

第二种是通过Put列表,一次性提交一批请求。

客户端在批量写的时候首先会根据row key进行排序,然后分割,发送到相应的region server上。

批量写还有一个问题,可能一批里面一部分数据写入失败了。这种情况会通过抛异常的方式将出错的信息包含在异常信息中。后面会讲到如何通过batch方法更加灵活的进行错误处理。

客户端原子操作

即CAS。在写数据的时候带上前一个数据的值,确保原来的值没被修改过再写进去。这样可以让分布式系统支持并发的对同一条数据进行读写。如果想要写入的数据确保库中不存在,那么将前一个值设置为null即可。

get操作

get操作可以获取一条数据或者一批数据。先说获取一条数据。

get方法的原型为Result get(Get get) throws IOException。其中Get类的构造方法原型有:

  • Get(byte[] row)
  • Get(byte[] row, RowLock rowLock)

可以看到row-key是必填项,行锁是可选项。Get可以设置的查询条件有:

  • addFamily(byte[] family),添加需要获取的列族。
  • addColumn(byte[] family, byte[] qualifier),添加需要获取的列。
  • setTimeRange(long minStamp, long maxStamp) throws IOException。设置时间戳范围。
  • setTimeStamp(long time)。获取特定的时间,本质上调用setTimeRange
  • setMaxVersions(int maxVersions) throws IOException,获取最大的版本数量,默认是1。
  • setMaxVersions(),获取所有版本。
  • setFilter,设置过滤条件。
  • setCacheBlocks,设置是否将块缓存到内存,已方便加载相邻的信息。顺序读取建议加上,随机读取不建议加。

Result类。包含了get操作的到的结果集。它有以下几个重要的成员方法:

  • byte[] getValue(byte[] family, byte[] qualifier):取得指定单元格的内容,只能取得最新版本。
  • byte[] value():返回第一列、第一个版本的值。
  • byte[] getRow():获取行健。
  • int size():查询的到的结果数量。
  • boolean isEmpty():查询结果是否为空。
  • KeyValue[] raw():拿到底层原始KeyValue结构
  • List<KeyValue> list():将raw得到的数据用List进行封装。

Result类中还有面向列的存取方式,可以获取一个列中包含的多个版本。

  • List<KeyValue> getColumn(byte[] family, byte[] qualifier):获取列对应的多个版本数据。
  • KeyValue getColumnLatest(byte[] family, byte[] qualifier):获取列中最新版本的数据。
  • boolean containsColumn(byte[] family, byte[] qualifier):是否版本指定的列。

Result类中还有更方便的获取全部数据的方法:

  • NavigableMap<byte[], NaviableMap<byte[], NavigableMap<Long, byte[]>>> getMap():获取所有的列族、列、时间戳、数据。
  • NavigableMap<byte[], NaviableMap<byte[], byte[]>> getNoversionMap():只获取最新版本的数据。

上面的get方法只能获取一个key的数据,而get列表方法可以批量取值。其原型为Result[] get(List<Get> gets) throws IOException。这个方法一次可以获取一批数据。这一批数据中有任何一条数据取出时发生错误,则会抛异常。如果需要得到获取成功的那部分数据,则需要通过batch方法,后面会讲。

获取数据相关的方法还有:

  • exists(Get get) throws IOException。这个方法只判断数据是否存在,而不会把行键对应的具体内容返回给客户端。这样避免了一部分网络开销,但是服务端读取文件块这些操作是无法避免的。
  • Result getRowOrBefore(byte[] row, byte[] family) throws IOException。这个方法获取行键对应的数据或者这个行键之前的数据。因为HBase中所有的行键都是排序好的,因此获取行键之前的意思就是获取行键字母顺序更小的数据。

删除

删除方法的原型为:void delete(Delete delete) throws IOException。这个方法最多只能删除一行数据。如果要删除多行就要调用多次。Delete类的构造方法原型有:

  • Delete(byte[] row)
  • Delete(byte[] row, long timestamp, RowLock rowLock)

可以看到row key是必填的,时间戳和行锁是选填的。其中timestamp意思是删除此时间戳以及之前的数据,保留此时间戳之后的数据。

默认删除一整行,包括所有的列族、列、版本。可以通过以下方法缩小删除的范围:

  • Delete addFamily(byte[] family):只删除指定列族
  • Delete addColumns(byte[] family, byte[] qualifier):只删除指定列的数据
  • Delete addColumn(byte[] family, byte[] qualifier):只删除指定列中的最新版本
  • void setTimestamp(long timestamp):设定时间戳,限定删除的范围是此时间戳以及之前。所以对于addColumnsaddFamily而言,删除此时间戳以及之前的数据,对于addColumn而言则是删除此时间戳或者之前数据中时间戳最晚的一条数据,如果没有符合条件的数据,则不删除。

删除操作也支持批量操作,其方法原型为:void delete(List<Delete> deletes) throws IOException

delete操作也支持CAS特性,方法名字叫做compareAndDelete

批量操作

前面讲到的putdelete都支持批量操作。本质上它们都会调用底层的batch方法实现。其方法原型为

  • void batch(List<Row> actions, Object[] results) throws IOException, InterruptedException
  • Object[] batch(List<row> actions) throws IOException, InterruptedException

这里面出现了Row类(实际上它是接口),这是因为Put类、Delete类、Get类都实现了Row接口。以上两种方法第二种方法已被标记为deprecated。它们的区别在于当客户端抛出异常时,比如当请求执行了一半时,当前线程发生了中断,那么第一种方法可以拿到执行了前半部分的指定结果,而第二种方法的执行结果没办法取到。

需要注意的是不要把Put请求和Delete请求放在同一批请求中,在某些情况下会产生随机效果。

batch方法的返回结果是一个Object[],它里面的数据类型有以下几种:

  • null:通信失败
  • EmptyResultPutDelete操作成功返回的结果
  • ResultGet操作成功返回的结果。如果没有匹配的行,则会返回空的Result
  • Throwable:在服务端执行时出现了错误,可能是参数有问题,也可能是服务端出现故障

batch方法直接将请求发送给服务端,不受写缓冲的控制。

batch方法会对暂时性的错误进行重试,默认重试10次。可在配置项hbase.client.retries.number中调整。

行锁

前面讲到的CAS特性是一种乐观锁,当并发冲突较少时可采用CAS方案。当并发冲突经常发生时,可以采用悲观锁的方案。其方法为:

  • RowLock lockRow(byte[] row):锁定一行
  • void unlockRow(RowLock rowLock):解锁一行

当一行锁定时,别的客户端不能写入也不能读取,只能等到锁释放之后才能继续。锁有租期限制,默认120秒,可通过配置hbase.regionserver.lease.period调整。

通常行锁不建议使用,容易导致死锁等问题。

get操作不会拿到写了一半的数据,所有行级别的操作都是原子性的。实现方式是通过MCC实现的(维基词条: ),就是说修改一行时,不会覆盖原来的数据,而是新开一个版本写入,写完之后才给别的客户端可见。

扫描

通过scan操作可以获取连续一批行健。其方法原型为ResultScanner getScanner(Scan scan) throws IOException。其中Scan类的构造函数原型为:

  • Scan()
  • Scan(byte[] startRow, Filter filter)
  • Scan(byte[] startRow)
  • Scan(byte[] startRow, byte[] stopRow)

可以看到扫描操作可以设置起止行健、过滤器。返回的结果集中包含行健为startRow的数据,不包含stopRow的数据。也就是左闭右开区间。

Scan类中还可以增加限定条件,方法如下。由于HBase是列式存储,如果限定了获取的列,那么开销就会相应减少。

  • Scan addFamily(byte[] family):只获取指定列族的数据
  • Scan addColumn(byte[] family, byte[] qualifier):只获取指定列的数据

ResultScanner负责批量从服务器中取数据,然后通过如下的接口让用户获取:

  • Result next() throws IOException:获取下一条数据,如果没有了就返回null。每次调用都会发生一个RPC请求。
  • Result[] next(int nbRows) throws IOException:获取后面nbRows条数据。如果到底了,返回的数组长度可能比nbRows小。每次调用都会发生RPC请求。
  • void close():扫描结束后要关掉。必须要关掉,否则时间长了占用资源。扫描器也有租约限制,默认是120秒,可通过hbase.regionserver.lease.period调整。

可以看到hbase.regionserver.lease.period同时控制扫描器和行锁的租期限制。

前面提到了next方法每次都会发出RPC请求。这个行为可以通过配置做优化,有一下两种方法。

  • Table.setScannerCaching(int scannerCaching):表级别设置缓冲数量,对这张表所有的扫描都生效。
  • Scan.setCache(int caching):扫描器级别设置缓冲数量,仅对本次扫描生效。

设置缓冲之后,第一次调用next会发起一次RPC,然后后续几次next直接在客户端内部完成操作,直到缓冲区中的数据读完完毕,又会发起一个RPC请求。因此缓冲数量过大则会导致next方法性能不稳定,有时快有时慢。

还有一种情况,列非常多,内存放不下怎么办?可以通过Scan.setBatch(int batchSize)参数控制每次返回的列数。比如一行数据有13列,如果batch设置为5,那么这一条数据会拆成3个Result返回,分别包含5、5、3列数据。

获取Region的物理分布

通过Connection.getRegionLocator得到RegionLocator,然后通过RegionLocator.getAllRegionLocations得到所有的分区物理分布。

转载地址:http://pvyub.baihongyu.com/

你可能感兴趣的文章
行为型模式之状态模式(State)
查看>>
行为型模式之策略模式(Strategy)
查看>>
行为型模式之模板方法模式(TemplateMethod)
查看>>
行为型模式之访问者模式(Visitor)
查看>>
大小端详解
查看>>
source insight使用方法简介
查看>>
<stdarg.h>头文件的使用
查看>>
C++/C 宏定义(define)中# ## 的含义 宏拼接
查看>>
Git安装配置
查看>>
linux中fork()函数详解
查看>>
C语言字符、字符串操作偏僻函数总结
查看>>
Git的Patch功能
查看>>
分析C语言的声明
查看>>
TCP为什么是三次握手,为什么不是两次或者四次 && TCP四次挥手
查看>>
C结构体、C++结构体、C++类的区别
查看>>
进程和线程的概念、区别和联系
查看>>
CMake 入门实战
查看>>
绑定CPU逻辑核心的利器——taskset
查看>>
Linux下perf性能测试火焰图只显示函数地址不显示函数名的问题
查看>>
c结构体、c++结构体和c++类的区别以及错误纠正
查看>>