HBase使用LSM(Log-Structured Merge Tree日志结构合并树)树,用于为那些长期具有很高记录更新(插入或删除)频率的文件提供低成本的索引机制。LSM-Tree通过使用某种算法对索引变更进行延迟及批量处理,并通过一种类似于归并排序的方式联合使用一个基于内存的组件和一个或多个磁盘组件。
与传统访问方式(比如B+树)相比,该算法大大减少了磁盘磁臂的移动次数,同时还会提高那些使用传统访问方式进行插入时,磁盘磁臂开销(寻道+转动)远大于存储空间花费的情况的性价比。但是,对于那些需要立即响应的查找操作来说,某些情况下,它也会损失一些IO效率,因此LSM-Tree最适用于那些索引插入比查找操作更常见的情况。比如,对于历史记录表和日志文件来说,就属于这种情况。
B+树与LSM-Tree树本质区别
他们本质不同点在于他们使用现代硬盘的方式,尤其是磁盘。从磁盘使用方面讲,有两种不同的数据库范式:一种是寻道,另一种是传输。RDBMS是寻道型,它需要随机读写数据。
LSM-Tree则属于传输型,它的原理是把一颗大树拆分成N棵小树, 它首先写入到内存中(内存没有寻道速度的问题,随机写的性能得到大幅提升),在内存中构建一颗有序小树,随着小树越来越大,内存的小树会flush到磁盘上。当读时,由于不知道数据在哪棵小树上,因此必须遍历所有的小树,但在每颗小树内部数据是有序的。
LSM-Tree工作在磁盘传输速率的级别上,因为它会使用日志文件和一个内存存储结构把随机写操作转换为顺序写。读操作与写操作是独立的,这样这两种操作之间就不会产生竞争。
以上就是LSM树最本质的原理,有了原理,再看具体的技术就很简单了。
1)首先说说为什么要有WAL(Write Ahead Log),很简单,因为数据是先写到内存中,如果断电,内存中的数据会丢失,因此为了保护内存中的数据,需要在磁盘上先记录logfile,当内存中的数据flush到磁盘上时,就可以抛弃相应的Logfile(对于存储系统,最简单的就是把记录直接写到记录文件的末尾,这样的做法写效率是最高的,WAL日志是以append形式插入,所以速度非常快)。
2)什么是memstore, storefile?很简单,上面说过,LSM树就是一堆小树,在内存中的小树即memstore,每次flush,内存中的memstore变成磁盘上一个新的storefile。
3)为什么会有compact?很简单,随着小树越来越多,读的性能会越来越差,因此需要在适当的时候,对磁盘中的小树进行merge,多棵小树变成一颗大树。
客户端Client:
1.是整个Hbase的集群访问入口;
2.使用Hbase RPC机制与HMaster和HRegionServer通信;
3.与HMaster通信进行管理表操作;
4.与RegionServer进行数据读写操作;
5.寻址访问Zookeeper,数据读写访问RegionServer
Zookeeper:
1.保证任何时候,仅有一个HMaster;
2.实时监控HRegion Server的上线和下线信息,并实时通知HMaster,让HMaster知道哪些HRegionServere是活的及HRegionServer所在位置,然后管理HRegionServer;
3.存储Hbase的table元数据;
HMaster:
1.HMaster没有单点问题,可以启多个HMaster,同Zookeeper的MasterElection机制保证总有一个Master在运行,主要负责Table和Region的管理工作;
2.管理用户对表的创建、删除等操作;
3.管理HRegionServer的负载均衡,调整Region分布;
4.Region split,负载重新分配Region;
HMaster仅仅维护table和region的元数据信息,负载很低;
HRegionServer:
1.维护Region,处理这些HRegion的IO请求,向HDFS文件系统写数据;
2.过大的Region,执行spilt操作;
3.Client访问hbase上的数据过程,不需要master参与;
Scanner在server结构:
scanner.next的关键步骤:
Hbase每次Put都会指定一个唯一ID,该ID是Region级递增的。每个Region得MVCC维护两个point:
没有数据写入的时候,二者的位置是一样的,当有数据写入的时候,readpoint要比writepoint小,只有readpoint之前的数据能够读取到(只要成功写入HLog和Memstore的数据能够读取到,无序写入HFile中)
在网络不稳定的情况下,当客户端发送rpc请求给regionserver服务器的时候,如果服务器处理时间过长导致超时,会出现服务器处理完毕,而无法及时通知客户端,导致客户端重新发送写入请求,即多次发送append,会造成数据多次添加。为了防止类似的现象,Hbase引入了Nonce机制,ServerNonceManager负责管理该RegionServer的nonce。
客户端每次申请以及重复申请会使用同一个nonce,发送到服务端之后,服务端会判断该nonce是否存在,如果不存在则可以放心执行,否则会根据当前的nonce进行相应的回调处理:
所有数据一般都保存在hadoop分布式系统上,用户通过访问region服务获取数据,一个物理节点一般只能运行一个region服务器,主要组成部分为:HLog(WAL)和多个region;一个HStore包含:一个MemStore和多个HFile
Hbase底层存储的架构图
Hbase的数据最终是以HFile的形式存储在HDFS中的,HBase中HFile有着对应格式。一次memstore的flush会产生一个StoreFile,一次Compact会导致多个StoreFile合并成一个。Hbase提供的读/写StoreFile的reader和writer工厂类,可以直接从StoreFile文件读取数据,从而绕过Hbase提供Scan、Get、Put等api
StoreFile以Hfile格式保存在HDFS上 ,内容是由一个个block组成的,按照block类型可分为:
1. datablock存放的key-value数据,一般一个datablock大小默认为64kb;
2. data index block,其中存放的是datablock的index,索引可以是多级索引,中间索引,叶子索引一般分布在HFile文件当中;
3. bloom filter block,保持了bloom过滤的值;
4. meta data block,meta data block有多个,且连续分布;
5. meta data index 顾名思义
6. file-info block,其中记录了关于文件的一些信息:HFile中最大的key,平均Key长度,HFile创建时间戳,data block使用的编码方式等;
7. trailer block,每个HFile文件都会有的,对于不同版本的HFile的trailer长度可能不一样,但是同一版本的所有的HFile trailer的长度都是一样长的
HFile里面的每个KeyValue对就是一个简单的byte数组:
1)KeyLength和ValueLength:两个固定的长度,分别代表着Key和Value的长度;
2)Key部分:RowLength是固定长度的数值,表示Rowkey的长度,Row就是RowKey,ColumnFamilyLength是固定长度的数值,表示Family的长度,接着就是ColumnFamily,再接着是Qualifier,然后是两个固定长度的数值,表示TimeStamp 和KeyType(Put/Delete)
3)Value 部分就没有这么复杂结构,是纯粹的二进制数组
HFile读写,都可以通过org.apache.hadoop.hbase.io.hfile.HFile这类类提供的一些静态方法来实现
参考链接:https://blog.csdn.net/nuisthou/article/details/49250435
https://www.jianshu.com/p/e2bbf23f1ba2