目录
连接HBase
Table类:CRUD
put
单行put
客户端的写缓冲区
多行Put
原子性Put
get
单行Get
Result类
多行Get
delete
单行delete
多行delete
原子性Delete
批量处理操作
扫描
Scan
ResultScanner
缓存和批量处理
Admin类:管理类操作
连接HBase
在HBase架构中的数据层面,HMaster负责管理类的操作(例如表、命令空间的创建删除),而HRegionServer负责表的读写(即CRUD)。对应地在客户端API中有两个类:Admin类与HTable(Table)类,Admin类对象职能类似于HMaster,而HTable的职能类似于HRegionServer。
通常这两个类的对象在一个应用程序中只创建一次,并且它们是由Connection对象获取的,Connection对象需要通过配置文件加载。所以以上对象一般为静态的,并且获取创建的过程在静态块中执行。
需要将core-site.xml、hdfs-site.xml、hbase-site.xml和log4j.properties放在src下
private static Configuration conf;
private static Connection conn;
private static Admin admin;
private static Table table
static{
//HBaseConfiguration是Configuration的子类,所以也可以获取到hdfs有关配置文件
conf = HBaseConfiguration.create();
try {
//通过ConnectionFactory建立与HBase的连接
conn = ConnectionFactory.createConnection(conf);
//从连接中获取Admin对象,而Table对象一般需要通过表名来获取
admin =conn.getAdmin();
} catch (IOException e) {
e.printStackTrace();
}
}
Table类:CRUD
以下是针对Table实例的操作
客户端对HBase的CRUD说到底就是针对单元格的操作,也就是发送不同类型的KeyValue(较新的api中使用的是Cell类,即单元格,不过内部构造是类似的),KeyValue的结构如下:
<row-key>/<family>:<qualifiter>/<version>/<type>/<value-length>
type字段指定了操作的类型,整体指定了具体的单元格和值
增对应Put类对象;查对应Get类对象;删对应Delete类对象;API中没有改操作,因为Put可以直接覆盖原值;这些对象内部不过是构造了不同的KeyValue
put
Put类的构造函数
//需要指定行键,为特定行添加数据
Put(byte[] row)
Put(byte[], long ts)
向Put对象指定的那一行添加一列的数据
//每次add都会添加特定的列,若再添加时间戳即形成了一个单元格
Put addColumn(byte [] family, byte [] qualifier, byte [] value)
Put addColumn(byte [] family, byte [] qualifier, long ts, byte [] value)
Put add(Cell kv)
若在addColumn时不指定时间戳,则会使用来自构造函数的时间戳;若构造时也没有声明,则时间戳将由HRegionServer来设定
单行put
最后由Table类对象调用put()完成单行put
void put(Put put) throws IOException
示例:
/**写入一行数据*/
public static void put_single_row(String tName, String rk,String cf, String qua, String val){
Put put = new Put(Bytes.toBytes(rk));
put.addColumn(Bytes.toBytes(cf),Bytes.toBytes(qua),Bytes.toBytes(val));
try {
//通过连接获取Table类对象需要传入TableName对象,即表名
TableName tbn = TableName.valueOf(tName);
table = conn.getTable(tbn);
table.put(put);
} catch (IOException e) {
e.printStackTrace();
}
}
客户端的写缓冲区
每一次put操作实际上就是一次rpc操作,这样的工作原理只适合小数据量的操作。减少rpc调用的关键是限制往返时间(LAN中1次rpc大约花1ms)和控制消息大小。所以API配备了一个客户端的写缓冲区,缓冲区负责收集put操作,然后调用rpc操作一次性将put发送至服务器。
默认情况下客户端缓冲区是禁用的,可以通过将自动刷写设置为false来激活缓冲区
table.setAutoFlush(false)
当需要强制将数据刷写到服务器上时调用
void flushCommits() throws IOException
//table.flushCommits()
被缓冲的Put实例可能跨越多行,客户端内部会批量处理这些Put并把它们传送到对应的regionserver,这是客户端对于Regionserver的分拣,而在Regionserver上的分拣则是决定数据去往哪个region。
通常强制刷写是不必要的,一旦超出了缓冲区指定的大小限制,客户端会隐式地调用刷写方法,可以通过以下命令设置缓冲区大小
setWriteBufferSize(long writeBufferSize) throws IOEception
//table.setWriteBufferSize(2097152)
默认缓冲区大小为2MB(2097152字节),若需要存储较大的数据,可以考虑增大此值,还可以在hbase-site.xml中设置此值
<property>
<name>hbase.client.write.buffer</name>
<value>2097152</value>
</property>
估算服务端内存的占用
hbase.client.write.buffer * hbase.regionserver.handler.count * regionserver的数量
多行Put
table同时还允许插入Put列表
void put(List<Put> puts) throws IOException
有错误的Put时:当Put列表中有部分失败,例如部分Put指定了不存在的列族,由于客户端并不知道HBase中表的结构,所以对列族的检查会在服务器端完成。正常Put的数据会添加成功,并且客户端不会抛出异常,失败的Put实例会被保存在本地写缓冲区内,下一次刷写时会重试。客户端会检查Put实例内容是否为空或是否制定了列,在这种情况下客户端会抛出异常。
Put实例执行顺序:当提交了Put列表时,用户无法控制服务器端执行Put实例的顺序。若要尽量保证写入的顺序,可以减少每一批Put的数量并显式地刷写。
原子性Put
检查写(check and put)保证了put操作的原子性
boolean checkAndPut(byte[] row, byte[] family, byte[] qualifier,
byte[] value, Put put) throws IOException
调用table的checkAndPut会对指定单元格数据与参数value对比,若相同则视为检查通过,并执行指定Put。
使用场景为:当读取数据的同时还需要处理数据,想把一个处理好的数据写回到HBase并确保没有其他客户端做了相同的事。
get
Get类构造函数
//指定行键
Get(byte[] row)
为指定行添加列信息
//指定列
Get addColumn(byte [] family, byte [] qualifier)
//指定列族
Get addFamily(byte [] family)
//指定时间戳及更旧的
Get setTimeStamp(long timestamp)
//Get请求默认返回版本数为1,不带参数的setMaxVersions设置最大版本号位int最大值
Get Get setMaxVersions()
Get setMaxVersions(int maxVersions) throws IOException
单行Get
然后由Table对象提交Get,返回Result实例
Result get(Get get) throws IOException
检查数据是否存在
boolean exist(Get get) throws IOException
exist()方法会引发regionserver服务器端的查询数据操作,通过这种方式而不是get()后检查可以避免网络数据传输的开销
查找一个特定行或某个请求之前的一行
Result getRowOrBefore(byte[] row, byte[] family) throws IOException
此方法中的行键要么与指定的一致,要么是指定行键之前的一行。
通常可以使用此方法在不知道行数的情况下获取表中最后一行的数据,调用getRowOrBefore()时传入像row-999999999这样的row参数,来获取表中最后一行row-n
Result类
Get实例指定了一行的数据,通过get()返回的是匹配Get实例的Result类对象
Result类提供的获取一行中信息的方法如下
//获取指定单元格的值
byte[] getValue(byte [] family, byte [] qualifier)
//获取第一列数据的最新版本
byte [] value()
//获取行键
byte [] getRow()
//获取单元格数量
int size()
//获取单元格列表
List<Cell> listCells()
示例
//获取一行数据中的所有单元格并打印基本信息
public static void get_row_cells(String tName, String rk){
Get get = new Get(Bytes.toBytes(rk));
try {
TableName tbn = TableName.valueOf(tName);
table = conn.getTable(tbn);
Result res = table.get(get);
List<Cell> cl = res.listCells();
for(Cell c : cl){
//调用Cell工具类的cloneXXX方法获取信息,注意Cell.getXXX()已过时
String row = Bytes.toString(CellUtil.cloneRow(c));
String cf = Bytes.toString(CellUtil.cloneFamily(c));
String qua = Bytes.toString(CellUtil.cloneQualifier(c));
String val = Bytes.toString(CellUtil.cloneValue(c));
System.out.println("row:"+row+",cf:"+cf+",qua:"+qua+",val:"+val);
}
} catch (IOException e) {
e.printStackTrace();
}
}
多行Get
同样get()方法可以传入一个Get列表
Result[] get(List<Get> gets) throws IOException
注意此时get()方法要么返回一个与发送Get列表大小一致的Result数组,要么抛出异常
有错误的Get时:与put列表不同,当有一个Get错误时会导致整个get()操作终止。关于批量操作中的局部错误,可以使用batch()方法更精细地处理
delete
Delete类构造函数
Delete(byte [] row)
Delete(byte [] row, long timestamp)
为指定行添加列信息
//不指定时间戳会删除整个列族;指定会删除改时间戳及更旧版本
Delete addFamily(final byte [] family)
Delete addFamily(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)
Delete setTimestamp(long timestamp)
另外,不指定时间戳,服务器会强制检索服务器端最近的时间错,这比指定时间戳的删除要慢;若尝试删除未设置时间戳的单元格,什么都不会发生
单行delete
然后调用Table实例的delete方法
void delete(Delete delete) throws IOException
多行delete
void delete(List<Delete> deletes) throws IOException
Delete实例的执行顺序:与put列表类似,用户不能设置删除操作在服务器端的执行顺序。API会重新排列它们,并将同一regionserver的操作集中到一个rpc中以提升性能
有错误的Delete时:与put类似,不能终止整个delete(),列信息正确的则被成功删除
原子性Delete
同样用户可以在服务器端进行读取并修改
boolean checkAndDelete(byte[] row, byte[] family, byte[] qualifier,
byte[] value, Delete delete) throws IOException
当指定单元格的值与参数value相同时,才会执行指定的Delete
批量处理操作
其实上面的delete(List<Delete> deletes)和put(List<Put> puts)都是基于批量处理batch()方法实现的
batch(final List<? extends Row> actions, final Object[] results)
Row类是Put、Get、Delete的父类。在actions的列表中可以存放3中同类型的子类,将不同的操作融为一个rpc调用
actions和results大小相同,一个操作返回一个result
关于results的返回结果:
null:该操作与服务器通信失败
EmptyResult:Put和Delete操作成功
Result:Get操作成功,若无匹配的行或列,则返回空的Result
Throwable:服务器端返回一个异常,会原样返回给客户端
注意:不可以针对同一行的Put和Delete操作放在同一个batch()请求中
关于batch()同步问题:调用batch()时Put实例不会写入客户端的写缓冲区中,batch()请求是同步的
扫描
扫描与get()方法十分类似,可以看成是多行的get()结果(通过指定扫描器Scan,即指定扫描规则)到客户端的一个结果扫描器(ResultScanner)中
Scan
规则扫描器Scan类的构造器
//扫描全表
Scan()
//指定开始扫描的行键,至表未
Scan(byte[] startRow)
//指定扫描开始和结束行键,包前不包后
Scan(byte[] startRow, byte[] stopRow)
注意:扫描会匹配比给定范围更大的范围(startRow更小,stopRow更大),用户提供的开始和结束行键不必精确匹配这两行
为Scan类指定列
Scan addColumn(byte [] family, byte [] qualifier)
Scan addFamily(byte [] family)
还可以指定过滤器
//设置过滤器
Scan setFilter(Filter filter)
因为HBase中数据(HFile)是按照列族存储的,如果扫描不读取某个列族,那么整个列族文件就不会比读取,这便是列式存储的优势
ResultScanner
扫描操作不会通过一次rpc返回所有匹配的行(可能需要扫描整个表),而是以行为单位返回。并将每一行数据封装成了Result实例,这类似于get(List<Get> gets)方法(返回Result数组),不过是将多行的Result实例放入一个迭代器ResultScanner中
通过Table类实例调用getScanner()获取结果扫描器
ResultScanner getScanner(Scan scan) throws IOException
//以下方法隐式地创建了Scan实例
ResultScanner getScanner(byte[] family) throws IOException
ResultScanner getScanner(byte[] family, byte[] qualifier) throws IOException
ResultScanner提供的方法
Result next() throws IOException
//返回nbRows行的Result
Result [] next(int nbRows) throws IOException
void close()
也可以通过迭代器的方式(foreach)去遍历
缓存和批量处理
在旧版本中默认情况下ResultScanner每一次调用next()都会为每行数据生成一个单独的rpc调用,next(int nbRows)中也不过是循环调用next()方法。API提供了扫描器缓存和批量处理来避免生成过多的rpc,总的来说扫描器缓存是面向行的,批量处理是面向列的
扫描器缓存
可以在两个层面设置扫描器缓存打开它:在表层面设置会所该表的所有Scan实例生效,也可以在扫描层面设置,只会影响当前的Scan实例。且扫描器层面的优先级更高
//Table层面,参数为一次rpc扫描的行数
void setScannerCaching(int scannerCaching)
//Scan层面
void setCaching(int caching)
不过在较新的版本中,扫描器缓存默认是打开的,且caching为2^31-1
<property>
<name>hbase.client.scanner.caching</name>
<value>2147483647</value>
</property>
批量处理
当一行的数据量很大时,就需要分列批量处理。批量可以让用户选择每一个ResultScanner实例的next()操作要取回多少列
在扫描器层面设置
void setBatch(int batch)
因为每一行中的列指的是单元格,可以把批量看成是单元格层面的处理;若设置的batch大于总列数,则每次取回所有列。另外每次扫描完成后会发送1个rpc确认完成
比如有一个10行20例的表,即一共200列(单元格):
caching batch Result个数 rpc次数
1 1 200 201
200 1 200 2
2 10 20 11
5 100 10 3
5 20 10 3
10 10 20 3
可以发现:
rpc次数=(总列数(单元格数) / caching*min(每行列数,batch)) + 1
Result个数 = 总列数(单元格数) / min(每行列数,batch)
Admin类:管理类操作
以下是针对Admin实例的操作
HBaseAdmin
HBaseAdmin类是Admin的子类,通常通过Admin实例即可完成对Hbase管理类的操作
在API中,用描述器来描述命名空间、表、列族,分别为NamespaceDescriptor、HTableDescripter和HColumnDescripter
Admin提供一切与管理类有关操作的方法
比如命名空间和表的创建修改删除等等
//获得所有命名空间
NamespaceDescriptor[] listNamespaceDescriptors()
//指定命名空间名获取ns
NamespaceDescriptor getNamespaceDescriptor(final String name)
//删除命名空间
deleteNamespace(final String name)
//列出指定命名空间下的所有表
TableName[] listTableNamesByNamespace(final String name)
//列出所有表
HTableDescriptor[] listTables() throws IOException
//建表
void createTable(HTableDescriptor desc) throws IOException
//等等太多
提供了许多方法,而且用起来都比较简单,可以通过方法名和参数名领会,还是看几个示例吧
/***创建命令空间*/
public static void createNS(String namespace){
Admin admin = null;
try {
admin = conn.getAdmin();
NamespaceDescriptor nsdesc = NamespaceDescriptor.create(namespace).build();
admin.createNamespace(nsdesc);
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
admin.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**删除名字空间*/
public static void deleteNS(String namespace){
Admin admin = null;
try {
admin = conn.getAdmin();
admin.deleteNamespace(namespace);
} catch (IOException e) {
e.printStackTrace();
} finally{
try {
admin.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**创建Table*/
public static void createTable(String tablename,String colunmfamily){
Admin admin = null;
try {
admin = conn.getAdmin();
TableName tname = TableName.valueOf(tablename);
HTableDescriptor htdesc = new HTableDescriptor(tname);
HColumnDescriptor hcdesc = new HColumnDescriptor(colunmfamily);
htdesc.addFamily(hcdesc);
admin.createTable(htdesc);
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
admin.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**查看Table*/
public static void getTable() {
try {
HTableDescriptor[] listtable = admin.listTables();
for(HTableDescriptor tables :listtable) {
System.out.println(tables);
}
} catch (IOException e) {
e.printStackTrace();
}
}
Region有关方法
合并Region
//合并两个Region
void mergeRegions(final byte[] nameOfRegionA, final byte[] nameOfRegionB,
final boolean forcible) throws IOException
最后一个参数指定是否强制合并,默认为false,默认会合并两个相邻的Region,若设置为True可合并两个不相邻的Region
关于Region名可以在web端查看,最后两个句号之间的数据即为Region名
移动Region
void move(final byte[] encodedRegionName, final byte[] destServerName)
throws IOException
第二个参数指定了目的地服务器名,servername可通过web端查看
切分Region
void splitRegion(final byte[] regionName) throws IOException
//设置切分点行键
void split(final TableName tableName, final byte[] splitPoint)
throws IOException
同样还可以切表
void split(final TableName tableName) throws IOException
void split(final TableName tableName, final byte[] splitPoint)
throws IOException
负载均衡
//传入true开启负载均衡
boolean balancer(boolean force) throws IOException
//进行负载均衡
boolean balancer() throws IOException
表和Region的合并(compact)
这里的合并指的是minor compact和major compact,是底层HFile层面的合并
void compact(final TableName tableName) throws IOException
void compact(final TableName tableName, final byte[] columnFamily)
void compactRegion(final byte[] regionName) throws IOException
void compactRegion(final byte[] regionName, final byte[] columnFamily) throws IOException
void majorCompact(TableName tableName) throws IOException
void majorCompactRegion(final byte[] regionName) throws IOException
//...
...
来源:CSDN
作者:Zsigner
链接:https://blog.csdn.net/Zsigner/article/details/104252141