Hbase是一种分布式、可扩展、支持海量存储的NoSQL数据库。
基于Hadoop可分布式,基于HDFS可扩展,可存储数十亿行百万列海量数据。
HBase的数据虽然存储在HDFS上,且HDFS只支持追加写而不支持随机写,但HBase通过技术手段实现随机、实时读写。
HBase以追加的方式对旧数据进行覆盖,从而实现对文件的修改,保留了时间戳记录不同的时间版本。
RDBMS: 传统关系型数据库。
非关系型数据库:底层的物理存储结构以KV键值对的形式存储数据。
表:有行有列,且必须得有元数据用来描述行和列。
数据仓库:数仓并非仅作为数据的存储,而存储数据最终的目的是为了计算和分析。
RowKey是有序的,以字典序进行排列。
HBase不是以列进行划分的,而是以列族作为列的划分,每个列族有多个列字段,建表时可以不指明列字段,但必须指明列族,列字段可以在列族中随时添加。
Store是HBase中最小的一个存储单位,最终都是以StoreFile的形式存储在HDFS上,StoreFile的文件格式为Hfile。
HBase的表可以是稀疏表,允许某个Cell值为空,即不存在值,但不是Null。
Cell 是一个五位的K-V 。
Key(RowKey、Column Family、Column Qualifier、TimeStamp、Type)-Value。
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。
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中方便下次查询。
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。
将合并后的最终结果返回给客户端。
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。已经不可配了。
由于memstore每次刷写都会生成一个新的HFile,且同一个字段的不同版本和不同类型有可能会分布在不同的HFile中,因此查询时需要遍历所有的HFile。为了减少HFile的个数,以及清理掉过期和删除的数据,会进行StoreFile Compaction,Compaction分为两种。
默认情况下每个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。
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插入数值在Hbase看的时候数值会变成负数,反过来也相同。但自己插自己查都没问题。
原因在于两者的字节序列化方式不同,对于 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的表只能是外部表
索引的本质:空间换时间,创建索引提高了查询效率,但占用了空间且更新慢。
一级索引:RowKey
二级索引:就是给出了RowKey之外的列创建索引。根据索引创建新表,需要过滤条件过滤时创建二级索引,过滤是需要定位的,而查找只需要将包含的结果输出即可,如果不需要过滤只是查询一下,不用建立该字段的索引,而将字段包含进索引表即可。
实现原理:协处理器,当对原始表进行数据操作时,协处理器会在索引表进行相应的操作。
说明:Phoenix建二级索引的时候需要做很多的操作,所以需要在HBase中设置相关的设置,否则HBase不允许Phoenix进行相关操作。
二级索引分类:
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)即可。
理论:散列性、唯一性、长度(满足需求的情况下尽可能短)。
预分区:
分区设置:
手动设置与分区
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
预分区
每一个region维护着startRow与endRowKey,如果加入的数据符合某个region维护的rowKey范围,则该数据交给这个region维护。依照这个原则可以将数据所要投放的分区提前大致的规划好,以提高HBase性能。
RowKey设计
数据的唯一标识就是rowkey,那么这条数据存储于哪个分区,取决于rowkey处于哪个一个预分区的区间内,设计rowkey的主要目的就是让数据均匀的分布于所有的region中,在一定程度上防止数据倾斜。
RowKey 设计:
内存优化
HBase操作过程中需要大量的内存开销,毕竟Table是可以缓存在内存中的,但是不建议分配非常大的堆内存,因为GC过程持续太久会导致RegionServer处于长期不可用状态,通常为16-32G,如果因为框架占用内存过高导致系统内存不足,框架一样会被系统服务拖死。
基础优化