分佈式數據庫HBase實踐指南

一、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();
		}
	}
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章