什么是HBase
HBase是一个分布式的、面向列的开源数据库,该技术来源于 Fay Chang 所撰写的Google论文“Bigtable:一个结构化数据的分布式存储系统”。就像Bigtable利用了Google文件系统(File System)所提供的分布式数据存储一样,HBase在Hadoop之上提供了类似于Bigtable的能力。HBase是Apache的Hadoop项目的子项目。HBase不同于一般的关系数据库,它是一个适合于非结构化数据存储的数据库。另一个不同的是HBase基于列的而不是基于行的模式。
NoSQL
Hbase是一个NoSQL(非关系型数据库)
- 非关系型数据库
指不遵循传统RDBMS模型的数据库
数据是非关系的,且不使用SQL作为主要查询语言
解决数据库的可伸缩性和可用性问题
不针对原子性或一致性问题
- 非关系型和
关系型数据库的区别
- NoSQL的特点
最终一致性
应用程序增加了维护一致性和处理事务等职责
冗余数据存储
HBase的概述
- HBase是一个领先的NoSQL数据库
是一个面向列存储的数据库
是一个分布式hash map
基于Google Big Table论文
使用HDFS作为存储并利用其可靠性 - HBase特点
数据访问速度快,响应时间约2-20毫秒
支持随机读写,每个节点20k~100k+ ops/s
可扩展性,可扩展到20,000+节点 - HBase应用场景
1.增量数据-时间序列数据–高容量,高速写入
2.信息交换-消息传递–高容量,高速读写
3.内容服务-Web后端应用程序–高容量,高速读写
HBase的特点
- HBase架构特点
1.强一致性
2.自动扩展
–当Region变大会自动分割
–使用HDFS扩展数据并管理空间
3.写恢复
–使用WAL(Write Ahead Log)
4.与Hadoop集成
HBase的操作
##Run below in hbase shell
##基本命令
version
status
whoami
help
##操作命令
##创建表
create ‘customer’, {NAME=>‘addr’}, {NAME=>‘order’}
##列出所有表
list
##查看表信息
desc ‘customer’
##判断表是否存在
exists ‘customer’
##表中插入数据语法:put ’
put ‘customer’, ‘jsmith’, ‘addr:city’, ‘montreal’
put ‘customer’, ‘jsmith’, ‘addr:state’, ‘ON’
put ‘customer’, ‘jsmith’, ‘order:numb’, ‘123456’
put ‘customer’, ‘jsmith’, ‘order:date’, ‘2015-12-19’
##根据rowkey获取数据
get ‘customer’, ‘jsmith’
##获取指定CF数据的两种方式
get ‘customer’, ‘jsmith’,‘addr’
get ‘customer’, ‘jsmith’,{COLUMNS=>[‘addr’]}
##获取指定列的数据
get ‘customer’, ‘jsmith’,{COLUMNS=>[‘order:numb’]}
##更新数据
put ‘customer’, ‘jsmith’, ‘order:numb’, ‘654321’
get ‘customer’, ‘jsmith’,{COLUMNS=>[‘order:numb’]}
##修改多版本存储
alter ‘customer’,NAME=>‘order’, VERSIONS=>5
##插入多行数据
put ‘customer’, ‘jsmith’, ‘order:numb’, ‘1235’
put ‘customer’, ‘jsmith’, ‘order:numb’, ‘1236’
put ‘customer’, ‘jsmith’, ‘order:numb’, ‘1237’
put ‘customer’, ‘jsmith’, ‘order:numb’, ‘1238’
put ‘customer’, ‘njones’, ‘addr:city’, ‘miami’
put ‘customer’, ‘njones’, ‘addr:state’, ‘FL’
put ‘customer’, ‘njones’, ‘order:numb’, ‘5555’
put ‘customer’, ‘tsimmons’, ‘addr:city’, ‘dallas’
put ‘customer’, ‘tsimmons’, ‘addr:state’, ‘TX’
put ‘customer’, ‘jsmith’, ‘addr:city’, ‘denver’
put ‘customer’, ‘jsmith’, ‘addr:state’, ‘CO’
put ‘customer’, ‘jsmith’, ‘order:numb’, ‘6666’
put ‘customer’, ‘njones’, ‘addr:state’, ‘TX’
put ‘customer’, ‘amiller’, ‘addr:state’, ‘TX’
##多版本数据查询
get ‘customer’, ‘jsmith’, {COLUMNS=>[‘order:numb’], VERSIONS => 5}
##全扫描
scan ‘customer’, {COLUMNS=>[‘order:numb’], VERSIONS => 2}
##指定rowkey范围查询
scan ‘customer’, {STARTROW => ‘j’, STOPROW => ‘t’}
##统计表中数据格式
count ‘customer’
##删除语法
##delete ‘<table_name>’, ‘<row_key>’, ‘<column_name >’, <time_stamp_value>
##deleteall ‘<table_name>’, ‘<row_key>’
##删除整行
deleteall ‘customer’,‘njones’
##删除一个单元格的值
delete ‘personal’,‘2’,‘personal_data:age’,1505286495492
##删除一列
delete ‘customer’,‘njones’,‘addr:city’
##删除一个列族数据,下面的操作是不正确的
##delete ‘customer’, ‘jsmith’, ‘addr’
##下面操作是正确的
alter ‘customer’,‘delete’=>‘addr’
##启动表,禁用表
disable ‘customer’
is_disabled ‘customer’
##清空表
truncate ‘customer’
##删除表,注意删除之前,如果表的状态不是disabled,需要先禁用表,才能删除,否则报错
drop ‘customer’
Hbase的框架
Java的API操作
public class hbase {
@Test
public void createTable() throws IOException {
// 1. 创建配置,配置zookeeper的集群和端口
Configuration conf = HBaseConfiguration.create();
conf.set("hbase.zookeeper.quorum","zcy01");
conf.set("hbase.zookeeper.property.clientPort","2181");
// 2. 创建连接
Connection conn = ConnectionFactory.createConnection(conf);
// 3. 创建admin
Admin admin = conn.getAdmin();
// 4. 创建表的描述信息
HTableDescriptor student = new HTableDescriptor(TableName.valueOf("student"));
// 5. 添加列簇
student.addFamily(new HColumnDescriptor("info"));
student.addFamily(new HColumnDescriptor("score"));
// 6. 调用API进行建表操作
admin.createTable(student);
}
// 判断表是否存在
@Test
public void isTableExists() throws IOException {
// 1. 创建配置,配置zookeeper的集群和端口
Configuration conf = HBaseConfiguration.create();
conf.set("hbase.zookeeper.quorum","zcy01");
conf.set("hbase.zookeeper.property.clientPort","2181");
// 2. 创建连接
Connection conn = ConnectionFactory.createConnection(conf);
// 3. 创建admin
Admin admin = conn.getAdmin();
// 4. 调用API进行判断表是否存在
System.out.println(admin.tableExists(TableName.valueOf("student2")));
}
// 向表中插入数据
@Test
public void putData2Table() throws IOException {
// 1. 创建配置,配置zookeeper的集群和端口
Configuration conf = HBaseConfiguration.create();
conf.set("hbase.zookeeper.quorum","zcy01");
conf.set("hbase.zookeeper.property.clientPort","2181");
// 2. 创建连接
Connection conn = ConnectionFactory.createConnection(conf);
// 3. 创建Table类
Table student = conn.getTable(TableName.valueOf("student"));
// 4. 创建Put类
Put put = new Put(Bytes.toBytes("1001"));
// 5. 向Put中添加 列簇,列名,值 注意:需要转化成字节数组
put.addColumn(Bytes.toBytes("info"),Bytes.toBytes("name"),Bytes.toBytes("zhangsan"));
put.addColumn(Bytes.toBytes("info"),Bytes.toBytes("sex"),Bytes.toBytes("female"));
put.addColumn(Bytes.toBytes("score"),Bytes.toBytes("course"),Bytes.toBytes("math"));
put.addColumn(Bytes.toBytes("score"),Bytes.toBytes("score"),Bytes.toBytes("89"));
// 6.调用API进行插入数据
student.put(put);
}
// 查看一条数据
@Test
public void getDataFromTable() throws IOException {
// 1. 创建配置,配置zookeeper的集群和端口
Configuration conf = HBaseConfiguration.create();
conf.set("hbase.zookeeper.quorum","192.168.174.41");
conf.set("hbase.zookeeper.property.clientPort","2181");
// 2. 创建连接
Connection conn = ConnectionFactory.createConnection(conf);
// 3. 创建Table类
Table student = conn.getTable(TableName.valueOf("student"));
// 4. 创建 Get 类
Get get = new Get(Bytes.toBytes("1001"));
// 5.调用API进行获取数据
Result result = student.get(get);
// 6. 将返回的结果进行遍历输出
Cell[] cells = result.rawCells();
for (Cell cell : cells) {
System.out.println("rowkey :"+Bytes.toString(CellUtil.cloneRow(cell)));
System.out.println("列簇 :"+Bytes.toString(CellUtil.cloneFamily(cell)));
System.out.println("列名 :"+Bytes.toString(CellUtil.cloneQualifier(cell)));
System.out.println("值 :"+Bytes.toString(CellUtil.cloneValue(cell)));
System.out.println("----------------");
}
}
// 删除表操作
@Test
public void dropTable() throws IOException {
// 1. 创建配置,配置zookeeper的集群和端口
Configuration conf = HBaseConfiguration.create();
conf.set("hbase.zookeeper.quorum","192.168.174.41");
conf.set("hbase.zookeeper.property.clientPort","2181");
// 2. 创建连接
Connection conn = ConnectionFactory.createConnection(conf);
// 3. 创建admin
Admin admin = conn.getAdmin();
// 4. 调用API禁用表
admin.disableTable(TableName.valueOf("student"));
// 5. 调用API删除表
admin.deleteTable(TableName.valueOf("student"));
}
}
HBase的读写流程
- HBase读数据的流程
1.Client先访问ZooKeeper,从meta表读取Region的位置,然后读取meta表中的数据。meta中又存储了用户表的Region信息;
2.根据namespace、表名和RowKey在meta表中找到对应的Region信息;
3.找到这个Region对应的RegionServer;
4.查找对应的Region;
5.先从MemStore找数据,如果没有,再到BlockCache里面读;
6.BlockCache还没有,再到StoreFile上读(为了读取的效率);
7.如果是从StoreFile里面读取的数据,不是直接返回给客户端,而是先写入BlockCache,再返回给客户端。
- HBase写数据的流程
(1)Client访问ZooKeeper,获取Meta表所处位置(ip)
(2)访问Meta表,然后读取Meta表中的数据。
(3)根据namespace、表名和RowKey在Meta表中找到该RowKey应该写入到哪个Region。
(4)找到这个Region对应的RegionServer,并发送写数据请求
(5)HRegionServer将数据先写到HLog(Write Ahead Log)。为了数据的持久化和恢复;
(6)HRegionServer将数据写到内存(MemStore);
(7)反馈Client写成功。
写数据这一块也可以看出,HBase将数据写入到内存中后,就返回给客户端写入成功,响应非常快。这也是为什么HBase写数据速度快的原因。
HBase与Hive的对比
1.Hive
(1)数据仓库
Hive的本质其实就相当于将HDFS中已经存储的文件在Mysql中做了一个双射关系,以方便使用HQL去管理查询。
(2)用于数据分析、清洗
Hive适用于离线的数据分析和清洗,延迟较高。
(3)基于HDFS、MapReduce
Hive存储的数据依旧在DataNode上,编写的HQL语句终将是转换为MapReduce代码执行。
2.HBase
(1)数据库
是一种面向列存储的非关系型数据库。
(2)用于存储结构化和非结构化的数据
适用于单表非关系型数据的存储,不适合做关联查询,类似JOIN等操作。
(3)基于HDFS
数据持久化存储的体现形式是Hfile,存放于DataNode中,被ResionServer以region的形式进行管理。
(4)延迟较低,接入在线业务使用
面对大量的企业数据,HBase可以直线单表大量数据的存储,同时提供了高效的数据访问速度。
Hbase与Hive集成使用
首先需要将hive-hbase-handler-1.1.0-cdh5.14.2.jar放置到hive/lib目录下
不然会报"HBase table exam202009:spu doesn’t exist while the table is declared as an external table"错误
目标:建立 Hive 表,关联 HBase 表,插入数据到 Hive 表的同时能够影响 HBase
表。
分步实现:
(1)在 Hive 中创建表同时关联 HBase
注意:-- Hive 中只支持 select 和 insert,不支持 HBase 中的版本控制。
– 在 hive 中创建外部表
create external table customer(
name string, order_numb string,order_date string,addr_city string,addr_state string)
stored by 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
with serdeproperties
("hbase.columns.mapping"=":key,order:numb,order:date,addr:city,addr:state")
tblproperties("hbase.table.name" = "customer");
提示:完成之后,可以分别进入 Hive 和 HBase 查看,都生成了对应的表
(2)向 hive 表中插入数据,在 hive 中执行如下语句。
hive> insert into table customer values (‘James’,‘1121’,‘2018-05-31’,‘toronto’,‘ON’);
(3)在 HBase Shell 中查看表中的记录。
hbase(main):002:0> scan ‘customer’
(4)可以在 HBase 中插入数据,然后在 Hive 表中查看更新的数据。
hbase(main):002:0> put ‘customer’,‘3’,‘order:numb’,‘1800’
hive> select * from customer;
2.案例二
目标:在 HBase 中已经存储了某一张表 customer,然后在 Hive 中创建一个
外部表来关联 HBase 中的 customer 这张表,使之可以借助 Hive 来分析 HBase 这
张表中的数据。
注:该案例 2 紧跟案例 1 的脚步,所以完成此案例前,请先完成案例 1。
(1)在 Hive 中创建外部表
create external table relevance_customer(
name string, order_numb string,order_date string,addr_city string,addr_state string)
stored by 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
with serdeproperties
("hbase.columns.mapping"=":key,order:numb,order:date,addr:city,addr:state")
tblproperties("hbase.table.name" = "customer");
(2)关联后就可以使用 Hive 函数进行一些分析操作了
hive (default)> select * from relevance_customer;
region裂变
一个Region就是一个表的一段Rowkey的数据集合。HBase设计中,当某个Region太大的时候HBase会拆分它。
那么为什么要拆分Region?因为当某个Region太大的时候读取效率太低了。想想我们为什么从MySQL、Oracle转移到NoSQL来?最根本的原因就是这些关系型数据库把数据放到一个地方,查询的本质其实也就是遍历key;而当数据增大到上亿的时候同一个磁盘已经无法应付这些数据的读取了,因为遍历一遍数据的时间实在太长了。我们用NoSQL的理由就是其能把大数据分拆到不同的机器上,然后就像查询一个完整的数据一样查询他们。但是当你的Region太大的时候,此时这个Region一样会遇到跟传统关系型数据库一样的问题,所以HBase会拆分Region。这也是HBase的一个优点,可以说HBase为“一个会自动分片的数据库”。
compaction
HBase是一种Log-Structured Merge Tree架构模式,HBase几乎总是在做新增操作。当你新增一个单元格的时候,HBase在HDFS上新增一条数据。当你修改一个单元格的时候,HBase在HDFS又新增一条数据,只是版本号比之前那个大(或者你自己定义)。 当你删除一个单元格的时候,HBase还是新增一条数据!只是这条数据没有value,类型为DELETE,这条数据叫墓碑标记 (Tombstone)。真正的删除发生在什么时候,由于数据库在使用过程中积累了很多增删查改操作,数据的连续性 和顺序性必然会被破坏。为了提升性能,HBase每间隔一段时间都会进行一次合并(Compaction),合并的对象为HFile文件。另外随着数据写入不断增多,Flush次数也会不断增多,进而HFile数据文件就会越来越多。然而,太多数据文件会导致数据查询IO次数增多,因此HBase尝试着不断对这些文件进行合并。
Hbase的优化
- 高可用
在 HBase 中 HMaster 负责监控 RegionServer 的生命周期,均衡 RegionServer
的负载,如果 HMaster 挂掉了,那么整个 HBase 集群将陷入不健康的状态,并且
此时的工作状态并不会维持太久。所以 HBase 支持对 HMaster 的高可用配置。
1.关闭 HBase 集群(如果没有开启则跳过此步)。
[hadoop@hadoop102 hbase]$ bin/stop-hbase.sh
2.在 conf 目录下创建 backup-masters 文件。
[hadoop@hadoop102 hbase]$ touch conf/backup-masters
3.在 backup-masters 文件中配置高可用 HMaster 节点。
[hadoop@hadoop102 hbase]$ echo hadoop103 > conf/backup-masters
4.将整个 conf 目录 scp 到其他节点
[hadoop@hadoop102 ~]$ scp -r /opt/install/hbase/conf/
hadoop@hadoop103:/opt/install/hbase/
[hadoop@hadoop102 ~]$ scp -r /opt/install/hbase/conf/
hadoop104:/opt/install/hbase/
5.启动 HBase 并打开页面测试查看,如图所示。
http://hadooo102:60010
图 HBase 高可用配置结果图
- RowKey 设计
一条数据的唯一标识就是 rowkey,那么这条数据存储于哪个分区,取决于
rowkey 处于哪个一个预分区的区间内,设计 rowkey 的主要目的 ,就是让数据
均匀的分布于所有的 region 中,在一定程度上防止数据倾斜(数据热点)。下面
rowkey 常用的设计方案。
1.生成随机数、hash、散列值
比如:
原本 rowKey 为 1001 的,SHA1 后变成:
dd01903921ea24941c26a48f2cec24e0bb0e8cc7
原本 rowKey 为 3001 的,SHA1 后变成:
49042c54de64a1e9bf0b33e00245660ef92dc7bd
原本 rowKey 为 5001 的,SHA1 后变成:
7b61dec07e02c188790670af43e717f0f46e8913
在做此操作之前,一般我们会选择从数据集中抽取样本,来决定什么样的 rowKey
来 Hash 后作为每个分区的临界值。
2.字符串反转
20170524000001 转成 10000042507102
20170524000002 转成 20000042507102
这样也可以在一定程度上散列逐步 put 进来的数据。
3.字符串拼接
20170524000001_a12e
20170524000001_93i7
- 内存优化
HBase 操作过程中需要大量的内存开销,毕竟 Table 是可以缓存在内存中的,
一般会分配整个可用内存的 70%给 HBase 的 Java 堆。但是不建议分配非常大的
堆内存,因为 GC 过程持续太久会导致 RegionServer 处于长期不可用状态,如果
因为框架占用内存过高导致系统内存不足,框架一样会被系统服务拖死。
- 基础优化
1.允许在 HDFS 的文件中追加内容
hdfs-site.xml、hbase-site.xml
属性:dfs.support.append
解释:开启 HDFS 追加同步,可以优秀的配合 HBase 的数据同步和持久化。
Hadoop2.0 默认值为 true。
2.优化 DataNode 允许的最大文件打开数
hdfs-site.xml
属性:dfs.datanode.max.transfer.threads
解释:HBase 一般都会同一时间操作大量的文件,根据集群的数量和规模以
及数据动作,设置为 4096 或者更高。默认值:4096。该操作前提是 Linux 的文
件最大数已经设置完成。
3.优化延迟高的数据操作的等待时间
hdfs-site.xml
属性:dfs.image.transfer.timeout
解释:如果对于某一次数据操作来讲,延迟非常高,socket 需要等待更长的
时间,建议把该值设置为更大的值(默认 60000 毫秒),以确保 socket 不会被
timeout 掉。
4.优化数据的写入效率
mapred-site.xml
属性:
mapreduce.map.output.compress
mapreduce.map.output.compress.codec
解释:开启这两个数据可以大大提高文件的写入效率,减少写入时间。第一
个属性值修改为 true , 第 二 个 属 性 值 修 改 为 :
org.apache.hadoop.io.compress.GzipCodec 或者其他压缩方式
5.设置 RPC 监听数量
hbase-site.xml
属性:hbase.regionserver.handler.count
解释:默认值为 30,用于指定 RPC 监听的数量,可以根据客户端的请求数
进行调整,读写请求较多时,增加此值。
6.优化 HStore 文件大小
hbase-site.xml
属性:hbase.hregion.max.filesize
解释:默认值 10737418240(10GB),如果需要运行 HBase 的 MR 任务,可
以减小此值,因为一个 region 对应一个 map 任务,如果单个 region 过大,会导
致 map 任务执行时间过长。该值的意思就是,如果 HFile 的大小达到这个数值,
则这个 region 会被切分为两个 Hfile。
7.优化 hbase 客户端缓存 客户端配置
属性:hbase.client.write.buffer
解释:用于指定 HBase 客户端缓存,增大该值可以减少 RPC 调用次数,但
是会消耗更多内存,反之则反之。一般我们需要设定一定的缓存大小,以达到减
少 RPC 次数的目的。
8.指定 scan.next 扫描 HBase 所获取的行数
客户端配置
属性:hbase.client.scanner.caching
解释:用于指定 scan.next 方法获取的默认行数,值越大,消耗内存越大。
9.flush、compact、split 机制
当 MemStore 达到阈值,将 Memstore 中的数据 Flush 进 Storefile;compact
机制则是把 flush 出来的小文件合并成大的 Storefile 文件。split 则是当 Region 达
到阈值,会把过大的 Region 一分为二。
涉及属性:
即:128M 就是 Memstore 的默认阈值 hbase.hregion.memstore.flush.size:134217728 即:这个参数的作用是当单个 HRegion 内所有的 Memstore 大小总和超过指
定值时,flush 该 HRegion 的所有 memstore。RegionServer 的 flush 是通过将请求
添加一个队列,模拟生产消费模型来异步处理的。那这里就有一个问题,当队列
来不及消费,产生大量积压请求时,可能会导致内存陡增,最坏的情况是触发
OOM。
hbase.regionserver.global.memstore.size:默认整个堆大小的 40%
regionServer 的全局 memstore 的大小,超过该大小会触发 flush 到磁盘的操
作,默认是堆大小的 40%,而且 regionserver 级别的 flush 会阻塞客户端读写。
hbase.regionserver.global.memstore.size.lower.limit:默认堆大小0.40.95
有时候集群的“写负载”非常高,写入量一直超过 flush 的量,这时,我们
就希望 MemStore 不要超过一定的安全设置。在这种情况下,写操作就要被阻塞
一直到 MemStore 恢复到一个“可管理”的大小, 这个大小就是默认值是堆大小 *
0.4 * 0.95,也就是当 HRegionserver 级别的 flush 操作发送后,会阻塞客户端写,一
直阻塞到整个 HRegionserver 级别的 MemStore 的大小为 堆大小 * 0.4 *0.95 为止。