一、HBase簡介
HBase官方網站:http://hbase.apache.org/
HBase是一個分佈式的、面向列的開源數據庫,該技術來源於 Fay Chang 所撰寫的Google論文《Bigtable:一個結構化數據的分佈式存儲系統》。就像Bigtable利用了Google文件系統(File System)所提供的分佈式數據存儲一樣,HBase在Hadoop之上提供了類似於Bigtable的能力。HBase是Apache的Hadoop項目的子項目。HBase不同於一般的關係數據庫,它是一個適合於非結構化數據存儲的數據庫。另一個不同的是HBase基於列的而不是基於行的模式。
HBase以表的形式存儲數據,表有行和列組成,列劃分爲多個列族/列簇(column family)。
HBase的運行有三種模式:單機模式、僞分佈式模式、分佈式模式。
- 單機模式
在一臺計算機上安裝和使用HBase,不涉及數據的分佈式存儲。
- 僞分佈式模式
在一臺計算機上模擬一個小的集羣。
- 分佈式模式
使用多臺計算機實現物理意義上的分佈式存儲。
二、安裝教程
本文示例的運行環境爲CentOS7。HBase版本爲1.1.2,Hadoop版本爲2.7.7,JDK1.8。
在安裝HBase之前,需要安裝Hadoop。可根據《分佈式處理框架Hadoop的安裝與使用》進行安裝。由於HBase對Hadoop版本具有依賴性,所以安裝其他版本前需要查看兩者版本之間是否匹配。
1、下載HBase
下載地址:http://archive.apache.org/dist/hbase/1.3.2/
下載*-bin.tar.gz文件
下載後將壓縮包放到/home/hadoop/download路徑下。我這裏download文件夾是爲了存放下載的文件,下載文件夾不限制位置。“~”路徑代表當前用戶文件夾,此處爲hadoop用戶,所以對應的路徑就是“/home/hadoop”。
2、安裝HBase
解壓到/usr/local/路徑下:
$ tar -zxf /home/hadoop/download/hbase-1.3.2-bin.tar.gz -C /usr/local
切換到root用戶,重命名hbase-1.3.2文件夾,將解壓後的文件賦權給hadoop用戶。
$ cd /usr/local
$ mv ./hbase-1.3.2 ./hbase
$ chown -R hadoop:hadoop hbase/
配置環境變量
切回到hadoop用戶:
$ su hadoop
編輯環境變量文件:
$ vi ~/.bashrc
添加Hbase環境變量。其中“:”,冒號是起分隔符的作用。
編輯完成後,使用source命令,讓配置文件在當前終端立即生效。
$ source ~/.bashrc
查看HBase版本,確定是否安裝成功。
$ hbase version
三、配置HBase
HBase有三種運行模式,單機模式、僞分佈式模式、分佈式模式,我們這裏案例使用單機模式與僞分佈模式。
在配置之前,必須要滿足如下條件:
- jdk
- Hadoop( 單機模式不需要,僞分佈式模式和分佈式模式需要)
- SSH
如果以上三者均爲安裝,請根據文章《分佈式處理框架Hadoop的安裝與使用》進行安裝。
1.單機模式配置
修改/usr/local/hbase/conf/hbase-env.sh配置
增加如下內容:
export JAVA_HOME=/usr/lib/jvm/jdk1.8.0_51
export HBASE_MANAGES_ZK=true
- JAVA_HOME
jdk安裝目錄。
- HBASE_MANAGES_ZK
true:表示由hbase自己管理zookeeper,不需要單獨的zookeeper。
1.1配置/usr/local/hbase/conf/hbase-site.xml
在啓動HBase前需要設置屬性hbase.rootdir,用於指定HBase數據的存儲位置,因爲如果不設置的話,hbase.rootdir默認爲/tmp/hbase-${user.name},這意味着每次重啓系統都會丟失數據。
<configuration>
<property>
<name>hbase.rootdir</name>
<value>file:///usr/local/hbase/hbase-tmp</value>
</property>
</configuration>
1.2測試運行
啓動HBase,並打開Shell命令模式,使用戶可以通過shell命令操作數據庫。由於前面配置了環境變量,所以可以直接運行,實際上這兩條命令都應該在/usr/local/hbase/bin目錄下運行。
$ start-hbase.sh
$ hbase shell
1.3停止HBase
$ stop-hbase.sh
2.僞分佈式配置,使用外部Zookeeper
2.1配置主機別名
$ vi /etc/hosts
爲本節點的ip設置一個別名:
2.2下載安裝zookeeper
官方下載地址:http://mirror.bit.edu.cn/apache/zookeeper/zookeeper-3.4.14/zookeeper-3.4.14.tar.gz
將安裝包放到/home/hadoop/download下
解壓文件到/usr/local/,會在此路徑下生成zookeeper-3.4.14文件夾。
$ tar -zxvf ./zookeeper-3.4.14.tar.gz -C /usr/local
將zookeeper文件夾所有權更改爲hadoop:
$ chown -R hadoop:hadoop zookeeper-3.4.14/
切換回hadoop用戶:
$ su hadoop
編輯zookeeper環境變量:
$ vi ~/.bashrc
使環境變量生效:
$ source ~/.bashrc
進入zookeeper配置文件路徑:
$ cd /usr/local/zookeeper-3.4.14/conf/
複製zoo-sample.cfg,並將新文件命名爲zoo.cfg
$ cp zoo_sample.cfg zoo.cfg
配置zoo.cfg
- dataDir
zookeeper文件存放位置,此文件夾需要hadoop用戶有權限使用,否則會報錯。
2.3配置/usr/local/hbase/conf/hbase-env.sh
配置下列項:
export JAVA_HOME=/usr/lib/jvm/jdk1.8.0_51
export HBASE_CLASSPATH=/usr/local/hbase/conf
export HBASE_MANAGES_ZK=false
- JAVA_HOME
jdk安裝路徑。
- HBASE_CLASSPATH
設置爲本機HBase安裝目錄下的conf目錄。
-
HBASE_MANAGES_ZK
此處設置爲false,表示不使用自帶zookeeper,使用外部zk。
2.4配置/usr/local/hbase/conf/hbase-site.xml
配置內容:
<configuration>
<property>
<name>hbase.rootdir</name>
<value>hdfs://master:9000/hbase</value>
</property>
<property>
<name>hbase.cluster.distributed</name>
<value>true</value>
</property>
<property>
<name>hbase.master.info.port</name>
<value>16010</value>
</property>
<property>
<name>hbase.master</name>
<value>master:16000</value>
</property>
<property>
<name>zookeeper.znode.parent</name>
<value>/hbase/master</value>
</property>
<property>
<name>hbase.zookeeper.quorum</name>
<value>master:2181</value>
</property>
</configuration>
- hbase.rootdir
指定HBase的存儲目錄。9000爲HDFS端口(NameNode端口),hbase文件夾必須在hdfs中存在,如果不存在,請使用
- hbase.cluster.distributed
設置集羣處於分佈式模式。
-
hbase.master.info.port
hmater管理界面端口,可通過ip+port在web界面查看hmaster狀態。
-
hbase.master
hmater的ip和端口信息。
-
hbase.zookeeper.quorum
zookeeper的ip和端口信息。
2.5修改regionservers
$ vi /usr/local/hbase/conf/regionservers
此處暫時只有一個節點,爲本機,此處master已在/etc/hosts文件中做了映射。
2.6測試運行
關閉防火牆
$ systemctl disable firewalld
關閉SElinux
編輯/etc/selinux/config,將SELINUX=enforcing 修改爲"SELINUX=disabled"
啓動hadoop:
$ start-dfs.sh
出現如下說明啓動Hadoop成功
啓動zookeeper
$ zkServer.sh start
啓動HBase:
$ start-hbase.sh
出現如下說明啓動HBase成功
關閉HBase:
$ stop-hbase.sh
如果一直關閉不了,可以先使用如下命令:
$ hbase-daemon.sh stop master
$ stop-hbase.sh
關閉zookeeper
$ zkServer.sh stop
關閉Hadoop:
$ stop-dfs.sh
啓動順序:Hadoop->Zookeeper->HBase
關閉順序:HBase->Zookeeper->Hadoop
錯誤日誌位置:HBase安裝目錄下的logs文件夾,本例日誌位於/usr/local/hbase/logs。
2.7解決錯誤
日誌目錄: /usr/local/hbase/logs/
mater節點日誌:hbase-hadoop-master-localhost.localdomain.log
zookeeper日誌:hbase-hadoop-zookeeper-localhost.localdomain.log
當啓動HBase報錯org.apache.hadoop.hbase.PleaseHoldException: Master is initializing:
進入zk客戶端:
$ zkCli.sh -server localhost:2181
查看所有文件夾:
刪除文件夾:
退出客戶端:
刪除hdfs中hbase文件夾下內容:
由於本文在寫時換了兩種不同環境的網絡,所以ip有所不同,172.20.10.6與192.168.0.121均爲我虛擬機的ip,在實踐時只需要統一即可。
$ hadoop fs -rm -r hdfs://172.20.10.6:9000/hbase/*
重啓HBase。
使用shell操作數據時:The node /hbase is not in ZooKeeper. It should have been written by the master. Check the value configured in 'zookeeper.znode.parent'. There could be a mismatch with the one configured in the master.
缺少配置,在/usr/local/hbase/conf/hbase-site.xml中添加下列配置:
<property>
<name>zookeeper.znode.parent</name>
<value>/hbase</value>
</property>
本人遇到的一個很坑的問題:
根據教學配置,完全按照步驟來,但是就是zookeeper連接不上,各種問題搜索了一大堆所謂的解決答案,同時也換了很多個HBase版本進行安裝,但是問題根本就沒有得以解決,反而浪費了兩天時間。
以下是我安裝過的HBase版本:
就在我心灰意冷的時候,突然想到會不會是hdfs的問題,然後改了hdfs的配置文件/usr/local/hadoop/etc/hadoop/core-site.xml:
將172.20.10.6(本機ip)改成了localhost
接着啓動Hadoop,啓動HBase,錯誤完美解決。問題就是這樣,有時候你越剛它越解決不出來,但是在之後的某一個時間,突然就意外解決了。
四、編程實踐
1、Shell命令
1.1HBase創建表
創建用戶user表,有name、password、age、address屬性。且還有HBase默認創建的行鍵。
create 'user','name','password','age','address'
describe命令查看錶信息:
1.2HBase增刪改查
在添加數據時,HBase會自動給數據添加一個時間戳,在需要改數據時,只需要新增一條數據就行,因爲有時間戳作爲版本區別,HBase會定時回收舊數據,只留下最新的幾個版本,單條的記錄留存的版本數量在創建的時候指定。
- 寫入數據
寫入行鍵爲1,名稱爲‘han’的記錄:
put 'user', '1', 'name', 'han'
- 刪除數據
delete:刪除數據,是put的反向操作。
deleteall:刪除一行數據。
刪除行鍵爲1的數據的name列:
我這裏是因爲之前給行鍵爲1的數據寫入了4個name值,分別爲‘han’,'han1','han2','han3',所以刪除時會逐級刪除。
刪除行鍵爲1的數據:
- 查看數據
get:查看錶中某一行數據。
scan:查表中所有數據。
查看user表的行鍵爲2的數據:
查看user表所有數據:
- 刪除表
先讓表不可用,再刪除。
disable 'user'
drop 'user'
- 查詢表歷史數據
指定歷史版本(5)來創建表:
create 'user',{NAME=>'name',VERSIONS=>5}
插入數據:
指定歷史版本數查詢的數據:
get 'user','1',{COLUMN=>'name',VERSIONS=>2}
退出hbase shell命令
exit
2、Java API
由於本文是在虛擬機上搭建hadoop、hbase環境,在本地物理機上寫代碼運行,所以需要在物理機上配變量:
編輯C:\Windows\System32\drivers\etc\hosts文件:
說明:192.168.0.121爲虛擬機ip,mater爲虛擬機別名。
完整項目請查看本片文章上傳資源。
maven依賴:
<dependencies>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-server</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-common</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs</artifactId>
<version>2.5.1</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>2.5.1</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>2.5.1</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.12</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
本段代碼包含對hbase數據庫的增刪改查,建表刪除表的功能。
package com.yl.hbase;
import java.io.IOException;
import java.util.List;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellUtil;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.HTableDescriptor;
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;
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;
/**
* HBASE的JAVA API
*
* @author hanguilin
*
*/
public class Example {
public static Configuration conf;
public static Admin admin;
public static Connection conn;
public static void main(String[] args) {
// // 創建一個列族爲name、age、address的名爲test_user的表
// createTable("test_user", Lists.newArrayList("name", "age", "address"));
// // 顯示所有表
// listTable();
// // 向test_user表中行鍵爲1,列族爲name的列添加值(此處name列族沒有子列,所以col列爲空)
// insertRow("test_user", "1", "name", "", "hanguilin");
// // 獲取test_user表中行鍵爲1,列族爲name的值
// getRow("test_user", "1", "name", "");
// // 刪除test_user表中行鍵爲1,列族爲name中的值
// deleteRow("test_user", "1", "name", "");
// // 刪除test_user表
// deleteTable("test_user");
}
/**
* 初始化
*/
public static void init() {
conf = HBaseConfiguration.create();
conf.set("hbase.rootdir", "hdfs://192.168.0.121:9000/hbase");
conf.set("hbase.cluster.distributed", "true");
conf.set("hbase.master.info.port", "16010");
conf.set("hbase.zookeeper.property.dataDir", "/usr/local/hbase/data/zookeeper");
conf.set("hbase.master", "192.168.0.121:16000");
conf.set("zookeeper.znode.parent", "/hbase/master");
conf.set("hbase.zookeeper.quorum", "192.168.0.121:2181");
try {
conn = ConnectionFactory.createConnection(conf);
admin = conn.getAdmin();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 關閉連接
*/
public static void close() {
try {
if(admin != null) {
admin.close();
}
if(conn != null) {
conn.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 創建表
*
* @param tableName 表明
* @param columnFamily 列族
*/
public static void createTable(String tableName, List<String> columnFamily) {
init();
TableName table = TableName.valueOf(tableName);
try {
// 判斷表是否已存在
if(admin.tableExists(table)) {
System.out.println(tableName + "已存在");
}
HTableDescriptor hTableDescriptor = new HTableDescriptor(table);
if(columnFamily != null && !columnFamily.isEmpty()) {
columnFamily.forEach(column -> {
HColumnDescriptor hColumnDescriptor = new HColumnDescriptor(column);
hTableDescriptor.addFamily(hColumnDescriptor);
});
}
admin.createTable(hTableDescriptor);
System.out.println("創建表成功");
} catch (IOException e) {
e.printStackTrace();
} finally {
close();
}
}
/**
* 刪除表
*
* @param tableName 表名稱
*/
public static void deleteTable(String tableName) {
init();
TableName table = TableName.valueOf(tableName);
try {
if(admin.tableExists(table)) {
// 棄用表
admin.disableTable(table);
// 刪除表
admin.deleteTable(table);
System.out.println("刪除表成功");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
close();
}
}
/**
* 獲取所有表
*/
public static void listTable() {
init();
try {
HTableDescriptor[] listTables = admin.listTables();
for (HTableDescriptor hTableDescriptor : listTables) {
System.out.println(hTableDescriptor.getTableName().getNameAsString());
}
} catch (IOException e) {
e.printStackTrace();
} finally {
close();
}
}
/**
* 插入行數據
*
* @param tableName 表名稱
* @param rowKey 行鍵
* @param colFamily 列族
* @param col 列名稱
* @param value 值
*/
public static void insertRow(String tableName, String rowKey, String colFamily, String col, String value) {
init();
try {
Table table = conn.getTable(TableName.valueOf(tableName));
Put put = new Put(rowKey.getBytes());
put.addColumn(colFamily.getBytes(), col.getBytes(), value.getBytes());
table.put(put);
table.close();
System.out.println("插入數據成功");
} catch (IOException e) {
e.printStackTrace();
} finally {
close();
}
}
/**
* 刪除行數據
*
* @param tableName 表名稱
* @param rowKey 行鍵
* @param colFamily 列族
* @param col 列名稱
*/
public static void deleteRow(String tableName, String rowKey, String colFamily, String col) {
init();
try {
Table table = conn.getTable(TableName.valueOf(tableName));
Delete delete = new Delete(rowKey.getBytes());
delete.addColumn(colFamily.getBytes(), col.getBytes());
table.delete(delete);
System.out.println("刪除數據成功");
} catch (IOException e) {
e.printStackTrace();
} finally {
close();
}
}
/**
* 獲取行數據
*
* @param tableName 表名稱
* @param rowKey 行鍵
* @param colFamily 列族
* @param col 列名稱
*/
public static void getRow(String tableName, String rowKey, String colFamily, String col) {
init();
try {
Table table = conn.getTable(TableName.valueOf(tableName));
Get get = new Get(rowKey.getBytes());
get.addColumn(colFamily.getBytes(), col.getBytes());
Result result = table.get(get);
Cell[] rawCells = result.rawCells();
for (Cell cell : rawCells) {
System.out.println("RowName:" + new String(CellUtil.cloneRow(cell)) + " ");
System.out.println("Timetamp:" + cell.getTimestamp() + " ");
System.out.println("column Family:" + new String(CellUtil.cloneFamily(cell)) + " ");
System.out.println("row Name:" + new String(CellUtil.cloneQualifier(cell)) + " ");
System.out.println("value:" + new String(CellUtil.cloneValue(cell)) + " ");
}
table.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
close();
}
}
}