前言
最初我们在使用MySql时,会发现它的查写的效率还是很高的,后来随着数据量的增加,它的查写效率就不再那么高了,这就出现了它的弊端。
HDFS在我们使用的时候,主要就解决了MySql存储数据量小的问题,同时也做到了数据的安全性。但是它也会有它的弊端,写入读取的速度比较慢,而且不能够修改数据。
一、HBase是什么?
1.概念
- 是分布式/可扩展/支持海量数据存储的·NoSQL数据库。
- 面向列族储存的非关系型数据库
- 用于存储结构化和非结构化的数据
2.数据模型
- Name Space:类似关系型数据库的DataBase概念,每个命名空间下有多个表。
- Region:类似于关系型数据库表的概念,不同的是,HBase定义表时只需要声明列族即可,不需要声明具体的列。这意味着,往HBase写入数据时,字段可以动态、按需指定。因此,和关系型数据库相比,HBase能够轻松应对字段变更的场景。
- Row:HBase表中的每行数据都由一个RowKey和多个Column(列)组成,数据是按照RowKey的字典顺序存储的,并且查询数据时只能根据RowKey进行检索,所以RowKey的设计十分重要。
- Column:每个列都由ClumnFamily(列族)和Column Qualifier(列限定符)进行限定,例如info:name,info:age。建表时,只需指明列族,而列限定符无需预先定义。
- Time Stamp:用于标识数据的不同版本(version),每条数据写入时,如果不指定时间戳,系统会自动为其加上该字段,其值为写入HBase的时间。
- Cell:由{rowkey, column Family:column Qualifier, time Stamp} 唯一确定的单元。
3.基本构架
- Region Server:Region的管理者,主要负责数的增、删、查、改;以及Region的切割与合并。
- Master:所有RegionServer的管理者,负责表的创建,删除及修改;主要还一个作用就是负责分配regions到具体RegionServer,监控每个RegionServer的状态(负载均衡及故障转移)。
- HBase通过Zookeeper来做Master的高可用、RegionServer的监控、元数据的入口以及集群配置的维护等工作。
- HDFS为HBase提供最终的底层数据存储服务,同时为HBase提供高可用的支持。
二、HBase Shell操作
//启动HBase
bin/start-hbase.sh
//基础操作:
//进入HBase客户端命令行
bin/hbase shell
//查看帮助命令
hbase(main):001:0> help
//创建命名空间
//create_namespace 'bigdata'
//查看所有的命名空间
list_namespace
//删除命名空间(下面没有表才能删除)
drop_namespace 'bigdata'
//创建表
create 'student','info'
create 'student','info1','info2'
//查看所有的表
list
//查看表的详情
describe ‘student’
//修改表
//删除列族信息
alter ‘bigdata:student’,'delete'=>'info2'
//修改版本信息
alter ‘bigdata:student’,{NAME=>'info1',VERSIONS=>3}
//删除表
disable 'student'
drop 'student'
//插入数据到表
put ‘bigdata:student’,'1001','info1:name','yilei'
//查看数据
scan ‘bigdata:student’
scan 'bigdata:student',{STARTROW=>'1001',STOPROW=>'1003'}
//查看全表包括标记删除或者被覆盖的数据
scan ‘bigdata:student’,{RAW=>true,VERSIONS=>10}
//GET查看数据
get 'bigdata:student','1001'
get 'bigdata:student','1002','info1:name'
get 'bigdata:student','1001',{COLUMN=>'info:name',VERSIONS=>3}
//统计表数据行数
count 'bigdata:student'
//更新数据
put 'bigdata:student','1001','info:name','NIN'
//删除数据
delete 'bigdata:student','1001','info:name'
deleteall 'bigdata:student','1001'
//清空表数据
truncate 'student'
三、HBase进阶
1.详细架构
- StoreFile:保存实际数据的物理文件,StoreFile以HFile的形式存储在HDFS上。每个Store会有一个或多个StoreFile(HFile),数据在每个StoreFile中都是有序的。
- MemStore:写缓存,由于HFile中的数据要求是有序的,所以数据是先存储在MemStore中,排好序后,等到达刷写时机才会刷写到HFile,每次刷写都会形成一个新的HFile。
- WAL:由于数据要经MemStore排序后才能刷写到HFile,但把数据保存在内存中会有很高的概率导致数据丢失,为了解决这个问题,数据会先写在一个叫做Write-Ahead logfile的文件中,然后再写入MemStore中。所以在系统出现故障的时候,数据可以通过这个日志文件重建。
2.HBase读数据流程
- 1.客户端首先访问zookeeper,请求meta’表所在的RegionServer
- 2.zookeeper返回meta所在的RegionServer
- 3.客户端请求获取meta通过meta所在RegionServer
- 4.meta所在的RegionServer返回meta给客户端
- 5.客户端将meta缓存并通过meta找到RowKey对应的RegionServer
- 6.客户端与目标RegionServer通信
- 7.将数据顺序写入或追加到WAL中
- 8.将数据写入或追加到MemStore,数据在MemStore中进行排序
- 9.向客户端发送ack
- 10.等到MemStore的刷写时机后,将数据写到HFile中
3.MemStore Flush
MemStore级别
当某个Mem的大小达到了hbase.hregion.memstore.flush.size(默认128M),会触发整个region的MemStore都会刷写
HRegion级别
当Region中的MemStore的大小达到了hbase.hregion.memstore.flush.size(默认值128M)* hbase.hregion.memstore.block.multiplier(默认值4),时,会阻止继续往该MemStore写数据。然后进行Flush
HRegionServer级别
当RegionServer中MemStore的总大小达到java_heapsize(RegionServer在配置文件中配置的内存大小)乘以hbase.regionserver.global.memstore.size(默认值0.4)乘以 hbase.regionserver.global.memstore.size.lower.limit(默认值0.95),Server会按照其所有MemStore的大小顺序(由大到小)依次进行刷写。
当处于读写高峰期时,可适当延迟刷写,当HRegionServer中MemStore的总大小达到java_heapsizehbase.regionserver.global.memstore.size(默认值0.4)时,会阻止继续往所有的MemStore写数据。这个时候就开始刷写,当regionserver上所有的memstore大小小于
java_heapsizehbase.regionserver.global.memstore.size(默认值0.4)* hbase.regionserver.global.memstore.size.lower.limit
会停止flush剩余的region
Hlog级别
当WAL文件的数量超过hbase.regionserver.maxlogs,region会按照时间顺序依次进行刷写,直到WAL文件数量减小到hbase.regionserver.maxlogs以下(该属性名已经废弃,现无需手动设置,最大值为32)。
定期刷写
距离上次刷写有一个小时没有Flush,此时也会进行Flush
手动刷写
用户通过shell命令“flush ‘table’”或者“flush ‘regionname’”分别对一个Region或者多个Region进行flush
4.读流程
- 客户端访问zookeeper,获取meta的位置
- 根据zookeeper的返回的位置,找到meta
- 根据meta中的记录找到数据存放在哪个RegionServer中的哪个Region;并将该table的region信息以及meta表的位置信息缓存在客户端的meta cache,方便下次访问
- 与目标RegionServer进行通讯
- 分别在BlockCache(读缓存),MemStore中查询目标数据,如果BlockCache中未查到相应数据则扫描对应的HFile文件,HFile中扫描到的数据块(默认64K)写入BlockCache,并将查到的所有数据进行合并。此处所有数据是指同一条数据的不同版本(timestamp)或者不同的类型(Put/Delete)
- 将合并后的结果返回给客户端
5.storeFile compact
原因:memstore每次flush都会生成一个storeFile文件,当storeFile文件越来越多时,就会影响读取的效率
分类:
minor compact
- 结果:minor compact将小文件合成大文件
- 过程:只是单纯的将小文件合并,合并过程中不会对任何数据进行删除
- 触发条件:小文件【小于128M】个数达到3个
major compact
- 结果:将所有文件合成一个文件
- 过程:在合并过程中,会清理掉过期和删除的数据
- 触发条件:每7天合并一次,在实际生产中会将此功能关闭,采用手动触发【major_compact ‘表名’】
6.region split
原因:当region写入的数据越来越多时,后续客户端数据的读写请求很大可能全部就落在一个region上,当然也是同一个RegionServer上,这样就会造成负载不均衡。
split策略:
0.94版本之前
当region中某个store大小达到10G的时候,region会进行切分,一分为二
0.94-2.0版本
R==0 || R>100 ? 10G: Min(10G,2128MR^3)
R为当前Region Server中属于该Table的Region个数
2.0之后
R==1 ? 2*128M:10G
R为当前Region Server中属于该Table的Region个数
四、HBase优化
1.预分区
why?
通常我们创建一个新表时,默认初始只有一个region,而如果这个时候我们的读写请求很频繁时,就会发生RegionServer的倾斜。而如果我们创建表时就规划了预分区就避免了这个问题。
预分区方式
- 手动设置预分区:create ‘staff1’,‘info’,SPLITS => [‘1000’,‘2000’,‘3000’,‘4000’]
- 生成16进制序列预分区:create ‘staff2’,‘info’,{NUMREGIONS => 15, SPLITALGO => ‘HexStringSplit’}
- 按照文件中设置规则预分区:创建splits.txt 再执行:create ‘staff3’,‘info’,SPLITS_FILE => ‘splits.txt’
- 使用API创建预分区
//自定义算法,产生一系列hash散列值存储在二维数组中
byte[][] splitKeys = 某个散列值函数
//创建HbaseAdmin实例
HBaseAdmin hAdmin = new HBaseAdmin(HbaseConfiguration.create());
//创建HTableDescriptor实例
HTableDescriptor tableDesc = new HTableDescriptor(tableName);
//通过HTableDescriptor实例和散列值二维数组创建带有预分区的Hbase表
hAdmin.createTable(tableDesc, splitKeys);
2.ROW KEY设置优化
- 长度原则:rowkey的长度不能太长,通常在16个字节以下
- 唯一性原则:两条数据的rowkey不能相同
- Hash原则:保证数据分散存储
常用解决方案
- 生成随机数、hash、散列值
- 字符串反转,例如时间字符串
- 字符串拼接
3.内存优化
4.基础优化
五、整合Phoeinx
1.Phoeinx定义
Phoenix是HBase的开源SQL皮肤。可以使用标准JDBC API代替HBase客户端API来创建表,插入数据和查询HBase数据。
2.Phoeinx特点
1)容易集成:如Spark,Hive,Pig,Flume和Map Reduce
2)操作简单
3)支持HBase二级索引创建
3.Phoenix二级索引
全局二级索引
创建全局索引时,会在HBase中建立一张新表。索引数据和数据表不存放在同一张表中。因此全局索引适用于读多写少的业务场景中。
写数据的时候会消耗大量开销,因为索引表也要更新,而索引表是分布在不同的数据节点上的,跨节点的数据传输带来了较大的性能消耗。
本地二级索引
索引数据和数据表的数据是存放在同一张表中(且是同一个Region),避免了在写操作的时候往不同服务器的索引表中写索引带来的额外开销。
本地二级索引的思想(空间换时间):
我们可以根据以往的经验,知道哪些字段是查询比较频繁的,例如:名字、年龄等。这个时候我们就可以把这些字段与rowkey进行拼接,抽取出来存到本地介质,比如MySql(查询速度快)中;这样当我们通过这些字段查询相关信息时,就可以先通过缓存介质中的索引表找到对应字段数据,从而就可以获取到原数据的rowkey,在通过rowkey在HBase中找到具体的详细信息。
六、与hive的集成
1.内部表
- 在hive创建表的时候会同步在HBase上也创建一个表,比如此时HBase已经存在则会报错;
- 删除外部表的时候会删除hbase的表
CREATE TABLE relevance_hbase_emp(
empno int,
ename string,
job string,
mgr int,
hiredate string,
sal double,
comm double,
deptno int)
STORED BY
'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
WITH SERDEPROPERTIES ("hbase.columns.mapping" =
":key,info:ename,info:job,info:mgr,info:hiredate,info:sal,info:comm,info:deptno")
TBLPROPERTIES ("hbase.table.name" = "hbase_emp_table");
2.外部表
- HBase表已经存在,通过hive建表进行映射
- 删除hive表,不会删除HBase的表
CREATE EXTERNAL TABLE relevance_hbase_emp(
empno int,
ename string,
job string,
mgr int,
hiredate string,
sal double,
comm double,
deptno int)
STORED BY
'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
WITH SERDEPROPERTIES ("hbase.columns.mapping" =
":key,info:ename,info:job,info:mgr,info:hiredate,info:sal,info:comm,info:deptno")
TBLPROPERTIES ("hbase.table.name" = "hbase_emp_table");
//hbase_emp_table这个表是HBase中已经存在的表
总结
HBase读写流程以及HBase的优化需要我们重点关注,同时本地二级索引的思想也是尤其重要。