HBase
HBase简介
-
Hbase是一种分布式、可扩展、支持海量存储的NoSQL数据库。
基于Hadoop可分布式,基于HDFS可扩展,可存储数十亿行百万列海量数据。
说明:
-
HBase的数据虽然存储在HDFS上,且HDFS只支持追加写而不支持随机写,但HBase通过技术手段实现随机、实时读写。
HBase以追加的方式对旧数据进行覆盖,从而实现对文件的修改,保留了时间戳记录不同的时间版本。
-
RDBMS: 传统关系型数据库。
-
非关系型数据库:底层的物理存储结构以KV键值对的形式存储数据。
-
表:有行有列,且必须得有元数据用来描述行和列。
-
数据仓库:数仓并非仅作为数据的存储,而存储数据最终的目的是为了计算和分析。
HBase 模型
- 逻辑上HBase的数据模型关系桶关系型数据库类似,数据存储在表中,有行有列。但从HBase的底层物理存储结构来讲,HBase更像一个多维的Map (multi-dimensional map)。
HBase 逻辑结构
- HBase的表中,第一列为RowKey主键,横向划分了多个Region分区,纵向划分了多个列族,横纵切分的区域称为一个Store。
说明:
-
RowKey是有序的,以字典序进行排列。
-
HBase不是以列进行划分的,而是以列族作为列的划分,每个列族有多个列字段,建表时可以不指明列字段,但必须指明列族,列字段可以在列族中随时添加。
-
Store是HBase中最小的一个存储单位,最终都是以StoreFile的形式存储在HDFS上,StoreFile的文件格式为Hfile。
-
HBase的表可以是稀疏表,允许某个Cell值为空,即不存在值,但不是Null。
-
Cell 是一个五位的K-V 。
Key(RowKey、Column Family、Column Qualifier、TimeStamp、Type)-Value。
HBase 数据模型
- Name Space :命名空间,类似于MySql中的数据库,Hbase自带两个命名空间,hbase和deafault(默认),Hbase中存放的是Hbase的内置表,内置表不能通过list显示,只能list_namespace_tables '库名'显示,default是用户默认使用的命名空间。
- Table:Hbase定义表时只需要声明列族即可,不需要指明字段,字段可以动态、按需指定。与关系型数据库相比,HBase能够轻松应对字段更变场景。
- Row:HBase表中的每行数据都由一个RowKey和多个Column组成,数据是按照RowKey的字典顺序存储的,并且查询数据时只能根据RowKey进行检索。
- Column:HBase中的每个列都由Column Family(列族)和Column Qualifier(列限定符)进行限定。
- TimeStamp:版本号,用于标识数据的不同版本(version),每条数据写入时,系统会自动为其加上该字段,其值为写入HBase的时间。
- Cell :唯一确定的单元,Cell中的数据全部是以字节码形式存储。
说明:HBase官方不建议设置过多的列族,每个列族都会对应一个文件,列族过多会产生多个小文件,并且读取某行数据时会扫描多个文件中的数据,涉及网络IO。
HBase 随机写实现
- Timestamp是HBase实现实时随机更改数据的关键,HDFS本身不支持随机修改,HBase只能通过追加的方式对数据进行操作,并且给每个操作后的数据都附带了一个timestamp,通过这个时间戳来显示不同版本的数据,默认只显示最新版本号的数据,对数据的删除同样是对文件的追加,给删除字段增加了一个最新版本号的Delete标签,带了这个标签的数据会被隐藏无法查出来,但是可以查之前版本号的,如果DeleteFamily整个字段的值都不能查询。所有这些操作都并没有对原始数据进行修改,全都是通过追加的方式写入文件中,当旧数据非常多的时候,HBase就会将HDFS上的文件下载下来修改过后再上传上去。
HBase 架构
-
Master:所有RegionServer的管理者,其实现类为HMaster,负责RegionServer中的Region分配,监控RegionServer的状态,负载均衡和故障转移;处理DDL请求对表进行操作,修改表的元数据(create、delete、alter)。
说明:在那个节点启动HBase就会在那个节点启动Master,不需要设定。
-
RegionServer:Region的管理者,其实现类为HRegionServer,负责Region的切分与合并;处理DML请求,对数据进行操作(get、put、delete)。
说明:RegionServer启动后会向Zookeeper注册,Master通过zookeeper监控RegionServer状态。
-
Zookeeper:HBase通过Zookeeper来做Master的高可用、RegionServer的监控、元数据的入口以及集群配置的维护等工作。
-
HDFS:为HBase提供最终的底层数据存储服务,同时为HBase提供高可用的支持。
-
Region:多行RowKey对应的所有Store构成Region,即一个Region包含多个Store。
说明:
- 每一个Region会被一个RegionServer管理,每一个Region具体分配到哪一个RegionServer不确定。
- 某个RegionServer故障后,Master会将其管理的Region分配给其他 RegionServer管理。
- Namespace中的meta表中记录表的元数据信息,即表有哪些region组成,每个region由哪个Regionserver服务的,并且记录了Regionserver存储在那个节,mete表由Zookeeper维护,ZK记录了Meta表的位置。
- Hbase需要用hadoop来存数据,需要用zookeeper来进行regionserver的协调,所以要先开这两个再开hbase。
RegionServer 架构
- 每个RegionServe 都包含Block Catch 、WAL、以及多个Region。
- 每个Region中包含多个Store。
- 每个Store对应一个列族,包含MemoryStore和StoreFile。
-
StoreFile:保存实际数据的物理文件(有序的K-V文件),StoreFile以Hfile的形式存储在HDFS上,每个Store会有一个或多个StoreFile(多次溢写),数据在每个StoreFile中都是有序的。
-
MemStore:写缓存,由于HFile中的数据要求是有序的,所以数据是先存储在MemStore中,排好序后等到达刷写时机才会刷写到HFile,每次刷写都会形成一个新的HFile。
-
WAL:由于数据要经MemStore排序后才能刷写到HFile,但把数据保存在内存中会有很高的概率导致数据丢失,为了解决这个问题数据会先写在'Write-Ahead logfile'文件中,然后再写MemStore中,在系统出现故障的时候,数据可以通过这个日志文件重建。
底层采用hlog存储,用来记录写入memstore中的操作,每个Region共享一个WAL,WAL也会不停的滚动刷写。存满了以后就刷写,产生新的WAL文件用来存储操作数据;虽然WAL数据存储在HDFS中需要落盘,但HDFS采用追加写速度很快。
-
BlockCache:读缓存,每次查询出的数据会缓存在BlockCache中方便下次查询。
说明:
- 每个RegionServer可以服务于多个Region。
- 每个RegionServer中有一个WAL以及一个BlockCache和多个Store和,每个Store对应一个列族,包含Memstore和StoreFile。
- MemStore每flush写一次就会产生一个StoreFile,只产生新的stroeFile文件与之前的文件无关。
HBase 写流程
- Client先访问zookeeper,获取hbase的meta表位于哪个RegionServer。
- 访问对应的RegionServer,获取HBase的meta表,根据读请求的Namespace:table/rowkey,查询出目标数据位于哪个RegionServer中的哪个Region中。并将该table的region信息以及meta表的位置信息缓存在客户端的metaCache,方便下次访问。
- 与目标RegionServer进行通讯。
- 将数据顺序写入(追加)到WAL。
- 将数据写入对应的MemStore,数据会在MemStore进行排序。
- 向客户端发送ack。
- 等达到MemStore的刷写时机后,将数据刷写到HFile。
HBase 读流程
-
Client先访问zookeeper,获取hbase的meta表位于哪个RegionServer。
-
访问对应的RegionServer,获取HBase的meta表,根据读请求的Namespace:table/rowkey,查询出目标数据位于哪个RegionServer中的哪个Region中。并将该table的region信息以及meta表的位置信息缓存在客户端的metaCache,方便下次访问。
-
与目标RegionServer进行通讯。
-
分别在MemStore和Store File(HFile)中查询目标数据,并将查到的所有数据进行合并。此处所有数据是指同一条数据的不同版本timestamp或者不同的类型(Put/Delete)。
说明:读取数据的时候是先读MemStore和BlockCache中的数据,无论BlockCache中的数据有没有都会读StoreFile的数据,如果BlockCache中没有,读StoreFile中的全部数据,如果BlockCache中没有,则读StoreFile中的部分数据(除BlockCache中以外的数据)。
-
将查询到的新的数据块(Block,HFile数据存储单元,默认大小为64KB)缓存到Block Cache。
-
将合并后的最终结果返回给客户端。
说明:
- 每个StoreFile是以Bloce(64K)块存储的,读取时会将StroeFile中的块加载到Block Cache中。
- 对表中的数据进行读写操作不需要Master也能执行,Maste的主要作用是负责管理数据的负载均衡和表DDL操作,Master 挂了以后向一个表中添加数据会导致该表的region变大,当表的region大到一定程度时RegionServer就会把这个分region成两个region,但该rgion并不会分给别的RegionServer管理,因为负责region负载均衡的Master挂掉了。
- 读数据的来源可能时StoreFile、memstore、block cache 。
- MemStore不能代表全部数据,一定要读StoreFile的,先通过Hfile文件中的索引定位Block信息,然后没有直接读Block,而是先去看一下Cache中有没有该Block有直接读,没有才真正打开Hfile读block信息。Block也是有范围的。
- Hfile自带布隆过滤器,是一个很长的数组,每个元素默认是0,布隆过滤器包含了多个hash算法。
- Memstore加快写速度,block Cache加快读流程。
- HBase读流程如何快速定位RowKey在哪一个Hfile中时间范围-根据每一条数据的时间戳、Rowkey范围、布隆过滤器
Memory Flush
-
Memory 刷写时机:
-
当某个memstore的大小达到了默认值128M,其所在region的所有memstore都会刷写。
当memstore的大小达到了515M(默认大小*4)时,会阻止继续往该memstore写数据。
-
当regionserver中memstore的总大小达到 'heapsize*0.4*0.95' 时Region会按照其所有memstore的大小顺序(由大到小)依次进行刷写,直到regionserver中所有memstore的总大小减小到上述值以下。
当regionserver中memstore的总大小达到 'heapsize*0.4'时会阻止继续往所有的memstore写数据。
-
到达自动刷写的时间也会触发memstoreflush,自动刷新的时间间隔默认1小时。
-
多个region共享一个WAL,当WAL文件的数量超过'hbase.regionserver.max.logs'时Region会按照时间顺序依次进行刷写,默认最大32。已经不可配了。
-
说明:
- HBase希望每个regionserver管理较少的region,每个region相对较大,避免由于文件总量达到了刷写条件而每个文件都很小,产生过多的小文件。并且太多的region会增加master负载均衡的压力,每个RegionServer维护100-200个Region为合理。
StoreFile Compaction
-
由于memstore每次刷写都会生成一个新的HFile,且同一个字段的不同版本和不同类型有可能会分布在不同的HFile中,因此查询时需要遍历所有的HFile。为了减少HFile的个数,以及清理掉过期和删除的数据,会进行StoreFile Compaction,Compaction分为两种。
- Minor Compaction(小合并):会将临近的若干个较小的HFile合并成一个较大的HFile,并清理掉部分过期和删除的数据。
- Major Compaction(大合并):会将一个Store下的所有的HFile合并成一个大HFile,并且会清理掉所有过期和删除的数据。
- 抖动比例:0.5,默认七天大合并,关掉进行手动合并。
说明:
- 每次刷写后都会小合并。但要判断,文件大小、文件个数、文件是否正在合并。
- 小合并只能删除部分过期数据,只能删除自己确保的数据,没办法保证其文件中是否还有该数据的时候不删除不能确定的数据。
- 大合并会删除所有的过期数据,不存在管不着的范围,但不推荐使用,会设计大量的数据io到内存进行合并,会损耗计算机的性能。
Region Split
-
默认情况下每个Table起初只有一个Region,随着数据的不断写入,Region会自动进行拆分。刚拆分时,两个子Region都位于当前的Region Server,但处于负载均衡的考虑,HMaster有可能会将某个Region转移给其他的RegionServer。
-
Region Split时机:
-
当1个region中的某个Store下所有StoreFile的总大小超过hregion的最大文件大小,该Region就会进行拆分。
-
当1个region中的某个Store下所有StoreFile的总大小超过"Min(initialSizeR^3 ,hbase.hregion.max.filesize)",该Region就会进行拆分。
其中initialSize的默认值为2*hbase.hregion.memstore.flush.size,R为当前RegionServer中属于该Table的Region个数。
具体切分策略: N^3 * 256 = 256、2048、6192、16384 MB > 10 GB,第四次以及之后都是10G。
-
HBase 数据删除
- HBase多用于数据的增改,删除操作较少。
- 删除数据时机:刷写(内存中的部分已知过期数据)、大合并(所有数据见面)。
- 表的删除:删除表之前必须将表禁用表disable,再drop 不然删不掉。
- 数据删除:
- 创建表时声明的版本数就是删除表以后会保留的版本数,如果不指明,则默认时1,当数据被清除以后会保留最新的哪一个。
- 可以存在相同时间戳的数据,指定一个时间戳,只是在查询的时候显示不出来了而已,只能显示一个,并且在客户端中删除一个会导致这两个都显示不出来,因为时间戳相同。
Phoenix
-
Phoenix是Hbase的一个皮肤客户端,将SQL转换成Hbase的一些操作执行。
-
Thin客户端通过queryserver将SQL传给RegionServer,port:8765。
-
Thick客户端直接将SQL传给了RegionServer。
-
在Phoenix中创库创表时,需要开启Hbase映射,使其可以在hbase中创库创表。
-
Phoenix中必须指明primary key 充当hbase中的Rowkey。
-
Phoenix 创建表的时候会在HBase默认创建列族从0开始。
-
Phoenix 连接Zookeeper时默认连接localhost2181,如果本地没有配置Zookeeper会连接不上。
-
Phoenix通过中间层编码的方式减小HBase中数据存储key所占的空间大小,可以通过column_encoded_bytes=0来设置使其不进行编码,但是建议使用编码,可以减少 数据所占内存空间。
-
Phoenix可能会存空列,但是HBase是以kv的形式存储的,不能单独的存储一个k,所以在存储Phoenix中的空列时会随机的赋值一个value。
-
HBase底层存储数据都是字节数组,查询的时候要统一解码,由于采用toStringBinary用来解码,只能解码字符串, 所以数值解的看不了,需要对数值进行转换。 scan ' table_name ' ,{COLUMNS => ['0:SALARY :toInt' ]}。
-
如果在HBase客户端直接put一个数字则会被认为是一个字符串,必须要告诉HBase这是一个数字,需要Bytes.toBytes(123456) ,并且Hbase默认的数值类型为long ,直接写数字Hbase会认为是String,直接toInt会得到一个0,所以要toLong,但是前面不能有不满足Long的,即负数不行。
-
Phoenix中不区分大小写,且所有小写字母会在HBase中转换成大写,如果不想让其大写,需要用双引号引起来,并且Phoenix中的名字大小写一定要和HBase中的名字大小写一致,在Phoenix中创建的表可以直接在Hbase中进行操作,但是在Hbase中创建的表必须在Phoenix中创建相应的映射。Phoenix中创建的表或schema会自动的在HBase中生产相应的表和namespace,但是在HBase中创建的不会在Phoenix中自动生成。
-
交叉查询问题:
- Phoenix插入数值, Phoenix查没有问题。
- Hbase插入数值,Hbase查没有问题。
- Phoenix插入数值,Hbase查有问题。
- Hbase插入数值,Phoenix查有问题。
-
Phoenix插入数值在Hbase看的时候数值会变成负数,反过来也相同。但自己插自己查都没问题。
- 如果数据中没有所谓的负数的数值,建议直接使用 UNSIGNED types。
- 如果数据中有所谓的负数的数值,正常使用Phoenix存和查没有问题。
原因在于两者的字节序列化方式不同,对于 varchar、char、unsigned * tpye, Phoenix采用HBase的字节序列化方式, 而在其他类型如tinyint、smallint、integer、bigint等采用自己的字节序列化方式。
-
Hbase在对rowkey进行字典排序,Rowkey是数值型时,负数的高位是1,整数的高位是0,会导致负数一定会排在整数的后面,Phoenix认为负数1排在整数0前面不好,所以Phoenix对整数和负数的高位进行了颠倒,使其在hbase存储时,整数会在负数的后面。如果不希望Phoenix对数值进行颠倒,则采用unsigned类型,或者不要交叉查询。
-
与HBase映射
phoenix建表,hbase也建表
hbase存在表,phoenix中添加试图
hbase中存在表,phoenix中创建表
hbase中有表创建hive的表只能是外部表
二级索引Phoenix
-
索引的本质:空间换时间,创建索引提高了查询效率,但占用了空间且更新慢。
-
一级索引:RowKey
-
二级索引:就是给出了RowKey之外的列创建索引。根据索引创建新表,需要过滤条件过滤时创建二级索引,过滤是需要定位的,而查找只需要将包含的结果输出即可,如果不需要过滤只是查询一下,不用建立该字段的索引,而将字段包含进索引表即可。
-
实现原理:协处理器,当对原始表进行数据操作时,协处理器会在索引表进行相应的操作。
说明:Phoenix建二级索引的时候需要做很多的操作,所以需要在HBase中设置相关的设置,否则HBase不允许Phoenix进行相关操作。
-
二级索引分类:
- 全局二级索引Global:将索引数据另外写一张表,更适合读,因为索引表和原表可能维护在不同的regionServer中,写的时候需要写两张表,还不一定在同一个节点,会跨regionserver进行操作,效率低,读数据的时候按照索引查只查索引表就可以不用查询原表。
- 原理:Phoenix创建的全局二级索引在Hbase中创建一张新表,表的rowKey 就是索引字段+原表的rowkey。
- 通过rowkey字段进行过滤可以快速定位,而通过非rowkey的字段进行过滤会进行全表扫描,而且找到了也不会停止,可能存在该字段的多个值,一定会扫面全表。
- 本地二级索引Local:将索引字段+原表的rowkey 作为新的rowKey插入到原表中。该表还包含了之前的rowkey。
- 全局二级索引Global:将索引数据另外写一张表,更适合读,因为索引表和原表可能维护在不同的regionServer中,写的时候需要写两张表,还不一定在同一个节点,会跨regionserver进行操作,效率低,读数据的时候按照索引查只查索引表就可以不用查询原表。
-
Example:
id | name | sex | deptId |
---|---|---|---|
11 | 张三 | male | 10 |
创建索引:
Globle:Sex
创建单个字段的全局二级索引:
create index myindex on table(sex)
创建含其他字段的全局二级索引:
create index myindex on table(sex) include(deptId)
说明:index中的数据在RowKey中,include中的字段在列中。
Local :Sex
create local index myindex on table(sex)
如果是本地索引,索引在原表数据的原Region中,跟数据在一起,按照索引切分出来RowKey直接查询数据即可,最多在Region中进数据扫描。
查询:
select id,sex,deptId from t where sex='male' and deptId='10';
需要建两个字段的索引, sex,deptId。
select id,sex,deptId from t where sex='male';
需要建一个索引sex,另外一个查询的字段值用Include(deptId)即可。
RowKey 设计
-
理论:散列性、唯一性、长度(满足需求的情况下尽可能短)。
-
预分区:
- 相同信息尽量放在一个分区中,方便查询时只需要查询少量的分区。
- 希望数据均衡的放到不同的region,一定程度上防止数据倾斜。
- 如果数据本身就在一个范围内再对该数据进行分区是不合适的,可能会造成数据分布不均匀。
- 不建议分配特别大的内存空间,太大了可能会增加gc负担,特别是fullgc时,会导致所有线程阻塞。
-
分区设置:
-
手动设置与分区
create 'staff1','info',SPLITS => ['1000','2000','3000','4000']
-
通过十六进制序列预分区
create 'staff2','info',{NUMREGIONS => 15, SPLITALGO =>'HexStringSplit'}
-
通过文件进行预分区
create 'staff3','info',SPLITS_FILE => 'split.txt'
-
API代码实现创建二位byte数组
-
-
需求:电信,根据手机号查询某个人某年[某月某日]的通话详情!
-
预分区:确定个数(数据量) -> 分区键[000|,001|,002|...999|] ->1001 个分区
-
分区号:000_,001_,002_,...,999_ 说明 |的ASICII码比_大。
分区号如何给到数据->考虑 设计考虑散列性、查询考虑集中性 在这两个之间找到平衡点。
将用户每个月的记录放在一个分区,分区的计算:身份信息+日期 进行hash 然后对分区数取余。
(手机号)%(分区数-1) -> 365
(手机号+年月)%(分区数-1) -> 12
-
Rowkey : 按月设计分区, 则 rowKey = 分区号+身份信息+日期 。
如:000_手机号_年月日时分秒
-
查找对象 :13412341234 2020-04
-
查找范围:
startRow:00X_13412341234_2021-04
stopRow :00X_13412341234_2021-05范围扫描 startrow stoprow 左闭右开。
-
-
我们公司RowKey 的设计。
维度数据:用户、其他维度 按照数据量分,数据量小的连预分区都没做。
预分区:数据量虽然不多,但是考虑到企业之后的发展,按照HBase服务器台数分区了10台。
分区号:00_,01_,02_,..09_,
hash(用户id)%(分区数-1)
RowKey:0X_123456
HBase优化
-
预分区
每一个region维护着startRow与endRowKey,如果加入的数据符合某个region维护的rowKey范围,则该数据交给这个region维护。依照这个原则可以将数据所要投放的分区提前大致的规划好,以提高HBase性能。
-
RowKey设计
数据的唯一标识就是rowkey,那么这条数据存储于哪个分区,取决于rowkey处于哪个一个预分区的区间内,设计rowkey的主要目的就是让数据均匀的分布于所有的region中,在一定程度上防止数据倾斜。
RowKey 设计:
- 生成随机数、hash、散列值
- 字符串反转
- 字符串拼接
-
内存优化
HBase操作过程中需要大量的内存开销,毕竟Table是可以缓存在内存中的,但是不建议分配非常大的堆内存,因为GC过程持续太久会导致RegionServer处于长期不可用状态,通常为16-32G,如果因为框架占用内存过高导致系统内存不足,框架一样会被系统服务拖死。
-
基础优化
- 减小Zookeeper会话超时时间,加快RegionServer挂掉后Master响应。
- 读写请求较多时,增加设置RPC监听数量。
- 手动控制Major Compaction.
- 优化HStore文件大小,减小Region中的文件大小最大值,因为一个region对应一个map任务,如果单个region过大,会导致map任务执行时间过长。
- 优化HBase客户端缓存,增大该值可以减少RPC调用次数,但是会消耗更多内存。
- 指定scan.next 扫描HBase所获取的行数,值越大,消耗内存越大。
- BlockCache占用RegionServer堆内存的比例,默认0.4,读请求比较多的情况下,可适当调大。
- MemStore占用RegionServer堆内存的比例,默认0.4,读请求比较多的情况下,可适当调大。