1.架构
逻辑结构
行 row key+多个cloum
存储结构
每列一行列式存储,存储的是列信息. (Rowkey famili info ts type value (按照时间戳 返回最大的数据 cell))
职责架构
- HMaster: 负责 DDL 创建或删除tables,同一时间只能有一个 active 状态的 master 存在,其余 standby;
- Zookeeper: 判定 HMaster 的状态,谁先创建临时节点谁就激活, 记录 Meta Table 的具体位置等功能;
- Region: 一张 BigTable 的一个分片(Shard),其包含着一个 key space 记录着 key 的开始和结束;
- WAL: 预写日志,持久化且顺序存储,一个 RegionServer 维护一套 WAL;(Eventually Consistency 的保证)
- RegionServer: 一个 RegionServer 中可维护多个 region,一个 region 里包含多个 MemStore(也就是逻辑上的列族,column family) 以及零个或多个 HFiles;
- MemStore: 对应一个 BigTable 的 Column Family,存在于文件缓存中,拥有文件句柄;
- BlockCache: LRU 读缓存,存于内存;(rowkey –> row);
- HFiles: 从MemStore Flush出来的文件,本身是持久化的,存储于 HDFS 的 DataNode 之中,每次Flush生成一个新的 HFile 文件,文件包含有序的键值对序列。后续会介绍 HFile 的数据结构。
2.写流程
LSM树的索引结构(Log-Structured Merge-Tree) 目的是为了磁盘顺序写
内存部分为ConcurrentSkipListMap (有序,后续HFile文件合并过程为多路归并)
随着不断写入,一旦内存占用超过一定的阈值时,就把内存部分的数据导出(flush, 过程生成快照),形成一个有序的数据文件,存储在磁盘上.(多文件会影响读取性能,所有有后续的合并过程)
写wal, 内存,然后定时落地
- client request —> 写WAL(顺序存储速度快,存于磁盘) —> ack
- WAL —> MemStore —> flush to HFiles
HBase 中每个 column family 对应一个 MemStore,对应多个 HFiles,也就是包含了多个 autual cells或者 KeyValue 实例,这些KeyValue 是以有序的方式组织成 HFiles 的,顺序且一次性写入,每次 flush 都会生成一个新的 HFile,非常快速。(这里的 CF 是 column family),关于 HFile 的拆分和合并,后续会有交代
具体实现流程:
写内存
wal同步
成功->提交
不成功->回滚
3.flush流程
HFiles 数据结构(重点)
HFile逻辑结构
HFile V2的逻辑结构如下图所示:
文件主要分为四个部分:Scanned block section,Non-scanned block section,Opening-time data section和Trailer。
Scanned block section:顾名思义,表示顺序扫描HFile时所有的数据块将会被读取,包括Leaf Index Block和Bloom Block。
Non-scanned block section:表示在HFile顺序扫描的时候数据不会被读取,主要包括Meta Block和Intermediate Level Data Index Blocks两部分。
Load-on-open-section:这部分数据在HBase的region server启动时,需要加载到内存中。包括FileInfo、Bloom filter block、data block index和meta block index。
Trailer:这部分主要记录了HFile的基本信息、各个部分的偏移值和寻址信息。
更多参考:http://hbasefly.com/2016/03/25/hbase-hfile/
4.读流程
读流程:
- client 从 zk 里拿 Meta table的访问地址并访问 Meta table;
- client 依据 Meta table 查到 row key想访问的那一个 region server A,并且将其meta缓存起来;
- 从这个 region server A 中获取到 row key 对应的 行(Row);
- 之后的查询都是从 client 缓存读取 meta 信息从对应的 region server 查询;(如果缓存中查询不到对应的数据那么将从第一步重新开始)
合并读:
- 先从BlockCache(读缓存)中找对应的 row;
- 如果缓存里找不到,那就去查询 MemStore(写缓存)找对应的最近的写数据;
- 如果两个地方都没用,那么就会根据 BlockCache 中的 B+ Tree Index(mentioned above)以及 Bloom Filter找 HFile 里的数据;
- 两个缓存无法命中且大量 HFile 未合并的时候,将有可能对很多的 HFiles 进行读操作(这叫 读放大(Read Amplification)。
HBase的Get操作就是通过运用低成本高效率的布隆过滤器来过滤大量无效数据块的,从而节省大量磁盘IO。
5.删除流程
Flush (同个文件)和 compact (major)
删除标记
Flush不会
compact会
6.Split流程(Region拆分)
预分区、不建议过多列簇(速度不一致,小文件)
7.Compact流程
Minor compact只是进行文件merge操作,
而Major compact除了做文件Merge操作,还会将其中的delete项删除。
hbase为了防止小文件(被刷到磁盘的menstore)过多,以保证保证查询效率,hbase需要在必要的时候将这些小的store file合并成相对较大的store file,这个过程就称之为compaction。在hbase中,主要存在两种类型的compaction:minor compaction和major compaction。
major compaction 的功能是将所有的store file合并成一个,触发major compaction的可能条件有:major_compact 命令、majorCompact() API、region server自动运行(相关参数:hbase.hregion.majoucompaction 默认为24 小时、hbase.hregion.majorcompaction.jetter 默认值为0.2 防止region server 在同一时间进行major compaction)。hbase.hregion.majorcompaction.jetter参数的作用是:对参数hbase.hregion.majoucompaction 规定的值起到浮动的作用,假如两个参数都为默认值24和0,2,那么major compact最终使用的数值为:19.2~28.8 这个范围。
minor compaction的运行机制要复杂一些,它由一下几个参数共同决定:
hbase.hstore.compaction.min :默认值为 3,表示至少需要三个满足条件的store file时,minor compaction才会启动
hbase.hstore.compaction.max 默认值为10,表示一次minor compaction中最多选取10个store file
hbase.hstore.compaction.min.size 表示文件大小小于该值的store file 一定会加入到minor compaction的store file中
hbase.hstore.compaction.max.size 表示文件大小大于该值的store file 一定会被minor compaction排除
hbase.hstore.compaction.ratio 将store file 按照文件年龄排序(older to younger),minor compaction总是从older store file开始选择,如果该文件的size 小于它后面hbase.hstore.compaction.max 个store file size 之和乘以 该ratio,则该store file 也将加入到minor compaction 中。
如果对minor compaction过程还是不了解,可以去看hbase中关于minor compaction 的源码,或者:http://www.linuxidc.com/Linux/2013-05/83675.htm
优化: rowkey设计 预分区
优化
写入优化
写也是Hbase常有的操作之一,并且Hbase在写入操作上有着其他NoSQL无法比拟的优势,下面讲如何优化写入操作
关闭写WAL日志
一般为了保证系统的高可用性,WAL日志默认是开启状态,WAL主要用于灾难恢复的,如果应用可以容忍一定的数据丢失风险,可以在写数据的时候,关闭写WAL。
风险: 当RegionServer宕机时,写入的数据出现丢失,且无法恢复。
设置AutoFlush
Htable有一个属性是AutoFlush,该属性用于支持客户端的批量更新,默认是true,当客户端每收到一条数据,立刻发送到服务端,如果设置为false,当客户端提交put请求时候,先将该请求在客户端缓存,到达阈值的时候或者执行hbase.flushcommits(),才向RegionServer提交请求。
风险 在请求未发送到RegionServer之前客户端崩溃,数据也会丢失
table.setAutoFlush(false);
table.setWriteBufferSize( 12 * 1024 * 1024 );
预创建Region
一般表刚开始只有一个Region,插入该表的数据都会保存在此Region中,插入该表的所有数据都会保存在该Region中,当到达一定的阈值时,才发生分裂。
这样开始时刻针对该表的写操作都集中在某台服务器上,造成这台服务器的压力很紧张,同时对整个集群资源的浪费。
建议刚开始的时候预创建Region,可以使用Hbase自带的RegionSplitter
延迟日志flush
默认写入操作,首先写入WAL,并且在1S内写入HDFS,这个时间默认是1S,可以通过参数配置
hbase.regionserver.optionallogflushinterval可以配置大一点的值,比如5s,这段时间数据会保留在内存中,直到RegionServer周期性的执行flush操作。
参考:http://www.linuxidc.com/Linux/2013-05/83674.htm
参考:https://www.bilibili.com/video/av65591724?p=1
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 951488791@qq.com