HBASE

Hase定义
分布式的实时查询的数据库。

Hbase架构

在这里插入图片描述
Hbase整体架构由Hmaster、RegionServer、ZooKeeper组成。ZK负责管理Hbase集群的HA以及存储元数据
1、HMaster
负责为RegionServer分配region,以及RegionServer的负载均衡(Region Split之后两个region开始在同一个regionServer中,之后会有Hmaster重新分配Region),管理用户对table的增删改操作,所以master挂掉之后,依然可以进行数据的增删改操作。
2、RegionServer
维护region,负责region的拆分,以及处理这些region的IO请求。
3、StoreFile
保存实际数据的物理文件,StoreFile以HFile的形式将数据存储在HDFS上,一个Store对应多个StoreFile,存储在不同的目录下(类似于hive的分区表),每个列族对应一个Store。StoreFile内部有序,且是以KV的形式存储的 RowKey=>{CF:col,timestamp,value}。
4、MenStore
写缓存,由于HFile中的数据要求是有序的,所以数据是先存储在MemStore中,排好序后,等到达刷写时机才会刷写到HFile,每次刷写都会形成一个新的HFile。
MemStore要承担多线程并发访问,采用跳跃表,JDK自带的ConcurrentSkipListMap。
5、WAL
由于数据要经MemStore排序后才能刷写到HFile,但把数据保存在内存中会有很高的概率导致数据丢失,为了解决这个问题,数据会先写在一个叫做Write-Ahead logfile的文件中,然后再写入MemStore中。所以在系统出现故障的时候,数据可以通过这个日志文件重建。

写流程

在这里插入图片描述
Hbase的元数据保存在ZK上的所以读写数据都要先访问ZK,写请求只将数据写入对应regionServer的内存就完成,所以写数据很快。
1)、Client先访问Zk获取到hbase:meta表所在的RegionServer的信息
2)、访问对应的RegionServer,获取hbase:meta表,根据读请求的namespace:table/rowkey,查询出数据对应RegionServer的信息,并将该table的region信息和meta表位置信息缓存在客户端的meta cache中,方便下次访问。
3)、访问对应RegionServer,将操作顺序写入WAL中,再将数据写入MemStore,数据在MemStore中排序。
4)、向客户端返回ack
5)、等待到达MemStore的刷写时机,将数据刷写到HFile

读流程

在这里插入图片描述
读数据会一次访问BlockCache、MemStore、StoreFile所以读取速度交写入速度较慢
1)、Client访问zk拿到hbase:meta表所在RegionServer的信息,再根据namespace:table/rowkey向对应RegionServer查出目标数据所在的region信息,并将该table的region表信息以及meta表的位置信息缓存Client。
2)、先查询BlockCache,如果命中则返回结果;没命中就查询Memstore和StoreFile,将查询的所有数据(同一条数据会有多个不同的版本(timestamp)或不同的类型(PUT/Delete))进行合并。每个RegionServer维护一个BlockCache。
BlockCache通过LRU算法维护缓存。
3)、将从文件中查询到的数据块(Block,HFile数据存储单元,默认大小为64KB,Block是Hbase的基本IO单元)缓存到Block Cache。
4)、结果返回给客户端。

BlockCache

  • 默认使用的LRUBlockCache,该策略使用LRU算法进行缓存淘汰,并且使用堆内内存,会受到fullgc影响。
  • SlabCache:和LRUBluckCache一样使用LRU算法进行缓存淘汰,不过使用了堆外内存。Hbase实现中将LRUCache和SlabCache组合使用,叫DoubleBlockCache(存在问题,已被淘汰)。
  • BucketCache:实际实现中,HBase将BucketCache和LRUBlockCache搭配使用,称为CombinedBlockCache。系统在LRUBlockCache中主要存储Index Block和Bloom Block,而将Data Block存储在BucketCache中

缓存淘汰算法除了LRU还有FIFO(队列)、LFU(最不长使用):LFU的每个数据块都有一个引用计数,所有数据块按照引用计数排序,具有相同引用计数的数据块则按照时间排序。
在这里插入图片描述

HFile 块索引 & 布隆过滤器

  • 块索引 : 块索引是HBase固有的一个特性,每条索引的key是被索引的block的第一条记录的key。因为HBase的底层数据是存储在HFile中的,而每个HFile中存储的是有序的<key, value>键值对,HFile文件内部由连续的块组成[1],每个块中存储的第一行数据的行键组成了这个文件的块索引,这些块索引信息存储在文件尾部。当HBase打开一个HFile时,块索引信息会优先加载到内存;HBase首先在内存的块索引中进行二分查找,确定可能包含给定键的块,然后读取磁盘块找到实际想要的键。
  • 布隆过滤器:实际应用中,仅仅只有块索引满足不了需求,这是因为,块索引能帮助我们更快地在一个文件中找到想要的数据,但是我们可能依然需要扫描很多文件。而布隆过滤器就是为解决这个问题而生。因为布隆过滤器的作用是,用户可以立即判断一个文件是否包含特定的行键,从而帮我们过滤掉一些不需要扫描的文件。如下图所示,块索引显示每个文件中都可能包含对应的行键,而布隆过滤器能帮我们跳过一些明显不包含对应行键的文件。
    在这里插入图片描述
MemStore Flush

在这里插入图片描述
1)、当某个memstroe的大小达到了hbase.hregion.memstore.flush.size(默认值128M),其所在region的所有memstore都会刷写。
2)、到达自动刷写的时间,也会触发memstore flush。自动刷新的时间间隔由该属性进行配置hbase.regionserver.optionalcacheflushinterval(默认1小时)。

MemStore刷盘流程

1、当 MemStore 达到一定的大小(由配置参数 hbase.hregion.memstore.flush.size 控制)时,HBase 会创建一个 MemStore 的快照(Snapshot),这个快照就是所说的 Immutable MemStore。这个快照包含了当前 MemStore 的所有数据。
2、创建快照后,原来的 MemStore 就被清空,可以继续接收新的写入操作。
3、然后,HBase 会将 Immutable MemStore 刷写到硬盘,形成一个新的 HFile。这个过程可能需要一些时间,但是不会影响新的写入操作,因为新的写入操作会写入到新的 MemStore 中。
4、刷写完成后,Immutable MemStore 就被丢弃,释放内存。
通过这种方式,HBase 可以在处理大量写入操作时,保持高效的写入性能,并且确保数据的持久性。

StoreFile Compaction

由于memstore每次刷写都会生成一个新的HFile,且同一个字段的不同版本(timestamp)和不同类型(Put/Delete)有可能会分布在不同的HFile中,因此查询时需要遍历所有的HFile。为了减少HFile的个数,以及清理掉过期和删除的数据,会进行StoreFile Compaction。

Compaction分为两种,分别是Minor CompactionMajor Compaction。Minor Compaction会将临近的若干个较小的HFile合并成一个较大的HFile,但不会清理过期和删除的数据。Major Compaction会将一个Store下的所有的HFile合并成一个大HFile,并且会清理掉过期和删除的数据
(1)Minor Compaction:触发时机,小文件(小于128m)达到3个,简单的内容合并,不会对删除数据和过期数据进行清理
(2)Major Compaction:出发时机:默认7天合并一次。hbase.hregion.maiorcompaction(默认值604800000ms),设置为0,取消自动合并。由于Major Compaction需要将数据读出来然后合并重写,所以会十分耗费资源
在这里插入图片描述

Region Split

默认情况下,每个Table起初只有一个Region,随着数据的不断写入,Region会自动进行拆分。刚拆分时,两个子Region都位于当前的Region Server,但处于负载均衡的考虑,HMaster有可能会将某个Region分配给其他的Region Server。所以Region Server可能会管理存储在远端服务器上的Region,当MajorCompact时,RegionServer会将任何不在本地的数据下载至本地。
也就是说,HBase中的数据在写入时总是存储在本地的。但是随着region的重新分配(由于负载均衡或数据恢复),数据相对于Region server不再一定是本地的。这种情况会在Major compaction后得到解决。
1)、当1个region中的某个Store下所有StoreFile的总大小超过hbase.hregion.max.filesize,该Region就会进行拆分
2)、当1个region中的某个Store下所有StoreFile的总大小超过Min(R^2 * “hbase.hregion.memstore.flush.size”,hbase.hregion.max.filesize"),该Region就会进行拆分,其中R为当前Region Server中属于该Table的个数

预分区

预分区大小一般为机器数2-3倍比较合理。
生产环境中预分区的分区间一般就按照分区数递增。
如果要设置300个region,分区间可以设置为001|、002|、…、025|、…、299|、300|;其中分区间某位的|符号是起分割作用,rowkey按照字典序排序,而|符号大小次序很大。

Hbase> create 'staff1','info','partition1',SPLITS => ['001|','002|','003|','004|']
RowKey设计

hbase根据RowKey查询数据,所以RowKey要根据具体业务设计,使RowKey均匀散列到各个Region中,还要尽可能的加入经常要查询的字段,这样可以直接通过startKey,endKey确定范围。
设计RowKey的主要目的 ,就是让数据均匀的分布于所有的region中,在一定程度上防止数据倾斜。
RowKey设计要满足三点要求:散列性、唯一性、长度原则
散列性:数据均分分布在region中,在保证散列性的前提下还有保证一定的聚合性,既将一定规则的数据放在一起,方便scan的时候指定startRow和endRow
长度原则:70-100位,rowkey最大可以是64kb,但是rowkey太大会浪费空间,因为HFile中数据是以kv的形式存储的,所以rowkey的存储是冗余的,rowkey过长会很浪费空间

RowKey设计的一般组成:预分区部分+时间戳+经常查询的字段。

案例场景:使用hbase存储电信的通话详情表,130868713268=》189123123131 2020/10/21 56min;呼叫号码、被呼叫号码、通话时间、通话时长。要求:经常查询某些用户近几个月的通话记录

假设Hbase集群有100台,我们设计Region为300个。
分区键设计:001|、002|、…、025|、…、299|、300|
为了确定每条数据的region索引rowkey一般会在前面拼接上类似分区键,这里RowKey前缀设计为001_,_大小小于|,索引可以直接根据前缀确定region。
RowKey前缀确定方式:
用手机号%300来确定RowKey前缀(意义:每个手机号的全部数据都在一个分区中)
用(手机号+202012)%300来确定RowKey前缀(意义:每个手机号每个月的数据在一个分区中)
RowKey:XXX_13012331231_2020-12-12 12:12:12
XXX=(手机号+202012)%300
要查询号码为1388312331二月份的通话记录就可以很方便的确定startRow、endRow。
startRow=(1388312331+202002)%300_1388312331_2020-02
endRow=(1388312331+202002)%300_1388312331_2020-02|

phoenix表映射

Hbase中创建的表在Phoenix中是看不见的,需要建立Phoenix到Hbase的映射,映射方式有两种:视图映射和表映射。
视图映射只能在Phoenix中查询,不能修改数据

0: jdbc:phoenix:hadoop101,hadoop102,hadoop103> create view "test"(empid varchar primary key,"name"."firstname" varchar,"name"."lastname" varchar,"company"."name" varchar,"company"."address" varchar);

建立表映射可以查看和修改源数据

0: jdbc:phoenix:hadoop101,hadoop102,hadoop103> create table "test"(empid varchar primary key,"name"."firstname" varchar,"name"."lastname" varchar,"company"."name" varchar,"company"."address" varchar)column_encoded_bytes=0;
phoenix创建Hbase二级索引

1、全局二级索引:Global Index是默认的索引格式,创建全局索引时,会在HBase中建立一张新表。也就是说索引数据和数据表是存放在不同的表中的,因此全局索引适用于多读少写的业务场景。新建的索引表以索引字段+原表rowkey作为新的rowkey。

CREATE INDEX my_index ON my_table (v1) INCLUDE (v2);

以v1+原表rowkey作为新的rowkey,索引表的列为v1和v2,意思就是从索引表可以直接查出v1和v2的值。
当表的数据量很大的时候,使用上面的语句建立索引会非常耗时,可以使用批量建立索引的方式。

CREATE INDEX my_index ON my_table (v1) INCLUDE (v2) async;
#这里的建立索引语句加了async,异步建立索引。先建立空的索引表,再通过MR建立索引数据
#下面启动批量建立索引的mr任务
${HBASE_HOME}/bin/hbase org.apache.phoenix.mapreduce.index.IndexTool --data-table "my_table" --index-table my_index --output-path ASYNC_IDX_HFILES

缺点:写数据的时候会消耗大量开销,因为索引表也要更新,而索引表是分布在不同的数据节点上的,跨节点的数据传输带来了较大的性能消耗。

2、本地二级索引:
每张表每个Region一个索引,索引和StoreFile写在一起,Local Index适用于写操作频繁的场景。
本地索引适用于写多读少,空间有限的场景,和全局索引一样,Phoneix在查询时会自动选择是否使用本地索引,使用本地索引,为避免进行写操作所带来的网络开销,索引数据和表数据都存放在相同的服务器中,当查询的字段不完全是索引字段时本地索引也会被使用,与全局索引不同的是,所有的本地索引都单独存储在同一张共享表中,由于无法预先确定Region的位置,所以在读取数据时会检查每个Region上的数据因而带来一定性能开销
索引数据和数据表的数据是存放在同一张表中(且是同一个Region),避免了在写操作的时候往不同服务器的索引表中写索引带来的额外开销。查询的字段不是索引字段索引表也会被使用,这会带来查询速度的提升。

CREATE LOCAL INDEX my_index ON my_table (my_column);

phoenix建立hbase二级索引原理
建立的索引表的新的rowkey是索引字段+原表rowkey。
由此,我猜测phoenix再根据索引字段筛选查询的时候,就可以直接先scan索引表,指定startrow=>a,endrow=>a|。从索引表中快速筛选出全部的索引字段是a的rowkey,然后再根据rowkey去查询原表

Phoenix加盐表预分区

如果主键的是顺序的序列,在频繁插入phoenix 到表中,最终会数据不停的落在一个regionServer中,容易造成热点问题。所以尽量将id打散。
利用表中定义盐值,数据会均匀的分布在各个region中。

create table "user333" ( "user_id"  varchar primary key , "info"."name" varchar , "info"."age" varchar)column_encoded_bytes=0,SALT_BUCKETS = 3
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值