Hbase
Hbase是一种分布式、可扩展、支持海量存储的NoSQL数据库。
基于Hadoop可分布式,基于HDFS可扩展,数十亿行百万列海量数据
说明:虽然HBase的数据存储在HDFS上,且HDFS不支持,但是HBase通过技术手段实现随机、实时读写。
HBase以追加的方式对旧数据进行覆盖,从而实现对文件的修改。
RDBMS:传统关系型数据库。
数据仓库:数仓并非仅作为数据的存储,而存储数据最终的目的是为了计算和分析。
非关系型数据库:底层的物理存储结构以KV键值对的形式存储数据。
Hbase逻辑结构:
Hbase的表中 第一列为RowKey主键,横向划分了多个Region分区,纵向划分了多个列族,横纵切分的区域称为一个Store。
说明:
<1>RowKey是有序的,以字典序进行排列。
<2>Hbase不是以列划分的,而是以列族作为列的划分,每个列族有多个列字段。
<3>Store是Hbase的一个最小存储单位,最终都是以StoreFile的形式存储在HDFS上的,StoreFile的文件格式为Hfile。
<4>Hbase中的表可以是稀疏表,允许某个Cell值是空的,即不存在值但不是null。
<5>Cell是一个五维的K-V。 K(RowKey、Column Family、Column Qualifier、TimeStamp、Type)-Value。
NmaeSpace:命名空间,类似于MySql中的数据库,Hbase自带两个命名空间,Hbase和Deafult(默认),Hbase中存放的是Hbase的内置表,内置表不能通过list显示,只能list_namespace_tables '库名'显示
Table:Hbase创建表时不需要指明字段,字段可以动态、按需指定。
Row:HBase表中的每行数据都由一个RowKey和多个Column组成,数据是按照RowKey的字典顺序存储的,并且查询数据时只能根据RowKey进行检索。
Column:HBase中的每个列都由Column Family(列族)和Column Qualifier(列限定符)进行限定。
TimeStamp 版本号:用于标识数据的不同版本(version),每条数据写入时,系统会自动为其加上该字段,其值为写入HBase的时间。
Cell 唯一确定的单元,Cell中的数据全部是字节码形式存贮。
Hbase的随机写:
Timestamp是HBase实现实时随机更改数据的关键,HDFS本身不支持随机修改,HBase只能通过追加的方式对数据进行操作,并且给每个操作后的数据
都附带了一个timestamp,通过这个时间戳来显示不同版本的数据,默认只显示最新版本号的数据,对数据的删除同样是对文件的追加给删除字段增加了一个最新版本号的
Delete标签,带了这个标签的数据会被隐藏无法查出来,但是可以查之前版本号的,如果DeleteFamily整个字段的值都不能查询。所有这些操作都并没有对原始数据进行修改,
全都是通过追加的方式写入文件中,当旧数据非常多的时候,Hbase就会将HDFS上的文件下载下来修改过后再上传上去。
Hbase架构:
Master:所有RegionServer的管理者,其实现类为HMaster,负责RegionServer中的Region分配,监控RegionServer的状态,负载均衡和故障转移;处理DDl请求。
RegionServer:Region的管理者,其实现类为HRegionServer,Region的切分与合并;处理DML请求。
Zookeeper:HBase通过Zookeeper来做master的高可用、RegionServer的监控、元数据的入口以及集群配置的维护等工作。
HDFS:为Hbase提供最终的底层数据存储服务,同时为HBase提供高可用的支持。
Region:多行RowKey对应的所有Store构成Region,即一个Region包含多个Store。
说明:
1.RegionServer启动起来向Zookeeper注册,Master通过zookeeper监控RegionServer状态。
2.每一个Region会被RegionServer管理,每一个Region具体分配到哪一个RegionServer不确定。
3.某个RegionServer故障后,Master会将其管理的Region分配给其他 RegionServer管理。
RegionServer架构:
StoreFile:保存实际数据的物理文件,StoreFile以Hfile的形式存储在HDFS上,每个Store会有一个或多个StoreFile(多次溢写),数据在每个StoreFile中都是有序的。
MemStore:写缓存,由于HFile中的数据要求是有序的,所以数据是先存储在MemStore中,排好序后等到达刷写时机才会刷写到HFile,每次刷写都会形成一个新的HFile。
WAL:由于数据要经MemStore排序后才能刷写到HFile,但把数据保存在内存中会有很高的概率导致数据丢失,为了解决这个问题数据会先写在'Write-Ahead logfile'文件中,
然后再写入MemStore中,在系统出现故障的时候,数据可以通过这个日志文件重建。
BlockCache:读缓存,每次查询出的数据会缓存在BlockCache中方便下次查询。
说明:
1.每个RegionServer可以服务于多个Region。
2.每个RegionServer中有多个Store和1个Wal以及一个BlockCache,每个Store对应一个列族,包含Memstore和StoreFile。
HBase写流程:
1.Client先访问zookeeper,获取hbase的meta表位于哪个Region Server。
2.访问对应的Region Server,获取hbase的meta表,根据读请求的namespace:table/rowkey,查询出目标数据位于哪个Region Server中的哪个Region中。
并将该table的region信息以及meta表的位置信息缓存在客户端的metaCache,方便下次访问。
3.与目标Region Server进行通讯。
4.将数据顺序写入(追加)到WAL。
5.将数据写入对应的MemStore,数据会在MemStore进行排序。
6.向客户端发送ack。
7.等达到MemStore的刷写时机后,将数据刷写到HFile。
Hbase读流程:
1.Client先访问zookeeper,获取hbase的meta表位于哪个Region Server。
2.访问对应的Region Server,获取hbase的meta表,根据读请求的namespace:table/rowkey,查询出目标数据位于哪个Region Server中的哪个Region中。
并将该table的region信息以及meta表的位置信息缓存在客户端的metaCache,方便下次访问。
3.与目标Region Server进行通讯
4.分别在MemStore和Store File(HFile)中查询目标数据,并将查到的所有数据进行合并。
此处所有数据是指同一条数据的不同版本timestamp或者不同的类型(Put/Delete)。
说明:读取数据的时候是先读MemStore和BlockCache中的数据,无论BlockCache中的数据有没有都会读StoreFile的数据。
5.将查询到的新的数据块(Block,HFile数据存储单元,默认大小为64KB)缓存到Block Cache。
6.将合并后的最终结果返回给客户端。
MemStore Flush:
MemStore刷写时机:
1.当某个memstore的大小达到了默认值128M,其所在region的所有memstore都会刷写。
当memstore的大小达到了515M(默认大小*4)时,会阻止继续往该memstore写数据。
2.当regionserver中memstore的总大小达到 'heapsize*0.4*0.95'Region会按照其所有memstore的大小顺序(由大到小)依次进行刷写,
直到regionserver中所有memstore的总大小减小到上述值以下。
当regionserver中memstore的总大小达到 'heapsize*0.4'时会阻止继续往所有的memstore写数据。
3.到达自动刷写的时间也会触发memstoreflush,自动刷新的时间间隔默认1小时。
4.当WAL文件的数量超过'hbase.regionserver.max.logs'时Region会按照时间顺序依次进行刷写,默认最大32。已经不可配了。
StoreFile Compaction:
由于memstore每次刷写都会生成一个新的HFile,且同一个字段的不同版本和不同类型有可能会分布在不同的HFile中,因此查询时需要遍历所有的HFile。
为了减少HFile的个数,以及清理掉过期和删除的数据,会进行StoreFile Compaction,Compaction分为两种。
Minor Compaction:会将临近的若干个较小的HFile合并成一个较大的HFile,并清理掉部分过期和删除的数据。
Major Compaction:会将一个Store下的所有的HFile合并成一个大HFile,并且会清理掉所有过期和删除的数据。
抖动比例:0.5,默认七天大合并,关掉进行手动合并。
Region Split:
默认情况下每个Table起初只有一个Region,随着数据的不断写入,Region会自动进行拆分。刚拆分时,
两个子Region都位于当前的Region Server,但处于负载均衡的考虑,HMaster有可能会将某个Region转移给其他的Region Server。
Region Split时机:
1.当1个region中的某个Store下所有StoreFile的总大小超过hregion的最大文件大小,该Region就会进行拆分。
2.当1个region中的某个Store下所有StoreFile的总大小超过"Min(initialSize*R^3 ,hbase.hregion.max.filesize)",该Region就会进行拆分。
其中initialSize的默认值为2*hbase.hregion.memstore.flush.size,R为当前Region Server中属于该Table的Region个数
具体切分策略: N^3 * 256 = 256、2048、6192、16384 MB > 10 GB,第四次以及之后都是10G。
优化:
1.预分区
每一个region维护着startRow与endRowKey,如果加入的数据符合某个region维护的rowKey范围,则该数据交给这个region维护。
依照这个原则可以将数据所要投放的分区提前大致的规划好,以提高HBase性能。
2.RowKey设计
数据的唯一标识就是rowkey,那么这条数据存储于哪个分区,取决于rowkey处于哪个一个预分区的区间内,
设计rowkey的主要目的就是让数据均匀的分布于所有的region中,在一定程度上防止数据倾斜。
RowKey设计:
1.生成随机数、hash、散列值
2.字符串反转
3.字符串拼接
3.内存优化
HBase操作过程中需要大量的内存开销,毕竟Table是可以缓存在内存中的,但是不建议分配非常大的堆内存,因为GC过程持续太久会导致RegionServer处于长期不可用状态,通常为16-32G,
如果因为框架占用内存过高导致系统内存不足,框架一样会被系统服务拖死。
4.基础优化
1.减小Zookeeper会话超时时间,加快RegionServer挂掉后Master响应。
2.读写请求较多时,增加设置RPC监听数量。
3.手动控制Major Compaction
4.优化HStore文件大小,减小Region中的文件大小最大值,因为一个region对应一个map任务,如果单个region过大,会导致map任务执行时间过长。
5.优化HBase客户端缓存,增大该值可以减少RPC调用次数,但是会消耗更多内存。
6.指定scan.next 扫描HBase所获取的行数,值越大,消耗内存越大。
7.BlockCache占用RegionServer堆内存的比例,默认0.4,读请求比较多的情况下,可适当调大。
8.MemStore占用RegionServer堆内存的比例,默认0.4,读请求比较多的情况下,可适当调大。
1.RowKey的设计
理论(笔试题):散列性、唯一性、长度
场景(面试题):
需求:电信,根据手机号查询某个人某年[某月某日]的通话详情!
预分区:确定个数(数据量) -> 分区键[000|,001|,002|...999|] ->1001 个分区
分区号:000_,001_,002_,...,999_ 说明 |的ASICII码比_大。
分区号如何给到数据->考虑 设计考虑散列性、查询考虑集中性 在这两个之间找到平衡点。
(手机号)%(分区数-1)
(手机号+年月)%(分区数-1)
Rowkey:000_手机号_年月日时分秒
13412341234 2020-04
startRow:00X_13412341234_2021-04
stopRow :00X_13412341234_2021-05
你公司的RowKey是如何设计的?
维度数据:用户、其他维度 按照数据量分,数据量小的连预分区都没做。
预分区:数据量虽然不多,但是考虑到企业之后的发展,按照HBase服务器台数分区了10台。
分区号:00_,01_,02_,..09_,
hash(用户id)%(分区数-1)
RowKey:0X_123456
2.二级索引Phoenix
实现原理:协处理器
分类:Global、Local
Global:将索引数据另外写一张表,更适合读,因为索引表和原表不在一起,写的时候需要写两张表,还不一定在同一个节点,读数据的时候
按照索引查只查索引表就可以不用查询原表。
Local:将索引数据跟原表(原Region)数据放到一起,写
说明:读比写慢,查询过程当中RowKey有布隆过滤器用来加快查询速度。
id,name,sex,deptId
11,张三,male,10
Global:sex create index myindex on table(sex) include(deptId)
Local:sex create local index myindex on table(sex)
select id,sex,deptId from t where sex='male' and deptId='10'; 需要建两个字段的索引。 sex,deptId,索引表中字段会包含include信息。
select id,sex,deptId from t where sex='male'; 需要建一个索引,另外查询的值用Include即可。
index(sex) include(deptId) index 中的数据在RowKey中, include 中的字段在列中。
Global索引表: 有include 没有include
+ --------------------+ + --------------------+
| | 0 | | | 0 |
| RowKey +--------| | RowKey +-------|
| | deptId | | | |
|-----------+---------| |--------------+-------|
|11_male | 10 | | 11_male | |
+ -------------------+ + ------------+-------+
如果是本地索引,索引在原表数据的原Region中,跟数据在一起,按照索引切分出来RowKey直接查询数据即可,最多再Region中进数据扫描。
Local索引表: __zs_11,第一个_对应的是分区号,如果有多个分区应该为具体的分区号,如1001_zs_11。
+ --------------------------------------------------------------------+
| | Info1 | IDX |
| RowKey +----------+----------+----------+-------------------+
| | name | deptId | sex | x |
|-------------+--------------+------------+------------+------------|
| 11 | zs | 10 | male | |
|-------------+------------+-------------+--------------+-----------|
| __zs_11 | | | | 1 |
|---------+----------+----------+----------+------------------------|
3.数据流程.
写 2.X 1.X
刷写 heap*0.38 heap*0.4、128M、1h、手动
读
Menstore
BlockCache -> StoreFile
BlockCache[空的->storeFile 有->storeFile部分]
合 7天、手动合并 超过3个文件
分 min(r*r*r*128M*2,10G)
增删改->增
删除数据时机:刷写(内存中的部分已知过期数据)、大合并(所有数据见面)
HBase官方不建议设置过多的列族,每个列族都会对应一个文件,列族过多会产生多个小文件,并且读取某行数据时会扫描多个文件中的数据,涉及网络IO。