本文是对如何使用Hbase提供的Java Api编写Hbase客户端程序的一个学习总结,共包括如下章节的内容:
- 概述
- 更新和查询数据
- 创建和更新表结构
- 编译和运行
- 远程访问Hbase
参考资料:
1、如果需要了解Hbase的基本知识,可参见《HBASE学习笔记》一文。
一、概述
Hbase提供了一套Java API,可以让编写java程序来访问Hbase。从HBase 1.0开始,Hbase提供了新的Api替代以前的老的Api,新的Api更加干净和直观。
新的Api使用接口,而不是特定的类来操作API。新的Api,使用ConnectionFactory的工厂方法来获取一个Connection对象,然后调用getAdmin()和getTable()方法来获取Admin和Table实例,接着使用实例完成具体操作,最后关闭实例和连接。
本文用的hbase版本是hbase2.0.2,将使用新的API进行介绍。新旧API一个非常直观的区别是,带H开头的是旧的API,如Htable,HBaseAdmin,HTableDescriptor,HColumnDescriptor等。
java客户端是通过zookeeper找到hbase服务的,因此,需要在代码中配置zookeeper的信息。
二、更新和查询数据
我们通过具体例子来说明,假设hbase中已经有一张表test,表中有一个列族data。关键的API是Table接口。
(一)插入数据
本例子代码是往表中插入数据,完整的代码如下:
package com.hbaseexample;
import java.io.IOException;
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;
public class Example {
private Connection connection = null;
private Table table = null;
public static void main(String[] args) {
Example example = new Example();
try {
example.conn();
example.addData();
System.out.println("finish");
} catch (IOException e) {e.printStackTrace();
} finally {example.close();}
}
public void conn() throws IOException {
Configuration config = HBaseConfiguration.create();
config.set("hbase.zookeeper.quorum", "127.0.0.1");//zk地址
config.set("hbase.zookeeper.property.clientPort", "2181");//zk端口
connection = ConnectionFactory.createConnection(config);
table = connection.getTable(TableName.valueOf("test"));
}
public void close() {
if (table != null)
try {table.close();} catch (IOException e) {}
if (connection != null)
try {connection.close(); } catch (IOException e) {}
}
public void addData() throws IOException {
Put p = new Put(Bytes.toBytes("rk_100001")); //行键
//addColumn第1个参数是列族,第2个参数是列名,第3个参数是插入的值
p.addColumn(Bytes.toBytes("data"), Bytes.toBytes("uname"), Bytes.toBytes("tom"));
p.addColumn(Bytes.toBytes("data"), Bytes.toBytes("age"), Bytes.toBytes("25"));
table.put(p);
}
}
上面代码是一个完整的带main方法的java类,首先获得一个Connection对象,然后获取到Table对象,再创建一个Put对象保存要插入的数据,最后利用Table对象提交数据。
下面例子代码都是在这个代码基础上运行。
(二)按键值查询数据
如果我们要查询指定的数据,可以利用Get对象来完成,代码如下(这里只给出了片段代码,只需把这代码加入到上面插入数据的完整例子代码中就可以使用):
public void getData() throws IOException{
Get g = new Get(Bytes.toBytes("rk_100001"));
Result r = table.get(g);
byte[] value = r.getValue(Bytes.toBytes("data"), Bytes.toBytes("uname"));
String valueStr = Bytes.toString(value);
System.out.println("uname:"+valueStr);
value = r.getValue(Bytes.toBytes("data"), Bytes.toBytes("age"));
System.out.println("age:"+Bytes.toString(value));
}
上面代码根据表的键值创建Get对象,然后利用Table对象获取到结果集,最后通过结果集获取各单元值。
(三)扫描表
在某项场景下,我们不知道键值的时候,可以利用Scan对象扫描整个表,遍历所需的数据。例子如:
public void scanData() throws IOException{
Scan s = new Scan();
ResultScanner scanner = table.getScanner(s);
try {
for (Result rr : scanner) {
byte[] row = rr.getRow();
byte[] uname = rr.getValue(Bytes.toBytes("data"), Bytes.toBytes("uname"));
byte[] age = rr.getValue(Bytes.toBytes("data"), Bytes.toBytes("age"));
System.out.println("Found:" + Bytes.toString(row)+","+Bytes.toString(uname)+","+Bytes.toString(age));
}
} finally {
scanner.close();
}
}
上面代码对整个表进行扫描。我们也可以给Scan对象增加条件,只获取指定的列族或指定列的数据。
如果指定获取列族的数据,可以如下面代码:
public void scanData() throws IOException{
Scan s = new Scan();
s.addFamily(Bytes.toBytes("data"));
ResultScanner scanner = table.getScanner(s);
try {
for (Result rr : scanner) {
byte[] row = rr.getRow();
byte[] uname = rr.getValue(Bytes.toBytes("data"), Bytes.toBytes("uname"));
byte[] age = rr.getValue(Bytes.toBytes("data"), Bytes.toBytes("age"));
System.out.println("Found:" + Bytes.toString(row)+","+Bytes.toString(uname)+","+Bytes.toString(age));
}
} finally {
scanner.close();
}
}
上面代码,调用Scan对象的addFamily方法限定只获取指定列族的数据,可以调用多次,增加多个列族。
我们也可以指定到具体的列,如下面代码:
public void scanData() throws IOException{
Scan s = new Scan();
s.addColumn(Bytes.toBytes("data"), Bytes.toBytes("uname"));
s.addColumn(Bytes.toBytes("data"), Bytes.toBytes("age"));
ResultScanner scanner = table.getScanner(s);
try {
for (Result rr : scanner) {
byte[] row = rr.getRow();
byte[] uname = rr.getValue(Bytes.toBytes("data"), Bytes.toBytes("uname"));
byte[] age = rr.getValue(Bytes.toBytes("data"), Bytes.toBytes("age"));
System.out.println("Found:" + Bytes.toString(row)+","+Bytes.toString(uname)+","+Bytes.toString(age));
}
} finally {
scanner.close();
}
}
(四)获取表的列族信息
有时我们可能需要通过代码获取hbase表定义的列族信息,例子代码如下:
public void getCfInfo() throws IOException{
ColumnFamilyDescriptor[] columnFamilyDescriptors = table.getDescriptor().getColumnFamilies();
for (ColumnFamilyDescriptor columnFamilyDescriptor : columnFamilyDescriptors) {
String familyName = columnFamilyDescriptor.getNameAsString();
System.out.println(familyName);
}
}
(五)删除表中数据
我们可以按照键值来删除数据,如下面代码。
public void deleteData() throws IOException{
String[] rowKeys={"row1","row3"};
List<Delete> deleteList = new ArrayList<>(rowKeys.length);
Delete delete;
for (String rowKey : rowKeys) {
delete = new Delete(Bytes.toBytes(rowKey));
deleteList.add(delete);
}
table.delete(deleteList);
}
三、创建和更新表结构
我们可以编写java代码创建表、删除表、修改表的结构,类似关系数据库的DDL语句。关键的Api是Admin接口。
(一)查看所有的表
类似hbase命令行中list命令,显示当前用户下所有的hbase表,例子代码如下。
package com.hbaseexample;
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Admin;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
public class AdminExample {
private Connection connection = null;
private Admin admin = null;
public static void main(String[] args) {
AdminExample example = new AdminExample();
try {
example.conn();
example.listTable();
System.out.println("finish");
} catch (IOException e) {
e.printStackTrace();
} finally {
example.close();
}
}
public void conn() throws IOException {
Configuration config = HBaseConfiguration.create();
config.set("hbase.zookeeper.quorum", "127.0.0.1");//zk地址
config.set("hbase.zookeeper.property.clientPort", "2181");//zk端口
connection = ConnectionFactory.createConnection(config);
admin = connection.getAdmin();
}
public void close() {
if (admin != null)
try {admin.close(); } catch (IOException e) {}
if (connection != null)
try {connection.close();} catch (IOException e) {}
}
public void listTable() throws IOException{
TableName[] names = admin.listTableNames();
for(TableName name:names){
System.out.println(name.getNameAsString());
}
}
}
上面代码是一个完整的带main方法的java类,首先获得一个Connection对象,然后获取到Admin对象,然后调用Admin对象的方法。下面的例子都是在上面代码基础上运行。
(二)创建/删除表
下面代码演示了检查一个表是否存在,如果存在,则先使之无效,再删除,然后创建一个新表。代码如下(不是完整代码,可放到上面完整例子代码中运行):
public void doDDL() throws IOException {
TableName tableName = TableName.valueOf("test");
boolean re = admin.tableExists(tableName );
if(re){
admin.disableTable(tableName);
admin.deleteTable(tableName);
}
TableDescriptorBuilder tableDesc = TableDescriptorBuilder.newBuilder(tableName);
tableDesc.setColumnFamily(ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("cf1")).build());
tableDesc.setColumnFamily(ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("cf2")).build());
admin.createTable(tableDesc.build());
}
(三)删除表的列族
例子代码如下
public void deleteCF() throws IOException{
TableName tableName = TableName.valueOf("test");
admin.deleteColumnFamily(tableName, Bytes.toBytes("cf2"));
}
(四)添加表的列族
例子代码如下
public void addCF() throws IOException{
TableName tableName = TableName.valueOf("test");
ColumnFamilyDescriptor cf =ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("cf2")).build();
admin.addColumnFamily(tableName, cf );
cf =ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("cf3")).build();
admin.addColumnFamily(tableName, cf );
}
(五)修改已存在的列族
例子代码如下
public void modifyCF() throws IOException{
TableName tableName = TableName.valueOf("test");
ColumnFamilyDescriptorBuilder builder = ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("cf1"));
builder.setMaxVersions(3);
admin.modifyColumnFamily(tableName, builder.build());
}
创建列族时,默认列族支持的最多存储的版本数是1,上面例子代码将列族cf1支持的最多存储版本数改为3。
四、编译和运行
上面的例子只是给出了程序的源代码,那怎么编译和执行呢?因为代码中引入了很多Hbase JAVA API的接口和类,需要引入相应的jar包。编写的java hbase客户端程序所依赖的jar包都位于hbase安装目录的lib目录下。
想要成功编译上面代码,只需引入如下三个jar包即可(均在lib目录下可找到):
hadoop-common-2.7.7.jar
hbase-common-2.0.2.jar
hbase-client-2.0.2.jar
但要想运行程序,需要依赖更多的jar包。这有两种方式:
1、将lib根目录下所有的jar包加入到classpath路径中(如果在ide中运行,则全部引入),这样可以在IDE(如eclipse)中直接运行。
2、把程序编译后的class打成jar包。然后利用hbase提供的命令行客户端脚本程序hbase来执行,这样就不需要关心要引入哪些jar包了。
执行hbase脚本前,需要先将打成的jar包加入到HBASE_CLASSPATH环境变量中。然后运行hbase,后面跟着可执行类名。如在linux下,在控制台下执行如下两条语句即可:
export HBASE_CLASSPATH= 上面打成的jar包
hbase com.hbaseexample.Example
如在windows下,在dos命令行执行,需要将export命令改成set命令。
五、远程访问hbase
如果我们希望编写的hbase客户端程序(位于windows系统下)能操作安装在远程的Linux机器上的hbase服务。这时需要做一些额外的配置。
当然前提是hbase客户端程序将运行的机器上已经有hbase客户端可运行的环境。
需要做如下配置:
1、配置远程hbase服务器支持远程连接。这时需要在hbase-site.xml中添加一个属性配置,属性名 hbase.zookeeper.quorum ,属性值为远程服务器主机的主机名(注意,一定不是IP地址,而是主机名)。如下面配置例子:
<property>
<name>hbase.zookeeper.quorum</name>
<value>master</value>
</property>
上面的master是远程服务器的主机名,不能配置成IP地址。
2、修改hbase客户端程所在的windows机器上的配置文件:C:\Windows\System32\drivers\etc\hosts 在其中添加远程Linux机器IP地址和主机名的对应关系。如:
192.168.2.6 master
上面的192.168.2.6是远程Linux主机的IP地址,master是远程Linux主机的主机名,要与hbase-site.xml中的配置一致。
3、修改java代码
将上面例子中的代码中的如下语句
config.set("hbase.zookeeper.quorum", "127.0.0.1");
改成如下的语句
config.set("hbase.zookeeper.quorum", "192.168.2.6");
上面的192.168.2.6是Linux服务器的IP地址,也可以用主机名master来替换IP地址,如:
config.set("hbase.zookeeper.quorum", "master");
经过上面的设置后,java程序在windows下运行,就可以连接运行在远程linux上的hbase服务了。
下面简单介绍下linux下查看和修改主机名的方法,我们这里用的是版本是Centos7的linux操作系统。方法如下:
1、查看主机名的命令 hostname,默认情况下,Linxu的主机名叫localhost.localdomain
2、修改主机名,可以临时修改(机器重启将无效),或永久生效。
临时修改只需执行如下命令:
sudo hostname 设置的主机名
如果要永久生效,就需要修改Linux下的配置文件** /etc/hostname**,将其中的内容改成要设置的主机名,如master。根据需要,我们还可以修改 /etc/hosts文件,添加IP地址与主机名的对应关系。
网友评论