分布式数据库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/路径下:

1
$ tar -zxf /home/hadoop/download/hbase-1.3.2-bin.tar.gz -C /usr/local

切换到root用户,重命名hbase-1.3.2文件夹,将解压后的文件赋权给hadoop用户。

1
2
3
$ cd /usr/local
$ mv ./hbase-1.3.2 ./hbase
$ chown -R hadoop:hadoop hbase/

配置环境变量

切回到hadoop用户:

1
$ su hadoop

编辑环境变量文件:

1
$ vi ~/.bashrc

添加Hbase环境变量。其中“:”,冒号是起分隔符的作用。

编辑完成后,使用source命令,让配置文件在当前终端立即生效。

1
$ source ~/.bashrc

查看HBase版本,确定是否安装成功。

1
$ hbase version

三、配置HBase

HBase有三种运行模式,单机模式、伪分布式模式、分布式模式,我们这里案例使用单机模式与伪分布模式。

在配置之前,必须要满足如下条件:

  • jdk
  • Hadoop( 单机模式不需要,伪分布式模式和分布式模式需要)
  • SSH

如果以上三者均为安装,请根据文章《分布式处理框架Hadoop的安装与使用》进行安装。

1.单机模式配置

修改/usr/local/hbase/conf/hbase-env.sh配置

增加如下内容:

1
2
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},这意味着每次重启系统都会丢失数据。

1
2
3
4
5
6
<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目录下运行。

1
2
$ start-hbase.sh
$ hbase shell

1.3停止HBase

1
$ stop-hbase.sh

2.伪分布式配置,使用外部Zookeeper

2.1配置主机别名

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文件夹。

1
$ tar -zxvf ./zookeeper-3.4.14.tar.gz -C /usr/local

将zookeeper文件夹所有权更改为hadoop:

1
$ chown -R hadoop:hadoop zookeeper-3.4.14/

切换回hadoop用户:

1
$ su hadoop

编辑zookeeper环境变量:

1
$ vi ~/.bashrc

使环境变量生效:

1
$ source ~/.bashrc

进入zookeeper配置文件路径:

1
$ cd /usr/local/zookeeper-3.4.14/conf/

复制zoo-sample.cfg,并将新文件命名为zoo.cfg

1
$ cp zoo_sample.cfg zoo.cfg

配置zoo.cfg

  • dataDir

zookeeper文件存放位置,此文件夹需要hadoop用户有权限使用,否则会报错。

2.3配置/usr/local/hbase/conf/hbase-env.sh

配置下列项:

1
2
3
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

配置内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<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

1
$ vi /usr/local/hbase/conf/regionservers

此处暂时只有一个节点,为本机,此处master已在/etc/hosts文件中做了映射。

2.6测试运行

关闭防火墙

1
$ systemctl disable firewalld

关闭SElinux

编辑/etc/selinux/config,将SELINUX=enforcing 修改为”SELINUX=disabled”

启动hadoop:

1
$ start-dfs.sh

出现如下说明启动Hadoop成功

启动zookeeper

1
$ zkServer.sh start

启动HBase:

1
$ start-hbase.sh

出现如下说明启动HBase成功

关闭HBase:

1
$ stop-hbase.sh

如果一直关闭不了,可以先使用如下命令:

1
2
$ hbase-daemon.sh stop master
$ stop-hbase.sh

关闭zookeeper

1
$ zkServer.sh stop

关闭Hadoop:

1
$ 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客户端:

1
$ zkCli.sh -server localhost:2181

查看所有文件夹:

删除文件夹:

退出客户端:

删除hdfs中hbase文件夹下内容:

由于本文在写时换了两种不同环境的网络,所以ip有所不同,172.20.10.6与192.168.0.121均为我虚拟机的ip,在实践时只需要统一即可。

1
$ 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中添加下列配置:

1
2
3
4
<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默认创建的行键。

1
create 'user','name','password','age','address'

describe命令查看表信息:

1.2HBase增删改查

在添加数据时,HBase会自动给数据添加一个时间戳,在需要改数据时,只需要新增一条数据就行,因为有时间戳作为版本区别,HBase会定时回收旧数据,只留下最新的几个版本,单条的记录留存的版本数量在创建的时候指定。

  • 写入数据

写入行键为1,名称为‘han’的记录:

1
put 'user', '1', 'name', 'han'

  • 删除数据

delete:删除数据,是put的反向操作。

deleteall:删除一行数据。

删除行键为1的数据的name列:

我这里是因为之前给行键为1的数据写入了4个name值,分别为‘han’,’han1’,’han2’,’han3’,所以删除时会逐级删除。

删除行键为1的数据:

  • 查看数据

get:查看表中某一行数据。

scan:查表中所有数据。

查看user表的行键为2的数据:

查看user表所有数据:

  • 删除表

先让表不可用,再删除。

1
2
disable 'user'  
drop 'user'

  • 查询表历史数据

指定历史版本(5)来创建表:

1
create 'user',{NAME=>'name',VERSIONS=>5}

插入数据:

指定历史版本数查询的数据:

1
get 'user','1',{COLUMN=>'name',VERSIONS=>2}

退出hbase shell命令

1
exit

2、Java API

由于本文是在虚拟机上搭建hadoop、hbase环境,在本地物理机上写代码运行,所以需要在物理机上配变量:

编辑C:\Windows\System32\drivers\etc\hosts文件:

说明:192.168.0.121为虚拟机ip,mater为虚拟机别名。

完整项目请查看本片文章上传资源。

maven依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<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数据库的增删改查,建表删除表的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
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();
}
}
}
查看评论