3. CRUD 操作 (CRUD Operations)
数据库的初始基本操作通常称为 CRUD (create, read, update, and delete), 指的是 增、查、改、删四种操作。HBase 中有与之对应的的一组操作,由
Table interface 接口提供。
public interface Table extends Closeable
Used to communicate with a single HBase table. Obtain an instance from a Connection and call close() afterwards.
Table can be used to get, put, delete or scan data from a table.
3.1 put 方法 (Put Method)
-----------------------------------------------------------------------------------------------------------------------------------------
put 操作可以划分为几种类型:一类操作用于单行,一类操作用于多行(lists of rows), 还有一类提供服务器端的,原子的 check-and-put 操作。
■ 单行 Put (Single Puts)
-----------------------------------------------------------------------------------------------------------------------------------------
向 HBase 中存储数据,执行如下调用:
void put(Put put) throws IOException
由一个 Put 对象作为参数,可以通过如下构造器创建:
Put(byte[] row)
Put(byte[] row, long ts)
Put(byte[] rowArray, int rowOffset, int rowLength)
Put(ByteBuffer row, long ts)
Put(ByteBuffer row)
Put(byte[] rowArray, int rowOffset, int rowLength, long ts)
Put(Put putToCopy)
需要提供一个行键 row 来创建 Put 实例。在 HBase 中每行数据都有唯一的行键(row key)作为标识,与 HBase 中的大多数数据类型类似,他是一个 Java
byte[] 数组。用户可以按自己的需求来指定每行的行键。行键的含义与实际的物理场景相关,例如,它的含义可以是一个用户名或者订单号,它的内容可以
是简单的数字,也可以是较复杂的 UUID 等。
HBase 非常友好地为我们提供了一个包含很多静态方法的辅助类 Bytes,这个类可以把许多 Java 数据类型转换为 byte[] 数组:
static byte[] toBytes(ByteBuffer bb)
static byte[] toBytes(String s)
static byte[] toBytes(boolean b)
static byte[] toBytes(long val)
static byte[] toBytes(float f)
static byte[] toBytes(int val)
...
例如,下面是将一个用户名从字符串转换为 byte[] 数组的示例:
byte[] rowkey = Bytes.toBytes("johndoe");
除了直接的方法,还有些构造器变体利用已有的数组,并提供给定的数组偏移量和长度参数,从数组中复制所需的行键位数来创建 Put 实例:
byte[] data = new byte[100];
...
String username = "johndoe";
byte[] username_bytes = username.getBytes(Charset.forName("UTF8"));
...
System.arraycopy(username_bytes, 0, data, 45, username_ bytes.length);
...
Put put = new Put(data, 45, username_bytes.length);
类似地,也可以提供一个已存在的 ByteBuffer, 或者是一个已存在的 Put 实例,它们都会从给定的已存在的对象中获取细节信息来构造 Put 实例。
创建 Put 实例之后,就可以向该实例添加数据了,添加数据的方法如下:
Put addColumn(byte[] family, byte[] qualifier, byte[] value)
Put addColumn(byte[] family, byte[] qualifier, long ts, byte[] value)
Put addColumn(byte[] family, ByteBuffer qualifier, long ts, ByteBuffer value)
Put addImmutable(byte[] family, byte[] qualifier, byte[] value)
Put addImmutable(byte[] family, byte[] qualifier, long ts, byte[] value)
Put addImmutable(byte[] family, ByteBuffer qualifier, long ts, ByteBuffer value)
Put addImmutable(byte[] family, byte[] qualifier, byte[] value, Tag[] tag)
Put addImmutable(byte[] family, byte[] qualifier, long ts, byte[] value, Tag[] tag)
Put addImmutable(byte[] family, ByteBuffer qualifier, long ts, ByteBuffer value, Tag[] tag)
Put add(Cell kv) throws IOException
每一个 addColumn 的调用指定一个列,或者指定一个可选的时间戳,一个单元格一个。注意,如果在调用 addColumn() 时没有指定时间戳,Put
实例会使用从构造器上传递过来的可选的时间戳(也称为 ts), 或者,构造器也没有设置时间戳,则由分区服务器(region server) 基于本地时钟
分配时间戳。如果客户端没有设置时间戳,在此 Put 实例上调用 getTimeStamp() 将返回 Long.MAX_VALUE。
注意,调用以上 addXYZ() 方法会在内部创建 Cell 实例。
每个 addColumn() 都有对应的 addImmutable() 方法,addImmutable() 与其对应的 addColumn() 做相同的事,区别是不从给定的数组复制数据,
它们假定不会修改给定参数的数组内容,它们更加高效地使用内存并且性能更好,但依赖于客户端正确地使用。
利用已存在 Cell 实例的变体 add(Cell kv) 是为高级用户提供的方法。要检查特定的 cell 是否存在,可使用如下方法:
boolean has(byte[] family, byte[] qualifier)
boolean has(byte[] family, byte[] qualifier, long ts)
boolean has(byte[] family, byte[] qualifier, byte[] value)
boolean has(byte[] family, byte[] qualifier, long ts, byte[] value)
Put 类还提供了很多方法,很多继承自其基础类型:
public class Put extends Mutation
implements org.apache.hadoop.hbase.io.HeapSize, Comparable<Row>
//Example application inserting data into HBase
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.Put;
import org.apache.hadoop.hbase.client.Table;
import org.apache.hadoop.hbase.util.Bytes;
import java.io.IOException;
public class PutExample {
public static void main(String[] args) throws IOException {
//Create the required configuration
Configuration conf = HBaseConfiguration.create();
//Instantiate a new client.
Connection connection = ConnectionFactory.createConnection(conf);
Table table = connection.getTable(TableName.valueOf("testtable"));
//Create put with specific row.
Put put = new Put(Bytes.toBytes("row1"));
//Add a column, whose name is “colfam1:qual1”, to the put.
put.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"), Bytes.toBytes("val1"));
//Add another column, whose name is “colfam1:qual2”, to the put.
put.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual2"), Bytes.toBytes("val2"));
//Store row with column into the HBase table.
table.put(put);
//Close table and connection instances to free resources.
table.close();
connection.close();
}
}
■ 客户端的写缓冲区 (Client-side Write Buffer)
-----------------------------------------------------------------------------------------------------------------------------------------
每一个 put 操作实际上都是一个 RPC (“remote procedure call”) 操作,它将客户端数据传送到服务器然后返回。这只适合于小数据量的操作。如果应用
程序需要每秒存储上千行的数据到 HBase 表中,这样的处理就不太合适了。
NOTE:
-------------------------------------------------------------------------------------------------------------------------------------
减少 RPC 调用的关键是限制往返时间(round-trip time), 往返时间就是客户端发送一个请求到服务器,然后服务器通过网络进行响应的时间。这个时
间不包含数据实际传输的时间,它其实就是通过线路传送网络包的开销。一般情况下,在 LAN 网络中大约要花 1 毫秒的时间,这意味着 1 秒钟的时间
只能完成 1000 次 RPC 往返响应。
另一个重要的因素就是消息的大小。如果通过网络发送的请求内容较大,那么需要请求返回的次数相应较少,这是因为时间主要花费在数据传输上。不过
如果传送的数据量很小,比如一个计数器递增操作,那么用户把多次修改的数据批量提交给服务器并减少请求次数,性能会有相应提升。
HBase API 自带有一个内置的客户端写缓冲区(write buffer) 用于收集 put 和 delete 操作,以使它们通过一次 RPC 调用发送到服务器。这个功能的入口点
是 BufferedMutator 类。通过 Connection 类的如下方法获得:
BufferedMutator getBufferedMutator(TableName tableName) throws IOException
BufferedMutator getBufferedMutator(BufferedMutatorParams params) throws IOException
返回的 BufferedMutator 是线程安全的,可用于批量的 put 和 delete 操作,称为 mutations, 或 operations。使用这个类有几件事要记住:
1. 必须在生命周期结束时调用 close() 方法。这会将未完成的操作同步刷写出去并释放资源。
2. 当提交了特定的 mutation 需要立刻发送到服务器上时,调用 flush() 方法。
3. 如果不调用 flush(), 那么依赖于内部的、异步的更新机制:达到某个特定的阈值,或者调用了 close() 方法。
4. 如果应用程序失败,在那一时刻仍然缓存的任何本地 mutation 会丢失。
第一个方法需要表名(table name) 为参数,很明显是发送批操作的表名称。后面的那个方法有点复杂,它需要一个 BufferedMutatorParams 的实例作为参数,
其中持有不仅仅是必要的表名称,还有其它更高级的参数,有如下的构造器和方法:
BufferedMutatorParams(TableName tableName)
TableName getTableName()
long getWriteBufferSize()
BufferedMutatorParams writeBufferSize(long writeBufferSize)
int getMaxKeyValueSize()
BufferedMutatorParams maxKeyValueSize(int maxKeyValueSize)
ExecutorService getPool()
BufferedMutatorParams pool(ExecutorService pool)
BufferedMutator.ExceptionListener getListener()
BufferedMutatorParams listener(BufferedMutator.ExceptionListener listener)
列表中第一个是带有表名参数的构造器,下面是可以进一步获取或设置的一些参数:
WriteBufferSize :如果计数值超出了 WriteBufferSize 设置的值,所有缓存的 mutation 异步地发送到服务器。由 hbase-default.xml 文件中
hbase.client.write.buffer 属性默认设置为 2097152 bytes, 即 2MB. 默认的 2MB 值表现在 RPC 包大小和客户端进程数据保持间
达到很好的平衡。
MaxKeyValueSize :操作允许客户端 API 发送到服务器之前,cell 中包含的数据的大小通过 MaxKeyValueSize 设置检查。如果 cell 超过这个设置的
限制,会被拒绝,并向客户端抛出 IllegalArgumentException 异常。这确保在有意义的边界内使用 HBase。
由 hbase-default.xml 文件中 hbase.client.keyvalue.maxsize 属性默认设置为 10485760 bytes (or 10MB)
Pool :由于所有的异步操作都是由客户端类库在后台执行的,这需要提交一个标准的 Java ExecutorService 实例。如果用户没有设置 pool
系统会创建一个默认的 pool, 由 hbase.htable.threads.max 控制,默认设置为 Integer.MAX_VALUE (意为无限数量), 以及
hbase.htable.threads.keepalivetime 属性, 设置为 60 秒。
Listener :最后,可以使用一个侦听器钩子(a listener hook), 在服务器上执行 mutation 期间发生错误时接收通知。要实现这一个功能,需要
实现 BufferedMutator.ExceptionListener 接口,它提供 onException() callback 方法。默认实现是抛出收到的异常。
// Shows the use of the client side write buffer
private static final int POOL_SIZE = 10;
private static final int TASK_COUNT = 100;
private static final TableName TABLE = TableName.valueOf("testtable");
private static final byte[] FAMILY = Bytes.toBytes("colfam1");
public static void main(String[] args) throws Exception {
Configuration configuration = HBaseConfiguration.create();
BufferedMutator.ExceptionListener listener = new BufferedMutator.ExceptionListener() {
// Create a custom listener instance
@Override
public void onException(RetriesExhaustedWithDetailsException e,
BufferedMutator mutator) {
// Handle callback in case of an exception
for (int i = 0; i < e.getNumExceptions(); i++) {
LOG.info("Failed to sent put: " + e.getRow(i));
}
}
};
// Create a parameter instance, set the table name and custom listener reference.
BufferedMutatorParams params = new BufferedMutatorParams(TABLE).listener(listener);
//Allocate the shared resources using the Java 7 try-with-resource pattern.
try (
Connection conn = ConnectionFactory.createConnection(configuration);
BufferedMutator mutator = conn.getBufferedMutator(params)
)
{
// Create a worker pool to update the shared mutator in parallel.
ExecutorService workerPool = Executors.newFixedThread‐Pool(POOL_SIZE);
List<Future<Void>> futures = new ArrayList<>(TASK_COUNT);
// Start all the workers up.
for (int i = 0; i < TASK_COUNT; i++) {
futures.add(workerPool.submit(new Callable<Void>() {
@Override
public Void call() throws Exception {
Put p = new Put(Bytes.toBytes("row1"));
p.addColumn(FAMILY, Bytes.toBytes("qual1"), Bytes.toBytes("val1"));
// Each worker uses the shared mutator instance, sharing the same backing buffer, callback listener, and RPC execuor pool.
mutator.mutate(p);
// [...]
// Do work... Maybe call mutator.flush() after many edits
to ensure
// any of this worker's edits are sent before exiting the
Callable
return null;
}
}));
}
for (Future<Void> f : futures) {
// Wait for workers and shut down the pool.
f.get(5, TimeUnit.MINUTES);
}
workerPool.shutdown();
} catch (IOException e) {
// The try-with-resource construct ensures that first the mutator, and then the connection are closed. This could trigger exceptions
// and call the custom listener.
LOG.info("Exception while creating or freeing resources", e);
}
}
}
客户端写缓冲区主要的应用场景是小的 mutation 操作,也就是 put 和 delete 请求。特别是后者更小,delete 操作并不携带值数据:delete 只有 cell 的 key
信息,设置一个删除标记。
另一个适用的场景是 MapReduce 作业操作 HBase, 因为它们都是有关尽可能快地发送 mutation 操作的。
隐式地刷写或显式调用 flush() method 会将所有修改应用到远程服务器。缓存的 Put 和 Delete 实例可以跨多个不同的行。客户端足以智能可以分别批次更新并将
它们发送到各自的分区服务器上去。仅仅需要简单的 put() 或 delete() 调用,不必担忧数据存在什么地方,因为这个处理对 HBase 客户端是透明的。
一个主意点是考虑上面提到的 executor pool. 其由 hbase.htable.threads.max 属性控制,并且默认设置为 Integer.MAX_VALUE, 意为无限数量。这不是指每个客
户端向服务器发送缓冲的写操作要创建无限多的工作线程。它真正的含义是每个分区服务器创建一个线程。这是由服务器数量决定的。但如果集群扩展到几千个节点,
就要考虑设置这个属性为某个最大值,以显式界定需要的数量。
下面是另一个客户端 API 使用 write buffer 的例子:
// Example using the client-side write buffer
TableName name = TableName.valueOf("testtable");
Connection connection = ConnectionFactory.createConnection(conf);
Table table = connection.getTable(name);
// Get a mutator instance for the table.
BufferedMutator mutator = connection.getBufferedMutator(name);
Put put1 = new Put(Bytes.toBytes("row1"));
put1.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"), Bytes.toBytes("val1"));
// Store some rows with columns into HBase.
mutator.mutate(put1);
Put put2 = new Put(Bytes.toBytes("row2"));
put2.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"), Bytes.toBytes("val2"));
mutator.mutate(put2);
Put put3 = new Put(Bytes.toBytes("row3"));
put3.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"),
Bytes.toBytes("val3"));
mutator.mutate(put3);
Get get = new Get(Bytes.toBytes("row1"));
Result res1 = table.get(get);
// Try to load previously stored row, this will print “Result:keyvalues=NONE”.
System.out.println("Result: " + res1);
// Force a flush, this causes an RPC to occur.
mutator.flush();
Result res2 = table.get(get);
// Now the row is persisted and can be loaded.
System.out.println("Result: " + res2);
mutator.close();
table.close();
connection.close();
■ Put 列表 (List of Puts)
-----------------------------------------------------------------------------------------------------------------------------------------
客户端 API 可以插入单个 Put 实例,同时也有批处理操作的高级特性,调用形式如下:
void put(List<Put> puts) throws IOException
需要创建一个 Put 实例的列表,并把它提交给上面的 put() 方法调用。
示例:
// Example inserting data into HBase using a list
// Create a list that holds the Put instances.
List<Put> puts = new ArrayList<Put>();
Put put1 = new Put(Bytes.toBytes("row1"));
put1.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"),
Bytes.toBytes("val1"));
// Add put to list.
puts.add(put1);
Put put2 = new Put(Bytes.toBytes("row2"));
put2.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"),
Bytes.toBytes("val2"));
// Add another put to list.
puts.add(put2);
Put put3 = new Put(Bytes.toBytes("row2"));
put3.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual2"),
Bytes.toBytes("val3"));
// Add third put to list.
puts.add(put3);
// Store multiple rows with columns into HBase.
table.put(puts);
由于用户提交的 mutation 的 row 列表可能涉及多个行,有可能不会全部成功。这可能会是多种原因导致的,如一个远程 region server 出现了问题,
导致客户端重试的次数超过了配置的最大值,因此不得不放弃当前操作。如果远程服务器的 put 调用出现问题,错误会通过随后的一个 IOException
异常反馈给客户端。
示例:使用不存在的列族 BOGUS 插入一个列
// Example inserting a faulty column family into HBase
Put put1 = new Put(Bytes.toBytes("row1"));
put1.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"), Bytes.toBytes("val1"));
puts.add(put1);
Put put2 = new Put(Bytes.toBytes("row2"));
// Add put with non existent family to list.
put2.addColumn(Bytes.toBytes("BOGUS"), Bytes.toBytes("qual1"),
Bytes.toBytes("val2"));
puts.add(put2);
Put put3 = new Put(Bytes.toBytes("row2"));
put3.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual2"),
Bytes.toBytes("val3"));
puts.add(put3);
table.put(puts);
调用 put() 会失败并产生如下错误消息:
WARNING: #3, table=testtable, attempt=1/35 failed=1ops, last exception:
null \
on server-1.internal.foobar.com,65191,1426248239595, tracking \
started Sun Mar 15 20:35:52 CET 2015; not retrying 1 - final
failure
Exception in thread "main" \
org.apache.hadoop.hbase.client.RetriesExhaustedWithDetailsException: \
Failed 1 action: \
org.apache.hadoop.hbase.regionserver.NoSuchColumnFamilyException:
\
Column family BOGUS does not exist in region \
testtable,,1426448152586.deecb9559bde733aa2a9fb1e6b42aa93. in
table \
'testtable', {NAME => 'colfam1', DATA_BLOCK_ENCODING => 'NONE', \
BLOOMFILTER => 'ROW', REPLICATION_SCOPE => '0', COMPRESSION =>
'NONE', \
VERSIONS => '1', TTL => 'FOREVER', MIN_VERSIONS => '0', \
KEEP_DELETED_CELLS => 'FALSE', BLOCKSIZE => '65536', \
IN_MEMORY => 'false', BLOCKCACHE => 'true'}
: 1 time,
服务器遍历所有操作并尝试执行它们,失败的返回,然后客户端可以使用 RetriesExhaustedWithDetailsException 报告远程错误,这样用户就可以查询
有多少个操作失败、出错的原因以及重试的次数。要注意的是,对于错误列族,服务器端的重试次数会自动设为 1 (查看 NoSuchColumnFamilyException:
1 time), 因为 HBase 认为这是个不可恢复的错误。
另外,可以使用异常实例来获得失败操作更详细的信息,甚至错误的 mutation 本身。下面示例扩展自上面错误示例,加入 catch 块来访问错误的详细
信息:
// Special error handling with lists of puts
try {
table.put(puts);
} catch (RetriesExhaustedWithDetailsException e) {
int numErrors = e.getNumExceptions();
System.out.println("Number of exceptions: " + numErrors);
for (int n = 0; n < numErrors; n++) {
System.out.println("Cause[" + n + "]: " + e.getCause(n));
System.out.println("Hostname[" + n + "]: " + e.getHostnamePort(n));
System.out.println("Row[" + n + "]: " + e.getRow(n));
}
System.out.println("Cluster issues: " + e.mayHaveClusterIssues());
System.out.println("Description: " + e.getExhaustiveDescription());
}
之前提到过为 BufferedMutator 提供的 MaxKeyValueSize 参数,API 必须确保提交的操作遵守这个限制。对单行 put 和列表 put 也执行相同的检查。
使用基于列表的 put 调用还要注意另外一个特殊性:无法控制应用到服务器端的 put 操作的次序,也就意味着服务器端调用的次序也不在用户的控制之下。
如果必须保证按一定的次序执行调用,最坏的情况是分别创建更小的批处理,并在客户端显式刷写缓冲区强制发送到远程服务器。
■ 原子性操作 (Check-and-Put (Atomic Check-and-Put)
-----------------------------------------------------------------------------------------------------------------------------------------
有一种特殊的 put 调用,它能保证自身操作的原子性:检查写(check and put)。方法签名如下:
boolean checkAndPut(byte[] row, byte[] family, byte[] qualifier, byte[] value, Put put) throws IOException
boolean checkAndPut(byte[] row, byte[] family, byte[] qualifier, CompareFilter.CompareOp compareOp, byte[] value, Put put)
throws IOException
这两个调用使用户可以发布原子性的、服务器端的带有检查保护的 mutation 操作。如果通过检查,put 操作被执行,否则完全放弃该操作。这种方法可用于
基于当前的,可能相关联的值的更新。
这种带有检查保护的操作经常用于系统处理,例如,账户余额,状态转换,或数据处理。这种处理的基本原则是,读取数据的同时处理这些数据。一旦准备好
将数据结果写回 HBase, 要确保没有其它的客户端对同样的数据做过处理。使用原子检查,比较数据的值没有被修改过,然后应用新的值。
第一个调用的含义是给定的值 value 与存储的值必须相等 (to be equal to), 第二个调用指定了一个实际的比较器(comparison operator) 进行比较,提供
了更灵活的检查测试。
NOTE:
-------------------------------------------------------------------------------------------------------------------------------------
有一种特殊类型的检查可以通过 checkAndPut() 执行:只有在另外一个值不存在时才执行更新操作。这可以通过设置 value 的值为 null 来实现。这
种情况下,当某个特地的列不存在时操作才会成功执行。
这两个调用返回一个 boolean 类型结果值,指明 Put 是否应用成功,分别返回 true 或 false。
示例:
// Example application using the atomic compare-and-set operations
Put put1 = new Put(Bytes.toBytes("row1"));
// Create a new Put instance.
put1.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"), Bytes.toBytes("val1"));
// Check if column does not exist and perform optional put operation.
boolean res1 = table.checkAndPut(Bytes.toBytes("row1"), Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"), null, put1);
// Print out the result, should be “Put 1a applied: true”.
System.out.println("Put 1a applied: " + res1);
// Attempt to store same cell again.
boolean res2 = table.checkAndPut(Bytes.toBytes("row1"), Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"), null, put1);
// Print out the result, should be “Put 1b applied: false” as the column now already exists.
System.out.println("Put 1b applied: " + res2);
Put put2 = new Put(Bytes.toBytes("row1"));
// Create another Put instance, but using a different column qualifier.
put2.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual2"), Bytes.toBytes("val2"));
// Store new data only if the previous data has been saved.
boolean res3 = table.checkAndPut(Bytes.toBytes("row1"), Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"), Bytes.toBytes("val1"), put2);
// Print out the result, should be “Put 2 applied: true” as the checked column exists.
System.out.println("Put 2 applied: " + res3);
Put put3 = new Put(Bytes.toBytes("row2"));
// Create yet another Put instance, but using a different row
put3.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"), Bytes.toBytes("val3"));
// Store new data while checking a different row.
boolean res4 = table.checkAndPut(Bytes.toBytes("row1"), Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"), Bytes.toBytes("val1"), put3);
// We will not get here as an exception is thrown beforehand!
System.out.println("Put 3 applied: " + res4);
示例中最后的调用会抛出 DoNotRetryIOException 错误,因为 checkAndPut() 强制检查的行 (row) 必须与 Put 实例中的行匹配。可以检查一个列(column),
而更新另外的一个列,但不能跨出行边界检查。
NOTE:
-------------------------------------------------------------------------------------------------------------------------------------
HBase 提供的 compare-and-set 操作依赖于对同一行进行检查和修改。尝试检查和修改两个不同的行将返回异常。
Compare-and-set (CAS) 操作十分强大,尤其是在分布式系统中,且有多个独立的客户端同时操作数据时。通过这个方法,HBase 与其它复杂的设计结构区分
开来,提供了使不同客户端可以并发修改数据的功能。
3.2 get 方法 (Get Method)
-----------------------------------------------------------------------------------------------------------------------------------------
客户端 API 中的下一个步是从 HBase 中获取已保存的数据。为此,Table 接口提供了 get() 方法调用,以及相关的类。这类操作被划分为在单行上操作,
和在一次调用过程中在多个行上获取数据。
■ 单行 get 操作 (Single Gets)
-----------------------------------------------------------------------------------------------------------------------------------------
从一个 HBase 表中获取特定值的方法:
Result get(Get get) throws IOException
get() 操作界定为一个特定的行,但可以获取包含在该行上的任意数量的列或 cell. 创建 Get 实例时要提供一个行键 (row key), 使用下列构造器:
Get(byte[] row)
Get(Get get)
Get 的主构造器通过 row 参数指定要访问的那一行,第二个构造器接受一个已经存在的 Get 实例并复制该对象的整个细节信息,效果上等同于 clone 实例。
并且,类似于 put 操作,有很多方法来指定相当广泛依据来查找目标数据,也可以指定精确的坐标获取某个单元格 (cell) 数据:
Get addFamily(byte[] family)
Get addColumn(byte[] family, byte[] qualifier)
Get setTimeRange(long minStamp, long maxStamp) throws IOException
Get setTimeStamp(long timestamp)
Get setMaxVersions()
Get setMaxVersions(int maxVersions) throws IOException
addFamily() 调用将请求限定到给定的列族。可以多次调用以添加更多的列族。addColumn() 调用也类似,通过该调用可以更进一步限定访问的地址空间:
特定的列。其它方法可以使用户设定要查找数据的准确的时间戳,或者匹配一个时间范围的 cell.
最后,是指定要获取多少个版本的方法(如果没有设定准确的时间戳)。默认值为 1,意思为 get 调用只返回当前匹配的数据。没有参数的 setMaxVersions()
设定返回的版本数量为 Integer.MAX_VALUE, 也就是在列族描述符中(column family descriptor) 配置的最大版本数量,因此告诉 API 返回所有匹配 cell
的所有可用的版本。
之前提到过, HBase 为我们提供了一个辅助类 Bytes,其中有很多静态方法将 Java 类型转换为 byte[] 数组,反过来一样,Bytes 类提供了将数组转换为
Java 类型的方法。从 HBase 中获取数据,例如,以前保存的行,可以利用 Bytes 的这些辅助方法,将 byte[] 数据转换会 Java 类型。
static String toString(byte[] b)
static boolean toBoolean(byte[] b)
static long toLong(byte[] bytes)
static float toFloat(byte[] bytes)
static int toInt(byte[] bytes)
...
示例:
// Example application retrieving data from HBase
// Create the configuration.
Configuration conf = HBaseConfiguration.create();
// Instantiate a new table reference.
Connection connection = ConnectionFactory.createConnection(conf);
Table table = connection.getTable(TableName.valueOf("testtable"));
// Create get with specific row.
Get get = new Get(Bytes.toBytes("row1"));
// Add a column to the get.
get.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"));
// Retrieve row with selected columns from HBase.
Result result = table.get(get);
// Get a specific value for the given column.
byte[] val = result.getValue(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"));
// Print out the value while converting it back.
System.out.println("Value: " + Bytes.toString(val));
// Close the table and connection instances to free resources
table.close();
connection.close();
Get 类提供了许多方法调用,查阅 org.apache.hadoop.hbase.client.Get javadoc 获取详细信息。其中:
Get setCacheBlocks(boolean cacheBlocks)
boolean getCacheBlocks()
控制读操作如何在服务器端处理。每一个 HBase region server 有一个块缓存,用于保留最近访问的数据,以提升后续的读取效率。在某些情况下,最好
不要使用缓存以避免在完全随机的 get 访问时搅乱缓存中的信息。不要用不相关联的数据污染块缓存中的数据,最好跳过这些缓存的块使其保持不受破坏
以使其它客户端执行读取相关联的数据。
Get setCheckExistenceOnly(boolean checkExistenceOnly)
boolean isCheckExistenceOnly()
联合使用允许客户端检查某个特定的列集,或列族是否存在。示例如下:
// Checks for the existence of specific data
List<Put> puts = new ArrayList<Put>();
Put put1 = new Put(Bytes.toBytes("row1"));
put1.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"), Bytes.toBytes("val1"));
puts.add(put1);
Put put2 = new Put(Bytes.toBytes("row2"));
put2.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"), Bytes.toBytes("val2"));
puts.add(put2);
Put put3 = new Put(Bytes.toBytes("row2"));
put3.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual2"), Bytes.toBytes("val3"));
puts.add(put3);
// Insert two rows into the table.
table.put(puts);
Get get1 = new Get(Bytes.toBytes("row2"));
get1.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"));
get1.setCheckExistenceOnly(true);
// Check first with existing data.
Result result1 = table.get(get1);
byte[] val = result1.getValue(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"));
// Exists is “true”, while no cel was actually returned.
System.out.println("Get 1 Exists: " + result1.getExists());
System.out.println("Get 1 Size: " + result1.size());
System.out.println("Get 1 Value: " + Bytes.toString(val));
Get get2 = new Get(Bytes.toBytes("row2"));
get2.addFamily(Bytes.toBytes("colfam1"));
// Check for an entire family to exist.
get2.setCheckExistenceOnly(true);
Result result2 = table.get(get2);
System.out.println("Get 2 Exists: " + result2.getExists());
System.out.println("Get 2 Size: " + result2.size());
Get get3 = new Get(Bytes.toBytes("row2"));
get3.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual9999"));
// Check for a non-existent column.
get3.setCheckExistenceOnly(true);
Result result3 = table.get(get3);
System.out.println("Get 3 Exists: " + result3.getExists());
System.out.println("Get 3 Size: " + result3.size());
Get get4 = new Get(Bytes.toBytes("row2"));
get4.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual9999"));
get4.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"));
// Check for an existent, and non-existent column
get4.setCheckExistenceOnly(true);
Result result4 = table.get(get4);
// Exists is “true” because some data exists.
System.out.println("Get 4 Exists: " + result4.getExists());
System.out.println("Get 4 Size: " + result4.size());
● Alternative checks for existence
-------------------------------------------------------------------------------------------------------------------------------------
Table 类有另外一种方法检查数据在表中是否存在,提供如下方法:
boolean exists(Get get) throws IOException
boolean[] existsAll(List<Get> gets) throws IOException;
需要设定一个 Get 实例,就像使用 Table 的 get() 方法调用那样。只是验证某些数据是否存在,而不是从远程服务器上获取 cell 数据, 它们只返回
一个 boolean 类型的标志值表示数据是否存在。实际上,这些调用是在 Get 实例上使用 Get.setCheckExistenceOnly(true) 调用的快捷方式。
使用 Table.exists(), Table.existsAll(), 或者 Get.setCheckExistenceOnly() 在 region server 上的查找具有相同的语义,包括载入文件块来检查
一个行或列是否真的存在。只是避免在网络上携带数据,但对于检查非常多的列时很有用,或者进行非常频繁的检查。
Get setClosestRowBefore(boolean closestRowBefore)
boolean isClosestRowBefore()
提供某些模糊的行匹配。假设有一个复杂的行键设计,利用多个字段组成的复合数据。可以仅仅匹配行键中从左到右的数据,再假设有几个前导字段,但没有
更多具体的数据。可以使用 get() 方法请求某个特定的行。可以用一个 scan 操作开始。使用 setClosestRowBefore() 方法设置 closestRowBefore 值为 true
示例:
// Retrieves a row close to the requested, if necessary
Get get1 = new Get(Bytes.toBytes("row3"));
// Attempt to read a row that does not exist.
get1.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"));
Result result1 = table.get(get1);
System.out.println("Get 1 isEmpty: " + result1.isEmpty());
CellScanner scanner1 = result1.cellScanner();
while (scanner1.advance()) {
System.out.println("Get 1 Cell: " + scanner1.current());
}
Get get2 = new Get(Bytes.toBytes("row3"));
get2.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"));
get2.setClosestRowBefore(true);
// Instruct the get() call to fall back to the previous row, if necessary.
Result result2 = table.get(get2);
System.out.println("Get 2 isEmpty: " + result2.isEmpty());
CellScanner scanner2 = result2.cellScanner();
while (scanner2.advance()) {
System.out.println("Get 2 Cell: " + scanner2.current());
}
// Attempt to read a row that exists.
Get get3 = new Get(Bytes.toBytes("row2"));
get3.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"));
get3.setClosestRowBefore(true);
Result result3 = table.get(get3);
System.out.println("Get 3 isEmpty: " + result3.isEmpty());
CellScanner scanner3 = result3.cellScanner();
while (scanner3.advance()) {
System.out.println("Get 3 Cell: " + scanner3.current());
}
Get get4 = new Get(Bytes.toBytes("row2"));
// Read exactly a row that exists.
get4.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"));
Result result4 = table.get(get4);
System.out.println("Get 4 isEmpty: " + result4.isEmpty());
CellScanner scanner4 = result4.cellScanner();
while (scanner4.advance()) {
System.out.println("Get 4 Cell: " + scanner4.current());
}
int getMaxResultsPerColumnFamily()
Get setMaxResultsPerColumnFamily(int limit)
int getRowOffsetPerColumnFamily()
Get setRowOffsetPerColumnFamily(int offset)
前面的一对方法用户处理 get 请求返回的 cell 的最大数量。后面的一对方法用于在行上设置一个可选的偏移量。
// Retrieves parts of a row with offset and limit
Put put = new Put(Bytes.toBytes("row1"));
for (int n = 1; n <= 1000; n++) {
String num = String.format("%04d", n);
put.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual" + num), Bytes.toBytes("val" + num));
}
table.put(put);
Get get1 = new Get(Bytes.toBytes("row1"));
// Ask for ten cells to be returned at most.
get1.setMaxResultsPerColumnFamily(10);
Result result1 = table.get(get1);
CellScanner scanner1 = result1.cellScanner();
while (scanner1.advance()) {
System.out.println("Get 1 Cell: " + scanner1.current());
}
Get get2 = new Get(Bytes.toBytes("row1"));
get2.setMaxResultsPerColumnFamily(10);
// In addition, also skip the first 100 cells.
get2.setRowOffsetPerColumnFamily(100);
Result result2 = table.get(get2);
CellScanner scanner2 = result2.cellScanner();
while (scanner2.advance()) {
System.out.println("Get 2 Cell: " + scanner2.current());
}
■ Result 类 (The Result class)
-----------------------------------------------------------------------------------------------------------------------------------------
使用 get() 方法调用获取数据时,会接收一个 Result 类的实例,其中包含所有匹配的单元格(cell)。 利用 Result 提供的方法,可以访问从服务器端返
回的给定行和匹配特定查询的所有数据的详细信息,例如列族,列限定符,时间戳,等等。
Result 有很多工具方法可以用于获取特定结果,第一类方法如下:
byte[] getRow()
byte[] getValue(byte[] family, byte[] qualifier)
byte[] value()
ByteBuffer getValueAsByteBuffer(byte[] family, byte[] qualifier)
ByteBuffer getValueAsByteBuffer(byte[] family, int foffset, int flength, byte[] qualifier, int qoffset, int qlength)
boolean loadValue(byte[] family, byte[] qualifier, ByteBuffer dst) throws BufferOverflowException
boolean loadValue(byte[] family, int foffset, int flength, byte[] qualifier, int qoffset, int qlength, ByteBuffer dst)
throws BufferOverflowException
CellScanner cellScanner()
Cell[] rawCells()
List<Cell> listCells()
boolean isEmpty()
int size()
getRow() 方法返回行键(row key), 调用 get() 方法时创建 Get 类实例时指定的行键值。size() 方法返回当前 Result 实例所包含 Cell 实例的数量。
可以使用这个调用,或者 isEmpty() 方法调用来检查自己的客户端代码,获取调用是否返回了一些匹配的数据。
getValue() 调用可以获取返回数据中某一特定的 cell 中的数据。由于不能在此调用中指定哪个时间戳,或者说,想要获取版本,因此得到的是最新版本
的数据。value() 调用返回找到的第一列中最新版本的数据。由于列在服务器上是按词汇顺序排序的,因此返回的列值首先是按列名(包括列族和限定符)
排序的。
还有其它一些单元格值的访问器,getValueAsByteBuffer() 和 loadValue(), 它们或者创建一个 Java ByteBuffer 对象,将数据的值封装到对象内的字节
数组中,或者将数据拷贝到提供的数据容器中。
访问原始的,低级别的 Cell 实例,通过 rawCells() 方法,返回当前 Result 实例内的 Cell 实例数组。listCells() 调用简单地将 rawCells() 调用
返回的数组转换到一个 List 实例中,给用户一个通过迭代器访问的便捷方法。Result 类也实现了 CellScannable 接口,因此可以直接地迭代访问其中
含有的 cell.
NOTE:
-------------------------------------------------------------------------------------------------------------------------------------
rawCells() 返回的数组是已经按词汇顺序排过序的,顾及到了 Cell 实例的完全坐标(full coordinates). 因此首先按列族排序,然后在每个列族内按
限定符排序,然后按时间戳,最后按类型 (type) 排序。
另一类访问器提供更加面向列的(column-oriented) 访问:
List<Cell> getColumnCells(byte[] family, byte[] qualifier)
Cell getColumnLatestCell(byte[] family, byte[] qualifier)
Cell getColumnLatestCell(byte[] family, int foffset, int flength, byte[] qualifier, int qoffset, int qlength)
boolean containsColumn(byte[] family, byte[] qualifier)
boolean containsColumn(byte[] family, int foffset, int flength, byte[] qualifier, int qoffset, int qlength)
boolean containsEmptyColumn(byte[] family, byte[] qualifier)
boolean containsEmptyColumn(byte[] family, int foffset, int flength, byte[] qualifier, int qoffset, int qlength)
boolean containsNonEmptyColumn(byte[] family, byte[] qualifier)
boolean containsNonEmptyColumn(byte[] family, int foffset, int flength, byte[] qualifier, int qoffset, int qlength)
通过 getColumnCells() 方法可以请求某个特定列的多个值,返回值版本的最大数量有之前调用 get() 方法时配置的 Get 实例界定,默认为 1.换句话说,
返回的列表可能包含 0 (给定 row 上的列没有值)或 1 个项目,意为值的最新版本。如果指定了返回大于默认的 1 个版本的值,列表可能包含任意数量的项目,
最多为指定的最大值。
getColumnLatestCell() 方法返回指定列的最新版本 cell, 但与 getValue() 相反,它不返回值的原始字节数组,而是完全的 Cell 实例。
containsColumn() 是检查某个特定的列内是否有 cell 返回的的便捷方法。
第三类访问返回数据的方法,是面向 map 的方法(map-oriented):
NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> getMap()
NavigableMap<byte[], NavigableMap<byte[], byte[]>> getNoVersionMap()
NavigableMap<byte[], byte[]> getFamilyMap(byte[] family)
最通用的方法时 getMap(), 在一个 Java Map 类实例中返回整个结果集,可以迭代访问所有的值。 getNoVersionMap() 做的是同样的事,只是仅仅包含每个列
最新版本的 cell. 最后,getFamilyMap() 方法可以只选择某个给定列族的数据,但包含所有版本。
不论使用 Result 的哪种访问方法来适应用户的需求,数据已经从服务器上通过网络传输到了客户端进程,因此不会导致性能或资源的负面影响。
最后,还有一些无法归为以上类别中的方法,参考 API javadoc 了解详情。
■ Get 列表 (List of Gets)
-----------------------------------------------------------------------------------------------------------------------------------------
另一个与 put() 调用相似的 get() 方法调用是用户可以用一次请求获取多行数据,它允许用户快速高效地从远程服务器获取相关联的,或者完全随机的多
行数据。
NOTE:
-------------------------------------------------------------------------------------------------------------------------------------
实际上,请求有可能被发往多个不同的服务器,但这部分逻辑已经被封装起来。因此对于客户端代码来说,还是表现为一次请求。
方法签名:
Result[] get(List<Get> gets) throws IOException
用户需要创建一个列表,并把准备好的 Get 实例添加到列表中,然后将列表传给 get(), 返回一个与 Get 列表数量相同的 Result 数组。
示例:
// Example of retrieving data from HBase using lists of Get instances
// Prepare commonly used byte arrays.
byte[] cf1 = Bytes.toBytes("colfam1");
byte[] qf1 = Bytes.toBytes("qual1");
byte[] qf2 = Bytes.toBytes("qual2");
byte[] row1 = Bytes.toBytes("row1");
byte[] row2 = Bytes.toBytes("row2");
// Create a list that holds the Get instances.
List<Get> gets = new ArrayList<Get>();
// Add the Get instances to the list.
Get get1 = new Get(row1);
get1.addColumn(cf1, qf1);
gets.add(get1);
Get get2 = new Get(row2);
get2.addColumn(cf1, qf1);
gets.add(get2);
Get get3 = new Get(row2);
get3.addColumn(cf1, qf2);
gets.add(get3);
// Retrieve rows with selected columns from HBase.
Result[] results = table.get(gets);
System.out.println("First iteration...");
// Iterate over results and check what values are available.
for (Result result : results) {
String row = Bytes.toString(result.getRow());
System.out.print("Row: " + row + " ");
byte[] val = null;
if (result.containsColumn(cf1, qf1)) {
val = result.getValue(cf1, qf1);
System.out.println("Value: " + Bytes.toString(val));
}
if (result.containsColumn(cf1, qf2)) {
val = result.getValue(cf1, qf2);
System.out.println("Value: " + Bytes.toString(val));
}
}
System.out.println("Second iteration...");
// Iterate over results again, printing out all values.
for (Result result : results) {
// Two different ways to access the cell data.
for (Cell cell : result.listCells()) {
System.out.println(
"Row: " + Bytes.toString(
cell.getRowArray(), cell.getRowOffset(), cell.getRowLength())
+
" Value: " + Bytes.toString(CellUtil.cloneValue(cell)));
}
}
System.out.println("Third iteration...");
for (Result result : results) {
System.out.println(result);
}
运行代码,将看到显示类似如下的输出:
First iteration...
Row: row1 Value: val1
Row: row2 Value: val2
Row: row2 Value: val3
Second iteration...
Row: row1 Value: val1
Row: row2 Value: val2
Row: row2 Value: val3
Third iteration...
keyvalues={row1/colfam1:qual1/1426678215864/Put/vlen=4/seqid=0}
keyvalues={row2/colfam1:qual1/1426678215864/Put/vlen=4/seqid=0}
keyvalues={row2/colfam1:qual2/1426678215864/Put/vlen=4/seqid=0}
两次遍历返回了同样的值,说明从服务器端得到结果之后,用户可以有多种方式访问结果。
Get 列表 get() 调用返回异常的方法与 Put 列表 put() 调用返回异常不同,get() 方法要么返回与给定的 Get 列表大小相同的 Result 数组,要么抛出
异常。下面示例展示了这种行为:
// Example trying to read an erroneous column family
List<Get> gets = new ArrayList<Get>();
// Add the Get instances to the list.
Get get1 = new Get(row1);
get1.addColumn(cf1, qf1);
gets.add(get1);
Get get2 = new Get(row2);
get2.addColumn(cf1, qf1);
gets.add(get2);
Get get3 = new Get(row2);
get3.addColumn(cf1, qf2);
gets.add(get3);
Get get4 = new Get(row2);
// Add the bogus column family get.
get4.addColumn(Bytes.toBytes("BOGUS"), qf2);
gets.add(get4);
// An exception is thrown and the process is aborted.
Result[] results = table.get(gets);
// This line will never reached!
System.out.println("Result count: " + results.length);
执行这个例子会导致整个 get() 操作终止,程序会抛出类似下面这样的异常,并且没有返回值:
org.apache.hadoop.hbase.client.RetriesExhaustedWithDetailsException:
Failed 1 action: NoSuchColumnFamilyException: 1 time,
servers with issues: 10.0.0.57:51640,
Exception in thread "main" \
org.apache.hadoop.hbase.client.RetriesExhaustedWithDetailsException:
\
Failed 1 action: \
org.apache.hadoop.hbase.regionserver.NoSuchColumnFamilyException:
\
Column family BOGUS does not exist in region \
testtable,,1426678215640.de657eebc8e3422376e918ed77fc33ba. \
in table 'testtable', {NAME => 'colfam1', ...}
at org.apache.hadoop.hbase.regionserver.HRegion.checkFamily(...)
at org.apache.hadoop.hbase.regionserver.HRegion.get(...)
...
对于批量操作中的局部错误,有一种更精细的处理方法。即使用 batch() 方法。
3.3 删除方法 (Delete Method)
-----------------------------------------------------------------------------------------------------------------------------------------
delete() 方法由几个变体,一个用于单行删除,一个接受 Delete 列表参数删除,另外一个提供原子性的,服务端 check-and-delete.
■ 单行删除 (Single Deletes)
-----------------------------------------------------------------------------------------------------------------------------------------
单行 delete() 方法调用接受一个 Delete 实例:
void delete(Delete delete) throws IOException
与 get() 和 put() 方法调用类似,需要创建一个 Delete 实例,然后向要删除的数据添加详细信息,构造器如下:
Delete(byte[] row)
Delete(byte[] row, long timestamp)
Delete(final byte[] rowArray, final int rowOffset, final int rowLength)
Delete(final byte[] rowArray, final int rowOffset, final int rowLength, long ts)
Delete(final Delete d)
需要为 Delete 提供要修改的行,以及可选地指定要操作的版本/时间戳。
此外,应进一步限定要删除数据的详细信息,使用如下方法:
Delete addFamily(final byte[] family)
Delete addFamily(final byte[] family, final long timestamp)
Delete addFamilyVersion(final byte[] family, final long timestamp)
Delete addColumns(final byte[] family, final byte[] qualifier)
Delete addColumns(final byte[] family, final byte[] qualifier, final long timestamp)
Delete addColumn(final byte[] family, final byte[] qualifier)
Delete addColumn(byte[] family, byte[] qualifier, long timestamp)
void setTimestamp(long timestamp)
有四类方法可用于限定删除数据所涉及的范围。首先可以通过 addFamily() 方法删除整个列族,包括其中所含有的所有列。一种类型是通过 addColumns(),
将操作限定在某一个准确的列上。地三种类型类似,使用 addColumn(), 操作限定在一个特定的列上,但删除最新或者指定版本的数据。
最后一种是 setTimestamp(), 它允许设定一个时间戳,用于后续的 addXYZ() 调用。实际上,利用一个 Delete 构造器接受一个显式的时间戳参数,只是在
创建了一个 Delete 实例之后调用了 setTimestamp(). 一旦实例范围的时间戳设定好,所有进一步的操作都会利用此时间戳。
对于高级用户,还有另外的方法可用:
Delete addDeleteMarker(Cell kv) throws IOException
这个调用检查提供的 Cell 实例为删除类型,行键匹配当前的一个 Delete 实例。
示例:使用单行 delete() 调用
// Example application deleting data from HBase
// Create delete with specific row.
Delete delete = new Delete(Bytes.toBytes("row1"));
// Set timestamp for row deletes.
delete.setTimestamp(1);
// Delete the latest version only in one column
delete.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"));
// Delete specific version in one column
delete.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual3"), 3);
// Delete all versions in one column.
delete.addColumns(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"));
//Delete the given and all older versions in one column.
delete.addColumns(Bytes.toBytes("colfam1"), Bytes.toBytes("qual3"), 2);
// Delete entire family, all columns and versions.
delete.addFamily(Bytes.toBytes("colfam1"));
// Delete the given and all older versions in the entire column family, i.e., from all columns therein.
delete.addFamily(Bytes.toBytes("colfam1"), 3);
// Delete the data from the HBase table.
table.delete(delete);
示例中列举了用户通过设定不同的参数操作 delete() 方法。实际应用中没什么意义,可以随意注释掉一些调用,观察控制台显示结果变化。
删除操作所设定的时间戳只对匹配的单元格有影响,即匹配给定时间戳的列和值。另一方面,如果不设定时间戳,服务器会强制检索服务器端最新的时间戳,
这比执行一个具有明确时间戳的删除要慢。
如果尝试删除一个不存在的时间戳的 cell, 什么也不会发生。例如,某一列有两个版本,版本 10 和版本 20,删除版本 15 将不会影响现存的任何版本。
■ Delete 列表 (List of Deletes)
-----------------------------------------------------------------------------------------------------------------------------------------
基于列表的 delete() 调用与基于列表的 put 操作类似,需要创建一个 Delete 实例的列表,配置这些实例,然后调用如下方法:
void delete(List<Delete> deletes) throws IOException
下面示例展示了三个不同行的删除操作,删除了它们所包含的各种细节。运行这个示例,会看到打印出删除之前和之后的状态。
NOTE:
-------------------------------------------------------------------------------------------------------------------------------------
正如其它基于列表的操作,用户不能对删除操作在远程服务器上的执行顺序做任何假设。 API 会重新安排它们,并将同一个 region server 的操作集中
到一个 RPC 请求中以提升性能。如果需要确保执行的顺序,用户需要把这些调用分成更小的组,并控制组之间的执行顺序。最坏的情况下,需要发送单独
的 Delete 调用以保证执行顺序。
// Example application deleting lists of data from HBase
// Create a list that holds the Delete instances.
List<Delete> deletes = new ArrayList<Delete>();
Delete delete1 = new Delete(Bytes.toBytes("row1"));
// Set timestamp for row deletes
delete1.setTimestamp(4);
deletes.add(delete1);
Delete delete2 = new Delete(Bytes.toBytes("row2"));
// Delete the latest version only in one column
delete2.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"));
// Delete the given and all older versions in another column.
delete2.addColumns(Bytes.toBytes("colfam2"), Bytes.toBytes("qual3"), 5);
deletes.add(delete2);
Delete delete3 = new Delete(Bytes.toBytes("row3"));
// Delete entire family, all columns and versions
delete3.addFamily(Bytes.toBytes("colfam1"));
// Delete the given and all older versions in the entire column family, i.e., from all columns therein.
delete3.addFamily(Bytes.toBytes("colfam2"), 3);
deletes.add(delete3);
// Delete the data from multiple rows the HBase table.
table.delete(deletes);
输出类似如下:
Before delete call...
Cell: row1/colfam1:qual1/2/Put/vlen=4/seqid=0, Value: val2
Cell: row1/colfam1:qual1/1/Put/vlen=4/seqid=0, Value: val1
Cell: row1/colfam1:qual2/4/Put/vlen=4/seqid=0, Value: val4
Cell: row1/colfam1:qual2/3/Put/vlen=4/seqid=0, Value: val3
Cell: row1/colfam1:qual3/6/Put/vlen=4/seqid=0, Value: val6
Cell: row1/colfam1:qual3/5/Put/vlen=4/seqid=0, Value: val5
Cell: row1/colfam2:qual1/2/Put/vlen=4/seqid=0, Value: val2
Cell: row1/colfam2:qual1/1/Put/vlen=4/seqid=0, Value: val1
Cell: row1/colfam2:qual2/4/Put/vlen=4/seqid=0, Value: val4
Cell: row1/colfam2:qual2/3/Put/vlen=4/seqid=0, Value: val3
Cell: row1/colfam2:qual3/6/Put/vlen=4/seqid=0, Value: val6
Cell: row1/colfam2:qual3/5/Put/vlen=4/seqid=0, Value: val5
Cell: row2/colfam1:qual1/2/Put/vlen=4/seqid=0, Value: val2
Cell: row2/colfam1:qual1/1/Put/vlen=4/seqid=0, Value: val1
Cell: row2/colfam1:qual2/4/Put/vlen=4/seqid=0, Value: val4
Cell: row2/colfam1:qual2/3/Put/vlen=4/seqid=0, Value: val3
Cell: row2/colfam1:qual3/6/Put/vlen=4/seqid=0, Value: val6
Cell: row2/colfam1:qual3/5/Put/vlen=4/seqid=0, Value: val5
Cell: row2/colfam2:qual1/2/Put/vlen=4/seqid=0, Value: val2
Cell: row2/colfam2:qual1/1/Put/vlen=4/seqid=0, Value: val1
Cell: row2/colfam2:qual2/4/Put/vlen=4/seqid=0, Value: val4
Cell: row2/colfam2:qual2/3/Put/vlen=4/seqid=0, Value: val3
Cell: row2/colfam2:qual3/6/Put/vlen=4/seqid=0, Value: val6
Cell: row2/colfam2:qual3/5/Put/vlen=4/seqid=0, Value: val5
Cell: row3/colfam1:qual1/2/Put/vlen=4/seqid=0, Value: val2
Cell: row3/colfam1:qual1/1/Put/vlen=4/seqid=0, Value: val1
Cell: row3/colfam1:qual2/4/Put/vlen=4/seqid=0, Value: val4
Cell: row3/colfam1:qual2/3/Put/vlen=4/seqid=0, Value: val3
Cell: row3/colfam1:qual3/6/Put/vlen=4/seqid=0, Value: val6
Cell: row3/colfam1:qual3/5/Put/vlen=4/seqid=0, Value: val5
Cell: row3/colfam2:qual1/2/Put/vlen=4/seqid=0, Value: val2
Cell: row3/colfam2:qual1/1/Put/vlen=4/seqid=0, Value: val1
Cell: row3/colfam2:qual2/4/Put/vlen=4/seqid=0, Value: val4
Cell: row3/colfam2:qual2/3/Put/vlen=4/seqid=0, Value: val3
Cell: row3/colfam2:qual3/6/Put/vlen=4/seqid=0, Value: val6
Cell: row3/colfam2:qual3/5/Put/vlen=4/seqid=0, Value: val5
After delete call...
Cell: row1/colfam1:qual3/6/Put/vlen=4/seqid=0, Value: val6
Cell: row1/colfam1:qual3/5/Put/vlen=4/seqid=0, Value: val5
Cell: row1/colfam2:qual3/6/Put/vlen=4/seqid=0, Value: val6
Cell: row1/colfam2:qual3/5/Put/vlen=4/seqid=0, Value: val5
Cell: row2/colfam1:qual1/1/Put/vlen=4/seqid=0, Value: val1
Cell: row2/colfam1:qual2/4/Put/vlen=4/seqid=0, Value: val4
Cell: row2/colfam1:qual2/3/Put/vlen=4/seqid=0, Value: val3
Cell: row2/colfam1:qual3/6/Put/vlen=4/seqid=0, Value: val6
Cell: row2/colfam1:qual3/5/Put/vlen=4/seqid=0, Value: val5
Cell: row2/colfam2:qual1/2/Put/vlen=4/seqid=0, Value: val2
Cell: row2/colfam2:qual1/1/Put/vlen=4/seqid=0, Value: val1
Cell: row2/colfam2:qual2/4/Put/vlen=4/seqid=0, Value: val4
Cell: row2/colfam2:qual2/3/Put/vlen=4/seqid=0, Value: val3
Cell: row2/colfam2:qual3/6/Put/vlen=4/seqid=0, Value: val6
Cell: row3/colfam2:qual2/4/Put/vlen=4/seqid=0, Value: val4
Cell: row3/colfam2:qual3/6/Put/vlen=4/seqid=0, Value: val6
Cell: row3/colfam2:qual3/5/Put/vlen=4/seqid=0, Value: val5
示例代码的执行过程中,使用以下方法可以看到 Cell 的详细情况:
System.out.println("Cell: " + cell + ", Value: " +
Bytes.toString(cell.getValueArray(), cell.getValueOffset(),
cell.getValueLength()));
最后要介绍的是基于列表的 delete () 操作的异常处理。提交的 deletes 参数,就是 Delete 实例列表,在调用返回时,被修改为值包含失败的 Delete 实例。
换句话说,如果每个操作都成功完成了,这个列表是空的。如果有从远程服务器上报告的异常,delete() 调用会抛出该异常。应使用 try/catch 块保护delete
调用。
示例:修改自上一个示例
// Example deleting faulty data from HBase
Delete delete4 = new Delete(Bytes.toBytes("row2"));
// Add bogus column family to trigger an error.
delete4.addColumn(Bytes.toBytes("BOGUS"), Bytes.toBytes("qual1"));
deletes.add(delete4);
try {
// Delete the data from multiple rows the HBase table.
table.delete(deletes);
} catch (Exception e) {
// Guard against remote exceptions
System.err.println("Error: " + e);
}
table.close();
// Check the length of the list after the call.
System.out.println("Deletes length: " + deletes.size());
for (Delete delete : deletes) {
// Print out failed delete for debugging purposes.
System.out.println(delete);
}
运行示例,输出类似如下的结果:
Before delete call...
Cell: row1/colfam1:qual1/2/Put/vlen=4/seqid=0, Value: val2
Cell: row1/colfam1:qual1/1/Put/vlen=4/seqid=0, Value: val1
...
Cell: row3/colfam2:qual3/6/Put/vlen=4/seqid=0, Value: val6
Cell: row3/colfam2:qual3/5/Put/vlen=4/seqid=0, Value: val5
Deletes length: 1
Error: org.apache.hadoop.hbase.client.RetriesExhaustedWithDetailsException:
\
Failed 1 action: \
org.apache.hadoop.hbase.regionserver.NoSuchColumnFamilyException:
\
Column family BOGUS does not exist ...
...
: 1 time,
{"ts":9223372036854775807,"totalColumns":1,"families":{"BOGUS":[{ \
"timestamp":9223372036854775807,"tag":[],"qualifier":"
qual1","vlen":0}]}, \
"row":"row2"}
After delete call...
Cell: row1/colfam1:qual3/6/Put/vlen=4/seqid=0, Value: val6
Cell: row1/colfam1:qual3/5/Put/vlen=4/seqid=0, Value: val5
...
Cell: row3/colfam2:qual3/6/Put/vlen=4/seqid=0, Value: val6
Cell: row3/colfam2:qual3/5/Put/vlen=4/seqid=0, Value: val5
正如所期望的,列表中含有一个剩下的 Delete 实例:bogus 列族的那个 Delete.
■ 原子性操作 Check-and-Delete (Atomic Check-and-Delete)
-----------------------------------------------------------------------------------------------------------------------------------------
之前讨论过 “Atomic Check-and-Put”, 对于 delete() 操作也有相应服务器端的,read-modify-write 功能:
boolean checkAndDelete(byte[] row, byte[] family, byte[] qualifier, byte[] value, Delete delete) throws IOException
boolean checkAndDelete(byte[] row, byte[] family, byte[] qualifier, CompareFilter.CompareOp compareOp, byte[] value, Delete delete)
throws IOException
在实际的删除操作执行之前,要给定 row key, column family, qualifier, 以及要比较的 value. 第一个调用意为使用给定的 value 与存储的值进行相等
比较(equal), 第二个调用指定一个实际的比较器,可以进行更复杂灵活的检查测试,例如,给定的 value 相等或小于存储的值。
如果测试失败,什么也不会删除,并且调用返回 false. 如果检查成功,删除会应用并返回 true.
示例:
// Example application using the atomic Check-and-Delete operations
// Create a new Delete instance
Delete delete1 = new Delete(Bytes.toBytes("row1"));
delete1.addColumns(Bytes.toBytes("colfam1"), Bytes.toBytes("qual3"));
// Check if column does not exist and perform optional delete operation.
boolean res1 = table.checkAndDelete(Bytes.toBytes("row1"), Bytes.toBytes("colfam2"), Bytes.toBytes("qual3"), null, delete1);
// Print out the result, should be “Delete successful: false”.
System.out.println("Delete 1 successful: " + res1);
// Delete checked column manually
Delete delete2 = new Delete(Bytes.toBytes("row1"));
delete2.addColumns(Bytes.toBytes("colfam2"), Bytes.toBytes("qual3"));
table.delete(delete2);
// Attempt to delete same cell again.
boolean res2 = table.checkAndDelete(Bytes.toBytes("row1"), Bytes.toBytes("colfam2"), Bytes.toBytes("qual3"), null, delete1);
// Print out the result, should be “Delete successful: true” since the checked column now is gone.
System.out.println("Delete 2 successful: " + res2);
// Create yet another Delete instance, but using a different row
Delete delete3 = new Delete(Bytes.toBytes("row2"));
delete3.addFamily(Bytes.toBytes("colfam1"));
try{
// Try to delete while checking a different row.
boolean res4 = table.checkAndDelete(Bytes.toBytes("row1"), Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"), Bytes.toBytes("val1"), delete3);
// We will not get here as an exception is thrown beforehand
System.out.println("Delete 3 successful: " + res4);
} catch (Exception e) {
System.err.println("Error: " + e.getMessage());
}
运行示例,会看到类似如下的输出:
Before delete call...
Cell: row1/colfam1:qual1/1/Put/vlen=4/seqid=0, Value: val1
Cell: row1/colfam1:qual2/2/Put/vlen=4/seqid=0, Value: val2
Cell: row1/colfam1:qual3/3/Put/vlen=4/seqid=0, Value: val3
Cell: row1/colfam2:qual1/1/Put/vlen=4/seqid=0, Value: val1
Cell: row1/colfam2:qual2/2/Put/vlen=4/seqid=0, Value: val2
Cell: row1/colfam2:qual3/3/Put/vlen=4/seqid=0, Value: val3
Delete 1 successful: false
Delete 2 successful: true
Error: org.apache.hadoop.hbase.DoNotRetryIOException: \
Action's getRow must match the passed row
...
After delete call...
Cell: row1/colfam1:qual1/1/Put/vlen=4/seqid=0, Value: val1
Cell: row1/colfam1:qual2/2/Put/vlen=4/seqid=0, Value: val2
Cell: row1/colfam2:qual1/1/Put/vlen=4/seqid=0, Value: val1
Cell: row1/colfam2:qual2/2/Put/vlen=4/seqid=0, Value: val2
使用 null 作为 value 参数的值会进行不存在(nonexistence)测试,如果指定的列不存在,测试成功。
与此前关于 CAS 调用一样,只能对同一行数据进行检查并删除。示例中尝试检查一个行键,而提供的 Delete 实例却指向另一个行键,因此会抛出异常。但
检查允许跨多个列族,例如,可以使用一组列族控制筛选其它的列。
3.4 Append 方法 (Append Method)
-----------------------------------------------------------------------------------------------------------------------------------------
与一般的 CRUD 功能类似,有另一种数据操作的方法调用。不是创建或更新某个列的值, append() method 执行原子性的 read-modify-write 操作,将数
据添加到某个列上:
Result append(final Append append) throws IOException
这个调用接受一个 Append 类实例,可以通过如下构造器创建:
Append(byte[] row)
Append(final byte[] rowArray, final int rowOffset, final int rowLength)
Append(Append a)
或者提供一个明显的行键,或者一个已存在的,更大的字节数组持有行键的子集,传入必要的偏移量(rowOffset) 和长度值。第三个构造器传入一个已存在的
Append 实例,并从其中拷贝所有参数。
创建了 Append 实例之后,可以利用如下方法添加细节信息:
Append add(byte[] family, byte[] qualifier, byte[] value)
Append add(final Cell cell)
类似于 Put 实例,必须调用其中一个方法,否则之后调用 append() 方法调用会抛出异常。第一个方法接受列族和限定符名称,以及要追加的的值。第二个方
法从已存在的 cell 实例拷贝这些参数。
示例:
//Example application appending data to a column in HBase
Append append = new Append(Bytes.toBytes("row1"));
append.add(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"),
Bytes.toBytes("newvalue"));
append.add(Bytes.toBytes("colfam1"), Bytes.toBytes("qual2"),
Bytes.toBytes("anothervalue"));
table.append(append);
输出类似如下:
Before append call...
Cell: row1/colfam1:qual1/1/Put/vlen=8/seqid=0, Value: oldvalue
After append call...
Cell: row1/colfam1:qual1/1426778944272/Put/vlen=16/seqid=0,
Value: oldvaluenewvalue
Cell: row1/colfam1:qual1/1/Put/vlen=8/seqid=0, Value: oldvalue
Cell: row1/colfam1:qual2/1426778944272/Put/vlen=12/seqid=0,
Value: anothervalue
append() 方法有一个特殊选项可以设置不从服务器返回数据:
Append setReturnResults(boolean returnResults)
boolean isReturnResults()
通常,新更新的 cell 会返回给调用者。但如果想将 append() 发送到服务器,并不关心其返回的结果,可以调用 setReturnResults( false) 来忽略携带的结果,
此时会返回 null.
3.5 Mutate 方法 (Mutate Method)
-----------------------------------------------------------------------------------------------------------------------------------------
■ 单行 Mutations (Single Mutations)
-----------------------------------------------------------------------------------------------------------------------------------------
到目前为止,所有的操作在 Table 中都有它们特定的方法,以及数据相关的类型。但如果想要跨操作并自动执行某一个行的更新,就需要 mutateRow()调用
void mutateRow(final RowMutations rm) throws IOException
RowMutations 参数是一个容器,它接受 Put 或 Delete 实例参数,然后在一次调用中在服务器端数据上应用这两种操作。
RowMutations : Performs multiple mutations atomically on a single row
RowMutations(byte[] row)
add(Delete)
add(Put)
getMutations()
getRow()
使用一个特定的行键创建 RowMutations 实例,然后向其添加任意的 delete 或 put 实例。用于创建 RowMutations 实例的行键必须与添加的任何 mutation
行键相匹配,否则在尝试添加时会收到异常。
示例:
// Modifies a row with multiple operations
Put put = new Put(Bytes.toBytes("row1"));
put.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"), 4, Bytes.toBytes("val99"));
put.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual4"), 4, Bytes.toBytes("val100"));
Delete delete = new Delete(Bytes.toBytes("row1"));
delete.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual2"));
RowMutations mutations = new RowMutations(Bytes.toBytes("row1"));
mutations.add(put);
mutations.add(delete);
table.mutateRow(mutations);
输出类似如下:
Before delete call...
Cell: row1/colfam1:qual1/1/Put/vlen=4/seqid=0, Value: val1
Cell: row1/colfam1:qual2/2/Put/vlen=4/seqid=0, Value: val2
Cell: row1/colfam1:qual3/3/Put/vlen=4/seqid=0, Value: val3
After mutate call...
Cell: row1/colfam1:qual1/4/Put/vlen=5/seqid=0, Value: val99
Cell: row1/colfam1:qual1/1/Put/vlen=4/seqid=0, Value: val1
Cell: row1/colfam1:qual3/3/Put/vlen=4/seqid=0, Value: val3
Cell: row1/colfam1:qual4/4/Put/vlen=6/seqid=0, Value: val100
■ 原子性操作 Check-and-Mutate (Atomic Check-and-Mutate)
-----------------------------------------------------------------------------------------------------------------------------------------
对于 mutation 操作也有访问服务器端的,read-modify-write 功能设计:
public boolean checkAndMutate(final byte[] row, final byte[] family, final byte[] qualifier, final CompareOp compareOp,
final byte[] value, final RowMutations rm) throws IOException
在实际的 mutation 操作应用之前,需要制定 row key, column family, qualifier, 以及要检查的 value。该调用让用户指定实际的比较器,提供更灵活的
检查测试,例如使给定的 value 等于(equal) 或小于存储的值。
如果测试失败,什么也不会应用并且调用返回 false 值。如果检查成功,mutation 操作会应用并返回 true.
示例:
// Example using the atomic check-and-mutate operations
Put put = new Put(Bytes.toBytes("row1"));
put.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"), 4, Bytes.toBytes("val99"));
put.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual4"), 4, Bytes.toBytes("val100"));
Delete delete = new Delete(Bytes.toBytes("row1"));
delete.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual2"));
RowMutations mutations = new RowMutations(Bytes.toBytes("row1"));
mutations.add(put);
mutations.add(delete);
// Check if the column contains a value that is less than “val1”. Here we receive “false” as the value is equal, but not lesser.
boolean res1 = table.checkAndMutate(Bytes.toBytes("row1"), Bytes.toBytes("colfam2"), Bytes.toBytes("qual1"),
CompareFilter.CompareOp.LESS, Bytes.toBytes("val1"), mutations);
System.out.println("Mutate 1 successful: " + res1);
// Update the checked column to have a value greater than what we check for.
Put put2 = new Put(Bytes.toBytes("row1"));
put2.addColumn(Bytes.toBytes("colfam2"), Bytes.toBytes("qual1"), 4, Bytes.toBytes("val2"));
table.put(put2);
// Now “val1” is less than “val2” (binary comparison) and we expect “true” to be printed on the console.
boolean res2 = table.checkAndMutate(Bytes.toBytes("row1"), Bytes.toBytes("colfam2"), Bytes.toBytes("qual1"),
CompareFilter.CompareOp.LESS, Bytes.toBytes("val1"), mutations);
System.out.println("Mutate 2 successful: " + res2);
输出:
Before check and mutate calls...
Cell: row1/colfam1:qual1/1/Put/vlen=4/seqid=0, Value: val1
Cell: row1/colfam1:qual2/2/Put/vlen=4/seqid=0, Value: val2
Cell: row1/colfam1:qual3/3/Put/vlen=4/seqid=0, Value: val3
Cell: row1/colfam2:qual1/1/Put/vlen=4/seqid=0, Value: val1
Cell: row1/colfam2:qual2/2/Put/vlen=4/seqid=0, Value: val2
Cell: row1/colfam2:qual3/3/Put/vlen=4/seqid=0, Value: val3
Mutate 1 successful: false
Mutate 2 successful: true
After check and mutate calls...
Cell: row1/colfam1:qual1/4/Put/vlen=5/seqid=0, Value: val99
Cell: row1/colfam1:qual1/1/Put/vlen=4/seqid=0, Value: val1
Cell: row1/colfam1:qual3/3/Put/vlen=4/seqid=0, Value: val3
Cell: row1/colfam1:qual4/4/Put/vlen=6/seqid=0, Value: val100
Cell: row1/colfam2:qual1/4/Put/vlen=4/seqid=0, Value: val2
Cell: row1/colfam2:qual1/1/Put/vlen=4/seqid=0, Value: val1
Cell: row1/colfam2:qual2/2/Put/vlen=4/seqid=0, Value: val2
Cell: row1/colfam2:qual3/3/Put/vlen=4/seqid=0, Value: val3
与之前的操作类似,将 value 参数设定为 null 测试不存在,如果指定的列不存在,检查成功。
与 put 或 delete 的 CAS 调用类似,只能在同一行上执行 check-and-modify 操作。
系列目录:
参考:
《HBase - The Definitive Guide - 2nd Edition》Early release —— 2015.7 Lars George