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