目录
一、简介
1、概述
①、HBase原本是由Yahoo!公司开发后贡献给了Apache的一套开源的、基于Hadoop的、分布式的、可扩展的、非关系型数据库
②、如果需要对大量数据进行随机且实时读写,那么可以考虑用HBase
③、HBase能够管理非常大的表:billions of rows * millions of columns数十亿行 x 数百万列
④、HBase是仿照Google的Big Table来进行实现的,因此,Hbase和BigTable的原理几乎一致,只有实现语言不同。HBase是使用Java语言实现的,BigTable使用的是C语言实现的。HBase最终将数据落地到HDFS上
⑤、Hbase提供了2个大版本,并且2个版本都在同时更新。其中,Hadoop3.1.3版本支持的是HBase2.2.X及以上的版本
⑥、HBase作为非关系型数据库,不支持标准的SQL语法,提供了一套全新的命令
⑦、HBase能够存储稀疏类型的数据,也因此HBase能够存储结构化(数据本身有结构,经过解析之后,能够用传统数据库中的一个或几个表来存储)和半结构化数据(数据本身有结构,但是解析之后无法用传统数据库中的表来存储)
⑧、HBase本身作为数据库,提供了完整的增删改查的功能。HBase基于HDFS来进行存储,HDFS的特点是允许一次写入多次读取,不允许修改而允许追加写入,但HBase提供了”改“功能,HBase如何实现”改“功能的?-HBase实际上并没有去修改写入的数据,而是在文件末尾去追加数据,HBase会对写入的每条数据自动添加一个时间戳,当用户获取数据的时候,HBase自动返回最新的数据,那么从用户角度来看,就是发生了数据的修改。
⑨、在HBase中,数据的每一个时间戳称之为一个版本
⑩、如果要锁定唯一的一条数据,那么需要通过行键+列族+列+时间戳这四个维度来锁定,这种结构称为一个cell(单元格)
⑪、HBase中的表在创建的时候,如果不指定,那么只对外提供一个版本的数据
⑫、如果建好表之后再修改可以获取的版本,那么已经添加的数据不起作用
⑬、即使表允许对外获取多个版本的数据,在获取的时候如果不指定,依然只获取一个版本的数据
2、基本概念
①、RowKey:行键
Ⅰ、在Hbase中没有主键的概念,取而代之的是行键
Ⅱ、不同于传统的关系型数据库,在HBase中,定义表的时候不需要指定行键列,而是在添加数据的时候来手动添加行键
Ⅲ、HBase默认会对行键进行排序,按照字典序排序
②、Column Family:列族/列簇
Ⅰ、在HBase中,没有表关联的概念,取而代之的是用列族来进行统计
Ⅱ、在HBase中,一个表中至少包含1个列族,可以包含多个列族,理论上不限制列族的数量
Ⅲ、在HBase中,强调列族,但是不强调列-在定义表的时候必须定义列族,但是列可以动态增删,一个列族可以包含0到多个列
③、namespace:名称空间
Ⅰ、在HBase中没有database的概念,取而代之的是namespace
Ⅱ、在HBase启动的时候,自带了两个空间:default和hbase。hbase空间下方的是HBase的基本信息;在建表的时候如果不指定,则表默认放在default空间下
④、Colum:列
HBase表的列是由列族名、限定符以及列名组成的,其中“:”为限定符。创建HBase表不需要指定列,因为列是可变的,非常灵活。
⑤、Timestamp:时间戳
表示时间戳,记录每次操作数据的时间,通常记作数据的版本号。
⑥、Cell:单元格
根据行键、列族和列可以映射到一个对应的单元格,单元格是HBase存储数据的具体地址。
二、基本命令
命令 | 解释 |
processlist | 查看当前HBase在执行的任务 |
status | 查看HBase的运行状态 |
version | 查看HBase的版本 |
whoami | 查看HBase的当前用户 |
create 'person', {NAME => 'basic'},{NAME => 'info'},{NAME => 'other'} 或者 create 'person', 'basic', 'info', 'other' | 建立一个person表,包含3个列族:basic,info,other |
append 'person', 'p1', 'basic:name', 'Bob' | 在person表中添加一个行键为p1的数据,向basic列族的name列中添加数据 |
get 'person', 'p1' | 获取指定行键的数据 |
get 'person' , 'p1', {COLUMN => 'basic' } 或者 get 'person', 'p1', 'basic' | 获取指定行键指定列族的数据 |
get 'person', 'p1', {COLUMN => ['basic', 'info']} 或者 get 'person', 'p1', 'basic', 'info' | 获取指定行键多列族的数据 |
get 'person', 'p1', {COLUMN => 'basic:name'} 或者 get 'person', 'p1', 'basic:name' | 获取指定行键指定列的数据 |
scan 'person' | 扫描整表 |
scan 'person', {COLUMNS => 'basic'} | 获取指定列族的数据 |
scan 'person', {COLUMNS => ['basic', 'info']} | 获取多列族的数据 |
scan 'person', {COLUMNS => ['basic:name', 'other:address']} | 获取多个列的数据 |
put 'person', 'p1', 'basic:age', 20 | 修改数据,还可以增加数据 |
delete 'person', 'p1', 'other:adderss' 或者 deleteall 'person', 'pb', 'basic:name' | 删除指定行键指定列族的指定列 |
deleteall 'person', 'p1' | 删除指定行键的所有数据 |
create 'students', {NAME => 'basic', VERSIONS => 3}, {NAME => 'info', VERSIONS => 4} | 指定每一个列族允许对外获取的版本数量 |
desc 'students' 或者 describe 'students' | 描述表 |
get 'students', 's1', {COLUMN => 'basic:age', VERSIONS => 3} | 获取指定行键指定列的指定数量版本的数据 |
scan 'students', {COLUMNS => 'basic:age', VERSIONS => 3} | 获取指定列的指定数量版本的数据 |
count 'person' | 统计person表中行键的个数 |
get_splits 'person' | 获取person表对应的HRegion的个数 |
truncate 'person' | 摧毁重建person表 |
list_namespace | 查看所有的空间 |
create_namespace 'demo' | 创建demo空间 |
create 'demo:users', 'basic' | 在demo空间下创建users表 |
list_namespace_tables 'demo' | 获取demo空间下的所有表 |
describe_namespace 'demo' | 描述demo空间 |
drop_namespace 'demo' | 删除demo空间,要求这个空间为空 |
disable 'demo:users' | 禁用表 |
drop 'demo:users' | 删除表 |
enable 'person' | 启用表 |
exists 'users' | 判断表是否存在 |
is_disabled 'person' | 判断person表是否被禁用 |
is_enabled 'person' | 判断person表是否被启用 |
list | 查看所有空间下的所有的表 |
locate_region 'person', 'p1' | 定位p1行键所在的HRegion的位置 |
show_filters | 展现所有的过滤器 |
disable_all 'demo:.*' | 禁用demo空间下的所有的表 |
drop_all 'demo.*' | 删除demo空间下的所有的表 |
enable_all 'demo:.*' | 启用demo空间下的所有的表 |
三、Hive和Hbase的比较
1、Hive本质上是一个用于进行数据仓库管理的工具,在实际过程中经常用于对数据进行分析和清洗,提供了相对标准的SQL结构,底层会将SQL转化为MapReduce来执行,因此Hive的效率相对较低,更适合于离线开发的场景。Hive一般针对历史数据进行分析,一般只提供增加和查询的能力,一般不会提供修改和删除的功能
2、HBase本质上是一个非关系型数据库,在实际过程中,用于存储数据。因为HBase的读写效率较高,吞吐量较大,因此一般使用HBase来存储实时的数据,最终数据会落地到HDFS上。HBase作为数据库,提供了完整的增删改查的能力,但是相对而言,HBase的事务能力较弱。HBase不支持SQL,提供了一套完整的命令
3、总结:Hive强调的是分析能力,但是HBase强调的是存储能力,相同的地方在于两者都是利用HDFS来存储数据
四、HBase的安装
1、硬件环境:至少需要3台虚拟机或者云主机,Centos7.5及以上版本,至少需要双核,至少4G内存+20G磁盘
2、软件环境:JDK1.8+Zookeeper3.5.7+Hadoop3.1.3
3、进入/home/software目录下,上传或者下载HBase的安装包
cd /home/software
rz hbase-2.4.2-bin.tar.gz
4、解压
tar -xvf hbase-2.4.2-bin.tar.gz
5、配置相关文件
①、进入HBase的配置目录
cd hbase-2.4.2/conf
②、编辑文件hbase-env.sh
export JAVA_HOME=/home/software/jdk1.8.0_131 export HBASE_MANAGES_ZK=false
保存退出,重新生效:
source hbase-env.sh
③、编辑文件hbase-site.xml
<!--指定HBase在HDFS上的数据存储目录--> <property> <name>hbase.rootdir</name> <value>hdfs://hadoop01:9000/hbase</value> </property> <!--开启HBase的分布式--> <property> <name>hbase.cluster.distributed</name> <value>true</value> </property> <!--配置Zookeeper的连接地址--> <property> <name>hbase.zookeeper.quorum</name> <value>hadoop01:2181,hadoop02:2181,hadoop03:2181</value> </property> <property> <name>hbase.unsafe.stream.capability.enforce</name> <value>false</value> </property> <property> <name>hbase.wal.provider</name> <value>filesystem</value> </property>
④、编辑文件vim regionservers
添加当前的三台主机的主机名
6、需要将Hadoop的核心配置文件拷贝到当前的HBase的配置目录下
cp /home/software/hadoop-3.1.3/etc/hadoop/core-site.xml ./
7、 回到software目录下,远程拷贝给另外两台云主机
cd /home/software/
scp -r hbase-2.4.2 root@hadoop02:$PWD
scp -r hbase-2.4.2 root@hadoop03:$PWD
8、配置三台主机的环境变量
vim /etc/profile
在文件末尾添加:
export HBASE_HOME=/home/software/hbase-2.4.2 export PATH=$PATH:$HBASE_HOME/bin
保存退出,重新生效
source /etc/profile
9、启动
启动Zookeeper
cd /home/software/apache-zookeeper-3.5.7-bin/bin
sh zkServer.sh start
sh zkServer.sh status
在第一台主机上启动Hadoop的HDFS
start-dfs.sh
在第一台主机上启动HBase
start-hbase.sh
三台主机只要有HRegionServer,就成功了
10、访问
可以通过hadoop01:16010来访问HBase的界面
五、HBase的API
1、HBase空间API
在我们的IDEA创建maven工程:
在pom.xml引入依赖:
<dependencies>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
<version>2.4.2</version>
</dependency>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-common</artifactId>
<version>2.4.2</version>
</dependency>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-server</artifactId>
<version>2.4.2</version>
</dependency>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-protocol</artifactId>
<version>2.4.2</version>
</dependency>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase</artifactId>
<version>2.4.2</version>
<type>pom</type>
<exclusions>
<exclusion>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-mapreduce</artifactId>
<version>2.4.2</version>
</dependency>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-zookeeper</artifactId>
<version>2.4.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>compile</scope>
</dependency>
</dependencies>
在包下新建一个类NamespaceDemo,然后进行空间的相关操作:
package org.example.hbase;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.NamespaceDescriptor;
import org.apache.hadoop.hbase.client.Admin;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
public class NamespaceDemo {
private Connection connection;
private Admin admin;
//连接
@Before
public void connect() throws IOException {
//获取HBase配置
Configuration conf = HBaseConfiguration.create();
//配置Zookeeper的连接地址
conf.set("hbase.zookeeper.quorum","hadoop01:2181,hadoop02:2181,hadoop03:2181");
//发起连接
connection = ConnectionFactory.createConnection(conf);
//获取管理权
admin = connection.getAdmin();
}
// 创建空间
@Test
public void createNameSpace() throws IOException {
//构建空间描述器
NamespaceDescriptor descriptor = NamespaceDescriptor.create("demo").build();
//创建空间
admin.createNamespace(descriptor);
}
//删除空间
@Test
public void deleteNamespace() throws IOException {
admin.deleteNamespace("demo");
}
//获取所有的空间
@Test
public void listNamespaces() throws IOException {
//获取所有空间
String[] namespaces=admin.listNamespaces();
//遍历
for (String namespace : namespaces) {
System.out.println(namespace);
}
}
@After
public void close() throws IOException {
//关闭管理权
admin.close();
//关闭连接
connection.close();
}
}
2、Hbase表API
同样在上小节的包中新建一个操作表的类:下面是源代码:
package org.example.hbase;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.CompareOperator;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.filter.Filter;
import org.apache.hadoop.hbase.filter.RegexStringComparator;
import org.apache.hadoop.hbase.filter.ValueFilter;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
public class TableDemo {
private Connection connection;
private Admin admin;
private Table users;
// 连接
@Before
public void connect() throws IOException {
// 获取HBase的配置
Configuration conf = HBaseConfiguration.create();
// 指定Zookeeper的连接地址
conf.set("hbase.zookeeper.quorum", "hadoop01:2181,hadoop02:2181,hadoop03:2181");
// 发起连接
connection = ConnectionFactory.createConnection(conf);
// 获取管理权
admin = connection.getAdmin();
// 指定要操作的表
users = connection.getTable(TableName.valueOf("users"));
}
// 创建表
@Test
public void createTable() throws IOException {
// 构建列族描述器
ColumnFamilyDescriptor cf1 = ColumnFamilyDescriptorBuilder
.newBuilder("basic".getBytes(StandardCharsets.UTF_8)).build();
ColumnFamilyDescriptor cf2 = ColumnFamilyDescriptorBuilder
.newBuilder("info".getBytes(StandardCharsets.UTF_8)).build();
// 构建表描述器
TableDescriptor table = TableDescriptorBuilder.newBuilder(TableName.valueOf("users"))
.setColumnFamily(cf1).setColumnFamily(cf2).build();
// 创建表
admin.createTable(table);
}
// 添加数据
@Test
public void appendData() throws IOException {
// 构建Append对象
Append append = new Append("u1".getBytes(StandardCharsets.UTF_8));
// 指定列族
byte[] basic = "basic".getBytes(StandardCharsets.UTF_8);
byte[] info = "info".getBytes(StandardCharsets.UTF_8);
// 指定列和数据
append.addColumn(basic, "name".getBytes(StandardCharsets.UTF_8), "David".getBytes(StandardCharsets.UTF_8));
append.addColumn(basic, "age".getBytes(StandardCharsets.UTF_8), "25".getBytes(StandardCharsets.UTF_8));
append.addColumn(basic, "gender".getBytes(StandardCharsets.UTF_8), "male".getBytes(StandardCharsets.UTF_8));
append.addColumn(info, "address".getBytes(StandardCharsets.UTF_8), "beijing".getBytes(StandardCharsets.UTF_8));
// 添加数据
users.append(append);
}
// 添加/修改数据
@Test
public void putData() throws IOException {
// 构建Put对象
Put put = new Put("u1".getBytes(StandardCharsets.UTF_8));
// 指定列和数据
put.addColumn("basic".getBytes(StandardCharsets.UTF_8), "password".getBytes(StandardCharsets.UTF_8),
"123456".getBytes(StandardCharsets.UTF_8));
// 添加/修改数据
users.put(put);
}
// 添加百万条数据 - 7425 ~ 7s
@Test
public void putMillionData() throws IOException {
// 指定列族
byte[] basic = "basic".getBytes(StandardCharsets.UTF_8);
// 指定列
byte[] password = "password".getBytes(StandardCharsets.UTF_8);
// 构建集合实现批量操作
List<Put> puts = new ArrayList<>();
// 记录起始时间
long begin = System.currentTimeMillis();
// 添加百万条数据
for (int i = 0; i < 1000000; i++) {
// 构建Put对象
Put put = new Put(("u" + i).getBytes(StandardCharsets.UTF_8));
// 添加数据
put.addColumn(basic, password, getPassword());
// 将put对象放到puts集合中
puts.add(put);
// 每1000条向HBase中添加一次
if (puts.size() >= 1000) {
users.put(puts);
// 清空集合
puts.clear();
}
}
// 记录结束时间
long end = System.currentTimeMillis();
System.out.println(end - begin);
}
// 产生六位大写字母的随机密码
private byte[] getPassword() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 6; i++) {
char c = (char) (Math.random() * 26 + 65);
sb.append(c);
}
return sb.toString().getBytes(StandardCharsets.UTF_8);
}
// 获取一行数据 - get 'users', 'u1'
@Test
public void getLine() throws IOException {
// 封装Get对象
Get get = new Get("u1".getBytes(StandardCharsets.UTF_8));
// 查询数据,获取结果
Result result = users.get(get);
// 获取一行数据
NavigableMap<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> families = result.getMap();
for (Map.Entry<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>> family : families.entrySet()) {
// 键表示的是列族名
System.out.println("Column Family:" + new String(family.getKey()));
// 值表示的这个列族中所包含的列
NavigableMap<byte[], NavigableMap<Long, byte[]>> columns = family.getValue();
for (Map.Entry<byte[], NavigableMap<Long, byte[]>> column : columns.entrySet()) {
// 键表示的是列名
System.out.println("\tColumn:" + new String(column.getKey()));
// 值表示的是实际数据
NavigableMap<Long, byte[]> values = column.getValue();
for (Map.Entry<Long, byte[]> value : values.entrySet()) {
// 键表示的是时间戳
System.out.println("\t\tTimestamp:" + value.getKey());
// 值表示的是实际的值
System.out.println("\t\tValue:" + new String(value.getValue()));
}
}
}
}
// 获取指定行键指定列族的数据 - get 'users', 'u1', 'basic'
@Test
public void getFamily() throws IOException {
// 封装Get对象
Get get = new Get("u1".getBytes(StandardCharsets.UTF_8));
// 指定列族
byte[] basic = "basic".getBytes(StandardCharsets.UTF_8);
get.addFamily(basic);
// 查询数据,获取结果
Result result = users.get(get);
// 获取数据
NavigableMap<byte[], byte[]> columns = result.getFamilyMap(basic);
// 遍历
for (Map.Entry<byte[], byte[]> column : columns.entrySet()) {
// 键是列族中包含的列名,值是实际数据
System.out.println(new String(column.getKey()) + "=" + new String(column.getValue()));
}
}
// 获取指定行键指定列的数据 - get 'users', 'u1', 'basic:age'
@Test
public void getData() throws IOException {
// 封装Get对象
Get get = new Get("u1".getBytes(StandardCharsets.UTF_8));
// 指定列
byte[] basic = "basic".getBytes(StandardCharsets.UTF_8);
byte[] password = "password".getBytes(StandardCharsets.UTF_8);
get.addColumn(basic, password);
// 查询数据,获取结果
Result result = users.get(get);
// 获取实际数据
byte[] value = result.getValue(basic, password);
System.out.println(new String(value));
}
// 遍历数据 - scan 'users'
@Test
public void scan() throws IOException {
// 封装Scan对象
// 遍历整表
Scan scan = new Scan();
// 获取结果集
ResultScanner rs = users.getScanner(scan);
// 遍历结果集
byte[] basic = "basic".getBytes(StandardCharsets.UTF_8);
byte[] password = "password".getBytes(StandardCharsets.UTF_8);
for (Result r : rs) {
byte[] value = r.getValue(basic, password);
System.out.println(new String(value));
}
}
// 遍历过程中过滤数据
@Test
public void filter() throws IOException {
// 封装Scan对象
Scan scan = new Scan();
// 封装Filter对象
Filter filter = new ValueFilter(CompareOperator.EQUAL, new RegexStringComparator(".*AAA.*"));
// 设置过滤器
scan.setFilter(filter);
// 获取结果集
ResultScanner rs = users.getScanner(scan);
byte[] basic = "basic".getBytes(StandardCharsets.UTF_8);
byte[] password = "password".getBytes(StandardCharsets.UTF_8);
// 遍历结果集
for (Result r : rs) {
byte[] value = r.getValue(basic, password);
System.out.println(new String(value));
}
}
// 删除数据 - 删除指定行键的指定列
@Test
public void deleteData() throws IOException {
// 封装Delete对象
Delete del = new Delete("u1".getBytes(StandardCharsets.UTF_8));
// 指定列
del.addColumn("basic".getBytes(StandardCharsets.UTF_8), "age".getBytes(StandardCharsets.UTF_8));
// 删除数据
users.delete(del);
}
// 删除指定行键的指定列族的数据
@Test
public void deleteFamily() throws IOException {
// 封装Delete对象
Delete del = new Delete("u1".getBytes(StandardCharsets.UTF_8));
// 指定列族
del.addFamily("info".getBytes(StandardCharsets.UTF_8));
// 删除数据
users.delete(del);
}
// 删除指定行键的数据
@Test
public void deleteLine() throws IOException {
// 封装Delete对象
Delete del = new Delete("u1".getBytes(StandardCharsets.UTF_8));
// 删除数据
users.delete(del);
}
// 删除表
@Test
public void deleteTable() throws IOException {
// 禁用表
admin.disableTable(TableName.valueOf("users"));
// 删除表
admin.deleteTable(TableName.valueOf("users"));
}
// 关流
@After
public void close() throws IOException {
// 关闭管理权
admin.close();
// 关闭连接
connection.close();
}
}
部分运行结果:
下面是获取一行数据 - get 'users', 'u1'
下面是获取指定行键指定列族的数据 - get 'users', 'u1', 'basic'