一、Hbase存储模型
-》hbase所有数据读写都依赖于rowkey
-》regionserver:负责存储hbase表数据
-》每个region唯一被一台regionserver管理(不考虑副本)
-》一台regionserver可以管理多个region
region:分区rowkey
region:分区,用于存储每张表中的所有数据
默认情况下,每一张表一开始只有一个region
region分割阈值:20
min-max:region1
00-20:region1 >>达到阈值分割,一般会从中间位置分割
min-10:region1-1 10
10-max:region1-2
10-30:region1-2 >>达到阈值准备分割
10-20:region1-2-1 10
20-max:region1-2-2
注意:region1-1 和region1-2-1还是有空余的空间了,但是不会再有数据往里面插入了
-》每一个region都有一个范围,起始rowkey值,终止rowkey的值
-》当数据进行插入时:
-》判断当前rowkey属于哪个region:rowkey的前缀匹配
-》将rowkey插入到对应region中
min-10: region1
10-20: region2
20-max: region3
012344_lkjdfldjklfdlkfkl: region1
122434_fkjdlkjflkfjlkddf: region2
503344_dkjfhdlkfkdfdkdfd: region3
原因:
1-rowkey设计不合理
region1、region2存储一半数据就不能再存储了
2-默认只有一个region
如果又多个region就可以在插入数据时候进行并发操作
接下来就围绕这两个问题进行探讨,解决热点问题.
启动hadoop:
启动zookeeper:
创建表时的第一个region是没有范围
region是hbase负载均衡和分布式存储的最小单元
regionserver:region
regionserver1:10 8个属于tab1 ,2 个属于tab2
80%
regionserver2:4 2个region属于tab1,2个region属于tab2
20%
将regionserver1上的region分配3个给regionserver2
Hbase的物理存储
put 'tbname','rowkey','cf1:name','laoda'
put 'tbname','rowkey','cf1:age','18'
put 'tbname','rowkey','cf2:phone','110'
好处:相同列簇时候读取非常快
regionserver:
-》region
-》1至多个store(一个列簇):将不同列簇的列分开存储
相同rowkey,不同列簇会分开存储;因为数据是在同一rowkey中的,所以放在同一个region
中。
1个store存储的是一个列簇
store1:cf1
store2:cf2
-》Store
-》memstore
-》storefile(0-多个)
-》HFILE
1-根据rowkey判读属于哪个region
2-根据列簇名称判断属于哪个store
3-将该条记录写入对应store的memstore
4-当memstore达到一定阈值时,memstore中的数据会开始溢写为storefile
(一次性写入)
hdfs上的存储结构
namespace:
default:默认创建表是时不加ns,就在默认的namespace下
hbase:系统表
meta:元数据
namespace:存储了hbase中所有的namespace的信息
Hbase在HDFS上的存储结构
ns1:order02,,1528188707239.b48cd2efa405a1f56f9864f345e79700.
表名,起始位置,region时间,region的唯一id
hive:database/table/partition/file
hbase:namespace/table/region/store/storefile+memstore
WALS:Hlog,write ahead log 预写日志
-》在hbase中写入数据的第一步就是将数据写入wals
-》第二步写入memstore
二、Hbase读写流程及集群架构
-》client:用户提交用户请求
-》Hmaster:负责管理
-》Hregionserver:负责存储数据
-》zookeeper:
-》存储元数据表对应的regionserver的地址
-》hbase自带的两张系统表:
hbase:namespace:存储了hbase中所有的namespace的信息
hbase:meta:元数据
rowkey:hbase中所有region的名称
column=info:regioninfo
column=info:seqnumDuringOpen
column=info:server
column=info:serverstartcode
-》记录了每个region的名称、起始范围与结束范围
-》每个region对的regionserver的地址
-》meta表只有一个region,该region的存放地址存储在zookeeper中
-》写:
put 'ns:tbname','rowkey','cf1:name','laoda'
-》客户端连接zookeeper,获取meta表所在的regionsever的地址
-》根据地址读取meta表
-》根据表名到meta表中进行前缀匹配,得到每张表的所有region信息
-》先读取元数据查询表所对应的region有哪些,以及每个region对应的范围
-》根据rowkey判断当前记录应该写入哪个region
-》根据元数据中的region信息找到region对应的regionsever
-》访问对应的regionserver,找到对应的region,将写入操作写入wals
-》根据列簇判断当前记录写入哪个store
-》将数据写入memstore
-》当内存达到阈值,开始flush,变成storefile
-》当满足一定条件时,多个storefile会进行合并:compaction
-》当大文件满足一定条件时,会触发split,当前region会等分为两个新的region
老的region下线,两个新的region会由master分配到不同的regionserver上
-》读取数据:
get 'tbname','rowkey'
get 'tbname','rowkey','info:id'
scan 'tbname'
-》客户端连接zookeeper,获取meta表所在的regionsever的地址
-》根据地址读取meta表
-》根据表名到meta表中进行前缀匹配,得到每张表的所有region信息
-》先读取元数据查询表所对应的region有哪些,以及每个region对应的范围
-》根据rowkey判断当前记录应该从哪个region中读
-》连接对应的regionsever,找到对应的region
-》判断需要的读取的列在哪些store中
-》从store中读取数据
-》先读memstore,如果内存中没有,从storefile中读
三、Hase的应用场景
-》hbase只负责读写执行,不负责删改的执行
-》删改:打标签
110 info:name laoda
put 110 info:name laoer
-》flush:
110 info:name laoda (delete)
110 info:name laoer
->compaction:合并多个storefile时,将所有标记为删除的数据进行删除
-》Hbase运维案例
https://mp.weixin.qq.com/s/vNqUqhasUvtEBhV5DWyi_w
如果文件都存在,但是元数据没有了
Bin/hbase –fsck –fixMate 进行修复
修改元数据的前提是regioninfo中的数据都是存在的
regioninfo:rigion中的所有信息,这也是获取元数据的重要文件
-》应用
1-统计结果、报表类数据:要求实时性比较高的业务场景
2-原始事实类数据:统一归档存储、提供搜索查询业务场景
短期数据:RDBMS
归档数据:NOSQL
3-中间结果数据:
-》原始数据在hbase,中间处理结果数据较大
-》符合hbase特点的数据
4-线上系统的备份数据:
归档
常见运维:
-》集群规划
-》性能调优
-》数据管理
##bin/hbase hbck 修复丢失的文件
修复HBASE丢失的数据,丢失元数据(元数据也是放到HBASE中)
内存中的数据修复不了,所以高频率的数据内存中放到HBASE中(变成storefile)
四、Hbase表的设计及其应用架构
HBase表设计的两个问题
1、每个表刚开始只有一个分区,所有读写请求全部集中在同一个region中
没有分区没有上限和下限,所有数据都存放在一台机器上
热点问题:大部分的读写请求全部集中在某台或者某几台regionserver中
2、rowkey为递增序列,不符合实际业务需求
虽然有上限,有阀值能产生分区。但是这样一很多region只能存储未满,后期也不会在
存储数据了,造成资源浪费.
region1:min max
00 20
region1-1: min 10
region1-2: 10 max
21 region1-2
22 region1-2
……
rowkey的设计
必须严格按照页面构建rowkey
所有读写依赖于rowkey的前缀匹配
组合字段
组合字段:由多个字段构成:经常作为查询条件的字段
userId_timestamp_orderId
>>这样设计不会产生递增序列:userId_timestamp_orderId
-》需求:用户进入订单查询页面>>根据业务场景设计rowkey
-》根据用户id在hbase进行前缀匹配
得到该用户的所有的订单
-》用户根据日期自己选择
使用userId+用户勾选的时间范围,查询信息
scan 'order',{STARTROW=>'userId_20180301',STOPROW=>'userId_20180401'}
示例:
timestamp_orderId:适合于小数据量
rowkey是递增的,还是会出现上面的两个问题.
110_timestamp:这种设计适用于手机营业厅查询时间间断的花费
-》极少部分需求没办法满足
比如:如上的rowkey设计,只能满足用户的查询;如果查询一个时间段的所有订单数,这时候就不好查询了。这时候需要用timestamp作为rowkey开头构建二级索引解决问题。
-》解决:构建二级索引
tb1:用户按照时间查询自己所有的订单
rowkey:userId+timestamp+orderId
110_20180609180000_000001
-》需求:按照时间查询所有订单
tb2:索引表
rowkey:
timestamp_orderId_userId
20180609180000_000001_110
info:tb1_id
userId+timestamp+orderId
110_20180609180000_000001
创建索引表:tb2 tb1中的rowkey对应tb2中列簇info:tb1_id,tb2中的rowKey就是二级索引
需求:查询某一时间段订单数
操作过程:1、通过时间(20180609180000)查询出tb2中所有info:tb1_id
2、将tb2中所有info:tb1_id作为rowkey交给tb1查询
-》问题:源表与索引表如何同步?
-》1、当往源表中插入数据时,同是也往索引表插入一份
>>缺点:性能低下
客户端向server端insert一条命令就变成两条命令,服务端需要处理两次;
中间消耗服务端的资源,还有并发量变大;这都会影响性能.
-》2、通过协处理器(Day30中会提到)
>> 往源数据表中插入一条数据时候会自动往索引表中插入数据
-》3、第三方框架
>> 公司做的最多,协处理器代码不好编写。第三方框架即结合了协处理器又不用自己编写代码.
-》Phoenix
-》solr
-》唯一性:rowkey唯一标识一条记录
散列原则
散列:将不连续的rowkey写入不同的region,避免热点(不连续必然随机)
方法一:添加随机数
-》在rowkey前面添加随机数:00-99
region1: min 20
region2: 20 40
region3: 40 60
……
regionN: 80 max
45_timestamp1_orderId1
11_timestamp2_orderId1
34_timestamp3_orderId2
查:00-99_timestamp_orderId1
查询:比如这里有0-99个随机数,我查询时候要遍历0-99查询100次才能查询到这条数据。
缺点:查询不方便,虽然解决了上述的两个问题,但是查询付出一定代价.
效率会受到影响,但是影响不大,属于常见的解决热点问题的方法.
方法二:反转
20180609180000 dlfkjdlkjfklds dfjlkdjflkdjkfl
20180609180001 dfdfdfdfeeewws dfjlkdjflkdjkf2
rowkey:timestamp_userid_orderid
00008190608102_dlfkjdlkjfklds_dfjlkdjflkdjkfl
10008190608102
20008190608102
方法三:固定编码
MD5、CRC32
RowKey的长度:
-》官方建议:rowkey长度不超过64字节
实际开发:不超过100
满足查询条件:越短越好
-》匹配越快
-》减少冗余
散列的好处:
1、散列后就不是连续数据,预分区后可以分布式存储
2、查询时候读取某段时间数据也不是基于某一个region,而是基于多个region分布式查询
列簇设计
-》长度原则:越短越好,具体叫什么无所谓<<也有冗余
-》个数原则:3个以内
一般给两个,一个是经常读写,一个是不经常读写
列标签设计
-》符合列名
-》使用列标签代替多版本
使用列簇代替列
将版本号写入到列标签中
-》使用多版本来存储
没有添加列标签时候,需要比较rowkey,cf,col,timestamp
添加了列标签后,需要比较rowkey,cf,col
原因:列标签中添加了版本号,不需要再比较timestamp
虽然这种方式不对timstamp进行比较,但是会对store进行比较。
但是相对于比较timestamp性能会提高很多
预分区
上面只解决散列问题,但是如果只有一个region还是会有热点问题
这时候我们就需要预分区
预分区是在创建表之前进行的
在创建表时,根据rowkey(的前缀)创建多个region
第一种方式:
create 'ts2', 'info', SPLITS => ['10', '20', '30', '40']
put 'ts2','000111','info:name','laoda'
put 'ts2','600111','info:name','laoda'
第二种方式:
create 'ts3', 'f1', SPLITS_FILE => 'splits.txt'
create 'ts4', 'f1', {NUMREGIONS => 15, SPLITALGO => 'HexStringSplit'}
sqoop将订单详情表导入hbase
create 'ns1:order03','info',SPLITS => ['20', '40', '60', '80']
bin/sqoop import \
--connect jdbc:mysql://bigdata-training01.erongda.com:3306/dingdan \
--username root \
--password 123456 \
--table so_detail \
--split-by rk \
--hbase-table ns1:order03 \
--hbase-row-key rk \
--column-family info \
-m 2
sqoop只能往HBase中写入数据,读数据的话还是要自己写MapReduce程序(Day30会说)
HBase数据存储
StoreFile不仅仅是HFile,还有索引文件
region是对rowkey(行)进行分区
region Server 是对列簇(列)进行分
面试题:HBase调优、 如何进行运维、出现故障如何处理?
重点内容:索引,HBase二级索引(二级索引的类型,二级索引的原理),Phoenix使用
四、Hbase Java API
引入Hbase的依赖
<repositories>
<!-- 指定该项目可以从哪些地方下载依赖包 -->
<repository>
<id>aliyun</id>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
</repository>
<repository>
<id>cloudera</id>
<url>https://repository.cloudera.com/artifactory/cloudera-repos/</url>
</repository>
<repository>
<id>jboss</id>
<url>http://repository.jboss.org/nexus/content/groups/public</url>
</repository>
</repositories>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
<version>1.2.0-cdh5.7.6</version>
</dependency>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-server</artifactId>
<version>1.2.0-cdh5.7.6</version>
</dependency>
配置访问路径:zookeeper
原因:客户端都需要经过zookeeper才能对HBASE数据进行读取和写入
数据都要经过zookeeper,zookeeper在转交数据给hbase
-》将hbase-site放入resource目录
-》在conf对象中配置