本文是對如何使用Hbase提供的Java Api編寫Hbase客戶端程序的一個學習總結,共包括如下章節(jié)的內容:
- 概述
- 更新和查詢數(shù)據(jù)
- 創(chuàng)建和更新表結構
- 編譯和運行
- 遠程訪問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一個非常直觀的區(qū)別是,帶H開頭的是舊的API,如Htable,HBaseAdmin,HTableDescriptor,HColumnDescriptor等。
java客戶端是通過zookeeper找到hbase服務的,因此,需要在代碼中配置zookeeper的信息。
二、更新和查詢數(shù)據(jù)
我們通過具體例子來說明,假設hbase中已經(jīng)有一張表test,表中有一個列族data。關鍵的API是Table接口。
(一)插入數(shù)據(jù)
本例子代碼是往表中插入數(shù)據(jù),完整的代碼如下:
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個參數(shù)是列族,第2個參數(shù)是列名,第3個參數(shù)是插入的值
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對象,再創(chuàng)建一個Put對象保存要插入的數(shù)據(jù),最后利用Table對象提交數(shù)據(jù)。
下面例子代碼都是在這個代碼基礎上運行。
(二)按鍵值查詢數(shù)據(jù)
如果我們要查詢指定的數(shù)據(jù),可以利用Get對象來完成,代碼如下(這里只給出了片段代碼,只需把這代碼加入到上面插入數(shù)據(jù)的完整例子代碼中就可以使用):
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));
}
上面代碼根據(jù)表的鍵值創(chuàng)建Get對象,然后利用Table對象獲取到結果集,最后通過結果集獲取各單元值。
(三)掃描表
在某項場景下,我們不知道鍵值的時候,可以利用Scan對象掃描整個表,遍歷所需的數(shù)據(jù)。例子如:
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對象增加條件,只獲取指定的列族或指定列的數(shù)據(jù)。
如果指定獲取列族的數(shù)據(jù),可以如下面代碼:
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方法限定只獲取指定列族的數(shù)據(jù),可以調用多次,增加多個列族。
我們也可以指定到具體的列,如下面代碼:
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);
}
}
(五)刪除表中數(shù)據(jù)
我們可以按照鍵值來刪除數(shù)據(jù),如下面代碼。
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);
}
三、創(chuàng)建和更新表結構
我們可以編寫java代碼創(chuàng)建表、刪除表、修改表的結構,類似關系數(shù)據(jù)庫的DDL語句。關鍵的Api是Admin接口。
(一)查看所有的表
類似hbase命令行中l(wèi)ist命令,顯示當前用戶下所有的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對象的方法。下面的例子都是在上面代碼基礎上運行。
(二)創(chuàng)建/刪除表
下面代碼演示了檢查一個表是否存在,如果存在,則先使之無效,再刪除,然后創(chuàng)建一個新表。代碼如下(不是完整代碼,可放到上面完整例子代碼中運行):
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());
}
創(chuàng)建列族時,默認列族支持的最多存儲的版本數(shù)是1,上面例子代碼將列族cf1支持的最多存儲版本數(shù)改為3。
四、編譯和運行
上面的例子只是給出了程序的源代碼,那怎么編譯和執(zhí)行呢?因為代碼中引入了很多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來執(zhí)行,這樣就不需要關心要引入哪些jar包了。
執(zhí)行hbase腳本前,需要先將打成的jar包加入到HBASE_CLASSPATH環(huán)境變量中。然后運行hbase,后面跟著可執(zhí)行類名。如在linux下,在控制臺下執(zhí)行如下兩條語句即可:
export HBASE_CLASSPATH= 上面打成的jar包
hbase com.hbaseexample.Example
如在windows下,在dos命令行執(zhí)行,需要將export命令改成set命令。
五、遠程訪問hbase
如果我們希望編寫的hbase客戶端程序(位于windows系統(tǒng)下)能操作安裝在遠程的Linux機器上的hbase服務。這時需要做一些額外的配置。
當然前提是hbase客戶端程序將運行的機器上已經(jīng)有hbase客戶端可運行的環(huán)境。
需要做如下配置:
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");
經(jīng)過上面的設置后,java程序在windows下運行,就可以連接運行在遠程linux上的hbase服務了。
下面簡單介紹下linux下查看和修改主機名的方法,我們這里用的是版本是Centos7的linux操作系統(tǒng)。方法如下:
1、查看主機名的命令 hostname,默認情況下,Linxu的主機名叫l(wèi)ocalhost.localdomain
2、修改主機名,可以臨時修改(機器重啟將無效),或永久生效。
臨時修改只需執(zhí)行如下命令:
sudo hostname 設置的主機名
如果要永久生效,就需要修改Linux下的配置文件** /etc/hostname**,將其中的內容改成要設置的主機名,如master。根據(jù)需要,我們還可以修改 /etc/hosts文件,添加IP地址與主機名的對應關系。