分布式数据库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命令

进入hbase命令行:

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

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值