1.简述hbase
hbase是一种分布式、可扩展、支持海量数据存储的nosql数据库(基于列式存储)。
存储表大,稀疏(空列,不占存储),高可用(zookeeper)、高可靠(HDFS)、可伸缩的分布式数据库。
hbase的物理存储结构
hbase架构构成
Region
每一个region都是表的一部分,而每一个region又划分为多个store,store中存储的是该部分一个列族的信息。(一台机器放2-3个region)
storeFile
保存实际数据的物理文件,storeFile以HFile的形式存储在HDFS上。每个Store会有多个StoreFile(HFile),数据在每个storeFile中都是有序的。
HFile
一个列族存储在一个HFile(二进制格式文件)中。我们可以认为HBase是一个有序多维的Map,每一个rowkey,都对应多个列和其值。
HFile由多个Block数据块组成,并且有一个固定的结尾块。数据块中由一个Header和多个Key-Value的键值对组成。结尾的数据块中包含了数据相关的索引信息。
每个store保存一个列族,每个store由一个memstore、0至多个storefile组成,storefile以Hfile的格式存储在HDFS上。
blocksize大小问题
推荐数据块的大小设置为8KB至1MB。大的数据块比较适合顺序的查询(比如Scan),但不适合随机查询,每一次随机查询可能都需要你去解压缩一个大的数据块。小的数据块适合随机的查询,但是需要更多的内存来保存数据块的索引(Data Index),而且创建文件的时候也可能比较慢,因为在每个数据块的结尾我们都要把压缩的数据流Flush到文件中去(引起更多的Flush操作)。
MemStore
写缓存,由于HFile的数据要求是有序,所以数据会先存储在MemStore中,排序以后,等待到刷写时机刷写到HFile中,每次刷写都会形成一个新的HFile。
WAL
由于数据要经 MemStore 排序后才能刷写到 HFile,但把数据保存在内存中会有很高的概率导致数据丢失,为了解决这个问题,数据会先写在一个叫做 Write-Ahead logfile 的文件中,然后再写入 MemStore 中。所以在系统出现故障的时候,数据可以通过这个日志文件重建。
2.hbase架构组成作用
HDFS为hbase提供高可靠的底层存储支持;mapreduce为hbase提供高性能的计算能力;zookeeper为hbase提供稳定的服务和失败恢复机制;hive,pig为hbase提供高层语言支持,使得在hbase上进行数据统计变得简单;sqoop为hbase提供了RDBMS数据导入功能,方便数据的迁移。 hbase的逻辑结构
主从结构:
zookeeper
实现HMaster的高可用,每个子znode包含当前作为热备的HMaster信息。
记录META表(不会split)的位置,可从META表中获取存储数据的位置,完成读写功能。
HRegionServer的监控,活动HMaster对/hbase/rs路径下的znode注册监听。
Log Split管理,HMaster会在ZK上注册/hbase/splitlog临时节点,存储存活RegionServer与其应该处理的Region HLog的映射关系。RegionServer分配得到region,重放HLog。
Replication管理,主集群和从集群之间的数据同步,从而支持容灾和备份,保证数据的最终一致性,整个Replication的状态信息都储存在ZK的/hbase/replication中。
Hmaster
管理用户对Table表的DDL操作;
实现RegionServer的负载均衡;
实现故障转移,当有Region失效时,将实现对其进行转移恢复。
HRegionServer
维护Region,处理Region的IO请求
切分超过阈值的Region。
BlockCache 读缓存,memstore写缓存,加快读写速度
HLog记录数据变更信息,恢复数据用途。
3.hbase读写底层实现
写过程
1)Client 先访问 zookeeper,获取 hbase:meta 表位于哪个 Region Server。 2)访问对应的 Region Server,获取 hbase:meta 表,根据读请求的 namespace:table/rowkey, 查询出目标数据位于哪个 Region Server 中的哪个 Region 中。并将该 table 的 region 信息以 及 meta 表的位置信息缓存在客户端的 meta cache,方便下次访问。 3)与目标 Region Server 进行通讯; 4)将数据顺序写入(追加)到 WAL; 5)将数据写入对应的 MemStore,数据会在 MemStore 进行排序; 6)向客户端发送 ack; 7)等达到 MemStore 的刷写时机后,将数据刷写到 HFile
注意:Memstore达到阈值会刷写成HFile文件,当多个HFile文件达到一定大小,触发Compact操作,合并为一个HFile。
当HFile大小达到一定阈值,会将Region切分为两个,并由Hmaster分配到对应的HRegionServer,实现负载均衡。
读过程
1)Client 先访问 zookeeper,获取 hbase:meta 表位于哪个 Region Server。 2)访问对应的 Region Server,获取 hbase:meta 表,根据读请求的 namespace:table/rowkey, 查询出目标数据位于哪个 Region Server 中的哪个 Region 中。并将该 table 的 region 信息以 及 meta 表的位置信息缓存在客户端的 meta cache,方便下次访问。 3)与目标 Region Server 进行通讯; 4)分别在 Block Cache(读缓存),MemStore 和 Store File(HFile)中查询目标数据,并将 查到的所有数据进行合并。此处所有数据是指同一条数据的不同版本(time stamp)或者不 同的类型(Put/Delete)。 5) 将从文件中查询到的数据块(Block,HFile 数据存储单元,默认大小为 64KB)缓存到 Block Cache。 6)将合并后的最终结果返回给客户端。
MemStore Flush时机
当某个 memstroe 的大小达到hbase.hregion.memstore.flush.size(默认值 128M),其所在 region 的所有 memstore 都会刷写。 当 memstore 的大小达到了 hbase.hregion.memstore.flush.size(默认值 128M) * hbase.hregion.memstore.block.multiplier(默认值 4) 时,会阻止继续往该 memstore 写数据。
当 region server 中 memstore 的总大小达到 java_heapsize * hbase.regionserver.global.memstore.size(默认值 0.4) * hbase.regionserver.global.memstore.size.lower.limit(默认值 0.95), region 会按照其所有 memstore 的大小顺序(由大到小)依次进行刷写。直到 region server 中所有 memstore 的总大小减小到上述值以下。 当 region server 中 memstore 的总大小达到java_heapsize * hbase.regionserver.global.memstore.size(默认值 0.4)时,会阻止继续往所有的 memstore 写数据。
到达自动刷写的时间,也会触发 memstore flush。自动刷新的时间间隔由该属性进行配置 hbase.regionserver.optionalcacheflushinterval(默认 1 小时)。
当 WAL 文件的数量超过 hbase.regionserver.max.logs,region 会按照时间顺序依次进行刷写,直到 WAL 文件数量减小到hbase.regionserver.max.log 以下(该属性名已经废弃,现无需手动设置,最大值为 32)。
StoreFile Compaction
Minor Compaction:不会清理过期和删除数据
只选取一些小的、相邻的HFile将他们合并成一个更大的Hfile。当所合并的HFile个数大于3时,相当于Mager Compaction,会清理过期和输出数据。
Major Compaction:会清理过期和删除数据
会将一个 Store 下的所有的 HFile 合并成一个大 HFile,并且会清理掉过期 和删除的数据。
Region Split
当 1 个 region 中 的 某 个 Store 下所有 StoreFile 的 总 大 小 超 过 Min(R^2 * “hbase.hregion.memstore.flush.size”,hbase.hregion.max.filesize(10G)"),该 Region 就会进行拆分,其 中 R 为当前 Region Server 中属于该 Table 的个数
问题:可能出现热点问题(64,256…9.99G分区问题),因此需要预分区。
4.列族设计、预分区和rowkey设计
列族设计:
建表至少指定一个列族,但一般不超过三个,一般一个。flush是以region为单位,当某个column family在flush的时候,他临近的column family 也会因关联效应被触发flush,产生更多的io。
列族名字不宜过长,会冗余存储。
不能设置太多列族。当region下所有的storeFile中最大的storeFile大小超过阀值时将进行split。由于不同的列族会共享一个region,可能会出现一个A列族存储几千万行的数据,而另一个B列族则存储了几百行的问题,使得B列族数据不多却可能被分散在多个region中,导致扫描查询B列族的性能下降(产生更多的io和占有更多的cpu资源)。
预分区
默认Region Split机制,造成热点问题
防止数据一直往同一个region上写入,造成热点问题
Region Split会消耗宝贵的I/O资源。
每一个 region 维护着 StartRow 与 EndRow,如果加入的数据符合某个 Region 维护的 RowKey 范围,则该数据交给这个 Region 维护。那么依照这个原则,我们可以将数据所要投放的分区提前大致的规划好,创建多个空region,设计合理的rowkey,并确定其startkey和endkey,以提高 HBase 性能。
rowkey设计原则
唯一性原则
rowkey在设计上保持其唯一性。rowkey是按照字典顺序排序存储,设计rowkey应把经常读取的数据存储到一起,将最近可能访问的数据放到一块。
长度原则
因为字典顺序存储的原因,rowkey的长度应相等且与分区的划分长度保持一致或者大于,保证可读性。
rowkey是二进制码流,可以是任意字符,最长长度为64kb,实际为10-100bytes,以byte[] 形式保存,一般设计为定长。建议越短越好,不超过16字节(但应该根据实际情况满足实际需求)。因为数据的持久化文件HFile是按照KeyValue存储,如果rowkey过长,将会极大地影响HFile的存储效率;Block Catch读数据会将部分缓存内容到内存,而MemStore写数据也会将内存内容写到缓存中,如果rowkey过长,内存的有效利用率就会降低,系统不能缓存更多的数据,降低读写速度。另外根据操作系统都是64位,内存8字节对齐,控制在16字节,8字节的整数倍,利用了操作系统的最佳特性。
散列原则(应实现负载均衡和快速查询)
散列的同时应注意将想要同时读取的数据放到一起(将想要读取数据的信息在rowkey中提取出来计算分区,再加到前缀即可,具体根据业务而定)
加盐
若rowkey按照时间戳的方式递增,不要将时间放在二进制码的最前面,建议将rowkey的高位作为散列字段,由程序随机生成,低位放时间字段,使数据均衡分布在每个RegionServer,实现负载均衡。(若没有散列字段,很可能使得数据集中在一个RegionServer上,造成热点问题,降低查询效率)。加盐的前缀种类数量应该和region数量相同。
哈希
哈希使得同一行永远用同一个前缀加盐。可以达到负载均衡的目的,并使得读是可以预测的。
反转
反转固定长度或者数字格式的rowkey。使得rowkey经常改变的部分放在最前面。(牺牲了有序性)比如手机号的反转,可以避免了例如手机号那样比较固定开头导致热点的问题。
时间戳反转
可以快速的获取数据的最近版本,使得反转的时间戳作为rowkey的一部分对这个问题十分有用。可以将Long。Max_Value-timestamp追加到rowkey的末尾,比如【key】【reverse_timestamp】。因为rowkey的有序性,就会将最后录入的数据放到第一条进行记录。
|