大数据-Hbase实验以及讲解

简介

HBase是一个面向列的高可靠、高性能、可伸缩的分布式数据库。是Google公司的BigTable的开源实现,主要用来存储非结构化数据半结构化数据的松散数据,支持大规模海量数据的、分布式并发数据处理效率极高;适用于廉价设备,适合读操作,不适合写操作。HBase可以直接使用本地文件系统而不用HDFS作为底层数据存储方式。为了提高数据可靠性和系统的健壮性,一般都使用HDFS作为底层数据存储方式。

HBase与传统数据库的对比

对比项目 传统数据库 HBase
数据类型 关系型模型 未经解释的字符串
数据操作 插入、删除、更新、查询等已经多表连接等 只有简单的插入、查询、删除、清空等。避免了复杂的表格之间的关系,只采用单表的主键查询,无法实现表的连接操作
存储模式 基于行存储,元组或者行会连续的存储在磁盘页 基于列存储的,每个列族都由几个文件保存,不同列族的文件是分离的
数据索引 通常可以针对不同的列构建复杂的多个索引 只有一个索引-行键
数据维护 更新操作的时候,新的值会覆盖旧的值 不会删除旧版的数据,而是生成一个新的版本
可伸缩性 很难实现横向扩展,纵向扩展有限 很好的实现水平扩展

数据模型的新的概念

  • 列族
    一个表被分成许多个列族的组合。列族需要在表创建的时候就创建好,数量不能太多(HBa se的一些缺陷导致的),存储在同一个列族当中的数据,通常都是属于同一种数据类型的。在存储的时候,列明都以列族作为前缀的。比如,course:history和 course:math这两个列都属于courses这个列族

  • 列限定符

列族里面的数据通过列限定符来定位。列限定符不用事先定义,也不需要在不同行之间保持一致。

  • 时间戳
    每个单元格都保存着同一份数据的多个版本,这些版本采用时间戳进行索引。每次对一个单元格执行操作的时候,HBa se都会隐式的自动生成并存储一个时间戳。

  • 数据坐标

    传统的数据库我们都是通过二维坐标进行取值的,但是对于HBase来讲,需要根据[行键、列族、列限定符、时间戳]来确定一个单元格。

    物理视图

对于数据表的观察,便于我们观察的是概念视图,但是在HBa se中实际存储的是按照数据的物理视图进行存储的。物理视图是采用了基于列的存储方式,而不是像传统的方式那样基于行的存储方式。
在这里插入图片描述
对于在概念视图中,空的列不会存储成null,而是根本就不会存储。

列式数据库主要适合于批量数据处理和即席查询 。
他的优点是:可以降低I/O开销,支持大并发用户查询,处理速度比传统方法快100倍。
他的缺点是:执行连接操作时需要昂贵的元组重构代价,因为一个元组的不同属性被分散到不同的磁盘页中存储,当需要一个完整的元组的时候,就要从多个磁盘页中读取相应字段的值来重新组合得到原来的一个元组。

运行机制

主要理解HBa se架构个部分的作用,主要包括:

  • 客户端
  • Zookper服务器
  • Master
  • Region服务器

    • Store
    • HLog文件

      HBase 常用的She l l命令

      自行学习,网上资料很多

      HBase 常用的Java API 及应用实例

      自行学习,网上资料很多
      主要包括:HBaseConfiguration、HTableDescriptor、HcolumnDescriptor、Put、Get、ResultScanner、Resul、Scan。

      HBase的安装

  • 可以使用wget命令在HBase的官网下载想要使用的版本

  • 解压
1
2
cd ~
sudo tar -zxf ~/下载/hbase-2.2.2-bin.tar.gz -C /usr/local
  • 更改名称
1
2
cd /usr/local
sudo mv ./hbase-2.2.2 ./hbase
  • 更改权限
1
2
cd /usr/local
sudo chown -R hadoop ./hbase
  • 配置环境变量
1
2
3
vim ~/.bashrc
//如果没有引入过PATH 直接添加下面的代码,如果引入过,就将路径添加到path路径的后面即可
export PATH=$PATH:/usr/local/hbase/bin

执行命令,让配置生效

1
source ~/.bashrc
  • 添加HBase的权限
1
2
cd /usr/local
sudo chown -R hadoop ./hbase #将hbase下的所有文件的所有者改为hadoop,hadoop是当前用户的用户名。
  • 查看版本,确保安装成功
1
/usr/local/hbase/bin/hbase version

伪分布式配置

  • 配置/usr/local/hbase/conf/hbase-env.sh。命令如下
1
vim /usr/local/hbase/conf/hbase-env.sh

配置JAVA_HOME,HBASE_CLASSPATH,HBASE_MANAGES_ZK.
HBASE_CLASSPATH设置为本机HBase安装目录下的conf目录(即/usr/local/hbase/conf)

1
2
3
export JAVA_HOME=/usr/lib/jvm/jdk1.8.0_162
export HBASE_CLASSPATH=/usr/local/hbase/conf
export HBASE_MANAGES_ZK=true
  • 配置/usr/local/hbase/conf/hbase-site.xml
    用命令vi打开并编辑hbase-site.xml,命令如下
1
vim /usr/local/hbase/conf/hbase-site.xml

修改hbase.rootdir,指定HBase数据在HDFS上的存储路径;将属性hbase.cluter.distributed设置为true。假设当前Hadoop集群运行在伪分布式模式下,在本机上运行,且NameNode运行在9000端口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<configuration>
<property>
<name>hbase.rootdir</name>
<value>hdfs://localhost:9000/hbase</value>
</property>
<property>
<name>hbase.cluster.distributed</name>
<value>true</value>
</property>
<property>
<name>hbase.unsafe.stream.capability.enforce</name>
<value>false</value>
</property>
</configuration>
  • 接下来测试运行HBase
    启动hbase的时候一定要确保hadoop已经成功启动
1
2
cd /usr/local/hbase
bin/start-hbase.sh

成功安装配置好之后,下面就开始我们基本的实验吧

实验

现有以下关系型数据库中的表和数据,要求将其转换为适合于HBase存储的表并插入数据:

学生表(Student)
学号(S_No) | 姓名(S_Name) |性别(S_Sex)|年龄(S_Age)
——– | —–|—-|—-
2015001 | Zhangsan|male|23
2015002 | Mary|female|22
2015003 | Lisi |male|24
下面先插入s003 学生的信息

1
2
3
4
5
6
7
8
 hbase(main):008:0>create 'Student','S_No','S_Name','S_Sex','S_Age'
hbase(main):029:0> put 'Student','s003','S_No','2015003'
Took 0.0138 seconds
hbase(main):030:0> put 'Student','s003','S_Name','Lisi'
Took 0.0118 seconds
hbase(main):031:0> put 'Student','s003','S_Sex','male'
Took 0.0090 seconds
hbase(main):032:0> put 'Student','s003','S_Age','24'

课程表(Course)
课程号(C_No) | 课程名(C_Name) |学分(C_Credit)
——– | —–|—-|—-
123001 | Math|2.0
123002 | Computer Science|5.0
123003 |English|3.0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
hbase(main):041:0> create 'Course','C_No','C_Name','C_Credit'
Created table Course
Took 1.3558 seconds
=> Hbase::Table - Course
hbase(main):042:0> put 'Course','C001','C_No','2015001'
Took 0.0373 seconds
hbase(main):043:0> put 'Course','C001','C_Name','Math'
Took 0.0097 seconds
hbase(main):044:0> put 'Course','C001','C_No','123001'
Took 0.0060 seconds
hbase(main):045:0> put 'Course','C001','C_Credit','2.0'
Took 0.0093 seconds
hbase(main):046:0> put 'Course','c002','C_No','123002'
Took 0.0049 seconds
hbase(main):047:0> put 'Course','c002','C_Name','Computer'
Took 0.1125 seconds
hbase(main):048:0> put 'Course','c002','C_Credit','5.0'
Took 0.0107 seconds
hbase(main):049:0> put 'Course','c003','C_No','123003'
Took 0.0097 seconds
hbase(main):050:0> put 'Course','c003','C_Name','English'
Took 0.0060 seconds
hbase(main):051:0> put 'Course','c003','C_Credit','3.0'
Took 0.0089 seconds

选课表(SC)
学号(SC_Sno) | 课程号(SC_Cno))|成绩(SC_Score)
——– | —–|—-|—-
2015001 | 123001|86
2015001 | 123003|69
2015002 |123002|77
2015002 | 123003|99
2015003 | 123001|98
2015003 |123002|95

1
2
3
4
5
6
7
8
9
10
11
12
create 'SC','SC_Sno','SC_Cno','SC_Score'
Created table SC
Took 1.2786 seconds
=> Hbase::Table - SC
hbase(main):053:0> put 'SC','sc001','SC_Sno','2015001'
NoMethodError: undefined method `ut' for main:Object

hbase(main):054:0> put 'SC','sc001','SC_Sno','2015001'
Took 0.0210 seconds
hbase(main):055:0> put 'SC','sc001','SC_Cno','123001'
Took 0.0201 seconds
hbase(main):056:0> put 'SC','sc001','SC_Score','86'

这样,我们的表还有数据就填充完了,上面因为一些数据的填充的过程都是一样的,因此就写了一个实例来演示一下,需要注意的是表格中每一个行的数据,都需要为他增加一个主键,也就是语句中的sc001的部分,因为hba se是列存储的,这个用来表示每一行,因此,对应每一行的时候,这个值要有所区分

编程实现以下指定功能,并用Hadoop提供的HBase Shell命令完成相同任务:

  • 先是shell命令

(1)列出HBase所有的表的相关信息,例如表名;

1
hbase(main):073:0> list

(2)在终端打印出指定的表的所有记录数据;

1
scan 'Student'

(3)向已经创建好的表添加和删除指定的列族或列;

1
2
3
create 's1','score'
put 's1','zhangsan','score:math','69'
delete 's1','zhangsan','score:math'

(4)清空指定的表的所有记录数据;

1
hbase(main):078:0> truncate 's1'

(5)统计表的行数。

1
hbase> count 's1'
  • Java命令

使用HBase Java API进行编程控制
在进行编程之前,我们先进行一些前期的准备工作,准备好编程需要的环境。
当我们建立好普通的java项目之后,首先要进行一个导包的操作。
(导包操作以及建立项目的过程,不会的自行百度学习)
所需要的包位于

<1> 进入到“/usr/local/hbase/lib”目录,选中该目录下的所有jar文件(注意,不要选中client-facing-thirdparty、ruby、shaded-clients和zkcli这四个目录)

<2>在“client-facing-thirdparty”目录下(如下图所示),选中所有jar文件
上面就是我们所需要的全部的包了。

在这个项目下,建立一个类就可以进行编程了。
下面是上面五个小实验的详细代码 讲解,需要执行哪一个过程,在main函数中,调用即可。

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
package Test1;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.*;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.util.Bytes;

import java.io.IOException;
import java.util.List;
public class ExampleForHBase {
//首先定义一个 与数据库连接所需要的配置文件
public static Configuration configuration;
//定义一个连接对象
public static Connection connection;
//定义一个操作数据库的对象。管理员可用于创建,删除,列出,启用和禁用以及以其他方式修改表以及执行其他管理操作。
public static Admin admin;

public static void main(String[] args)throws IOException{
init();
createTable("student",new String[]{"score"});
insertData("student","zhangsan","score","English","69");
insertData("student","zhangsan","score","Math","86");
insertData("student","zhangsan","score","Computer","77");
getData("student", "zhangsan", "score","English");
close();
}
//初始化 连接数据库所需要的 配置文件,连接对象,以及管理员
public static void init(){
configuration = HBaseConfiguration.create();
configuration.set("hbase.rootdir","hdfs://0.0.0.0:8020/hbase");
try{
connection = ConnectionFactory.createConnection(configuration);
admin = connection.getAdmin();
}catch (IOException e){
e.printStackTrace();
}
}
//设置数据库连接完成之后的关闭操作
public static void close(){
try{
if(admin != null){
//管理员权限关闭
admin.close();
}
if(null != connection){
//连接关闭
connection.close();
}
}catch (IOException e){
e.printStackTrace();
}
}
// 第一个题 列出HBase所有的表的相关信息
public static void listTables() throws IOException {
init();//建立连接
List<TableDescriptor> tableDescriptors = admin.listTableDescriptors();
for(TableDescriptor tableDescriptor : tableDescriptors){
TableName tableName = tableDescriptor.getTableName();
System.out.println("Table:" + tableName);
}
close();//关闭连接
}
//第二题 在终端打印出指定的表的所有记录数据

//在终端打印出指定的表的所有记录数据
public static void getData(String tableName)throws IOException{
init();
//根据表名 获得一个表对象
Table table = connection.getTable(TableName.valueOf(tableName));
// 获得一个查询表的查询对象
Scan scan = new Scan();
//使用查询对象,获得查询到的结果(Result)集合 ResultScanner
ResultScanner scanner = table.getScanner(scan);//获取行的遍历器
for (Result result:scanner){
//将每一条Result的结果 打印输出
printRecoder(result);
}
close();
}
//打印一条记录的详情
public static void printRecoder(Result result)throws IOException{
//每一个列又包含多条信息 ,因此 需要迭代打印输出
for(Cell cell:result.rawCells()){
System.out.print("行健: "+new String(Bytes.toString(cell.getRowArray(),cell.getRowOffset(), cell.getRowLength())));
System.out.print("列簇: "+new String( Bytes.toString(cell.getFamilyArray(),cell.getFamilyOffset(), cell.getFamilyLength()) ));
System.out.print(" 列: "+new String(Bytes.toString(cell.getQualifierArray(),cell.getQualifierOffset(), cell.getQualifierLength())));
System.out.print(" 值: "+new String(Bytes.toString(cell.getValueArray(),cell.getValueOffset(), cell.getValueLength())));
System.out.println("时间戳: "+cell.getTimestamp());
}
}



//第三题 向表中 添加数据 所需要的关键字 与 shell的put语句 是对应的
//colFamily代表的是列簇 如果列为空,那么应设置col的值为“”
public static void insertData(String tableName,String rowKey,String colFamily,String col,String val) throws IOException {
init();
//建立连接 获取表
Table table = connection.getTable(TableName.valueOf(tableName));
//语句执行 添加
Put put = new Put(rowKey.getBytes());
put.addColumn(colFamily.getBytes(),col.getBytes(), val.getBytes());
table.put(put);
table.close();
close();
}
//删除数据
public static void deleRow(String tableName,String rowKey,String colFamily,String col) throws IOException {
init();
Table table = connection.getTable(TableName.valueOf(tableName));
Delete delete = new Delete(rowKey.getBytes());
//删除指定列族
delete.addFamily(Bytes.toBytes(colFamily));
//删除指定列
delete.addColumn(Bytes.toBytes(colFamily),Bytes.toBytes(col));
table.delete(delete);
table.close();
close();
}

//第四题 清空指定的表的所有记录数据;
public static void clearRows(String tableName)throws IOException{
init();
TableName tablename = TableName.valueOf(tableName);
admin.disableTable(tablename);
admin.deleteTable(tablename);
TableDescriptorBuilder tableDescriptor = TableDescriptorBuilder.newBuilder(tablename);
admin.createTable(tableDescriptor.build());
close();
}

//第五题 统计表的行数
public static void countRows(String tableName)throws IOException{
init();
Table table = connection.getTable(TableName.valueOf(tableName));
Scan scan = new Scan();
ResultScanner scanner = table.getScanner(scan);
int num = 0;
for (Result result = scanner.next();result!=null;result=scanner.next()){
num++;
}
System.out.println("行数:"+ num);
scanner.close();
close();
}

//创建表
public static void createTable(String myTableName,String[] colFamily) throws IOException {
TableName tableName = TableName.valueOf(myTableName);
if(admin.tableExists(tableName)){
System.out.println("talbe is exists!");
//删除原来的表
admin.disableTable(tablename);
admin.deleteTable(tablename);
}else {
//创建新的表
TableDescriptorBuilder tableDescriptor = TableDescriptorBuilder.newBuilder(tableName);
for(String str:colFamily){
ColumnFamilyDescriptor family =
ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(str)).build();
tableDescriptor.setColumnFamily(family);
}
admin.createTable(tableDescriptor.build());
}
}
//获取表中的数据
public static void getData(String tableName,String rowKey,String colFamily, String col)throws IOException{
Table table = connection.getTable(TableName.valueOf(tableName));
Get get = new Get(rowKey.getBytes());
get.addColumn(colFamily.getBytes(),col.getBytes());
Result result = table.get(get);
System.out.println(new String(result.getValue(colFamily.getBytes(),col==null?null:col.getBytes())));
table.close();
}
}

同时完成:
(1)createTable(String tableName, String[] fields)
创建表,参数tableName为表的名称,字符串数组fields为存储记录各个域名称的数组。要求当HBase已经存在名为tableName的表的时候,先删除原有的表,然后再创建新的表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void createTable(String myTableName,String[] colFamily) throws IOException {
TableName tableName = TableName.valueOf(myTableName);
if(admin.tableExists(tableName)){
System.out.println("talbe is exists!");
//删除原来的表
admin.disableTable(tablename);
admin.deleteTable(tablename);
}else {
//创建新的表
TableDescriptorBuilder tableDescriptor = TableDescriptorBuilder.newBuilder(tableName);
for(String str:colFamily){
ColumnFamilyDescriptor family =
ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(str)).build();
tableDescriptor.setColumnFamily(family);
}
admin.createTable(tableDescriptor.build());
}
}

(2)addRecord(String tableName, String row, String[] fields, String[] values)
向表tableName、行row(用S_Name表示)和字符串数组files指定的单元格中添加对应的数据values。其中fields中每个元素如果对应的列族下还有相应的列限定符的话,用“columnFamily:column”表示。例如,同时向“Math”、“Computer Science”、“English”三列添加成绩时,字符串数组fields为{“Score:Math”,”Score;Computer Science”,”Score:English”},数组values存储这三门课的成绩。

1
2
3
4
5
6
7
8
9
10
11
12
public static void addRecord(String tableName,String row,String[] fields,String[] values) throws IOException {
init();
Table table = connection.getTable(TableName.valueOf(tableName));
for(int i = 0;i != fields.length;i++){
Put put = new Put(row.getBytes());
String[] cols = fields[i].split(":");
put.addColumn(cols[0].getBytes(), cols[1].getBytes(), values[i].getBytes());
table.put(put);
}
table.close();
close();
}

(3)scanColumn(String tableName, String column)
浏览表tableName某一列的数据,如果某一行记录中该列数据不存在,则返回null。要求当参数column为某一列族名称时,如果底下有若干个列限定符,则要列出每个列限定符代表的列的数据;当参数column为某一列具体名称(例如“Score:Math”)时,只需要列出该列的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static void scanColumn(String tableName,String column)throws  IOException{
init();
Table table = connection.getTable(TableName.valueOf(tableName));
Scan scan = new Scan();
scan.addFamily(Bytes.toBytes(column));
ResultScanner scanner = table.getScanner(scan);
for (Result result = scanner.next(); result != null; result = scanner.next()){
showCell(result);
}
table.close();
close();
}
//格式化输出
public static void showCell(Result result){
Cell[] cells = result.rawCells();
for(Cell cell:cells){
System.out.println("RowName:"+new String(Bytes.toString(cell.getRowArray(),cell.getRowOffset(), cell.getRowLength()))+" ");
System.out.println("Timetamp:"+cell.getTimestamp()+" ");
System.out.println("column Family:"+new String(Bytes.toString(cell.getFamilyArray(),cell.getFamilyOffset(), cell.getFamilyLength()))+" ");
System.out.println("row Name:"+new String(Bytes.toString(cell.getQualifierArray(),cell.getQualifierOffset(), cell.getQualifierLength()))+" ");
System.out.println("value:"+new String(Bytes.toString(cell.getValueArray(),cell.getValueOffset(), cell.getValueLength()))+" ");
}
}

(4)modifyData(String tableName, String row, String column)
修改表tableName,行row(可以用学生姓名S_Name表示),列column指定的单元格的数据。

1
2
3
4
5
6
7
8
9
public static void modifyData(String tableName,String row,String column,String val)throws IOException{
init();
Table table = connection.getTable(TableName.valueOf(tableName));
Put put = new Put(row.getBytes());
put.addColumn(column.getBytes(),null,val.getBytes());
table.put(put);
table.close();
close();
}

(5)deleteRow(String tableName, String row)
删除表tableName中row指定的行的记录。

1
2
3
4
5
6
7
8
public static void deleteRow(String tableName,String row)throws IOException{
init();
Table table = connection.getTable(TableName.valueOf(tableName));
Delete delete = new Delete(row.getBytes());
table.delete(delete);
table.close();
close();
}