这边笔记主要作用就是让自己更好的深入理解HBase的运行原理,当遇到问题时能高效快捷的锁定并解决问题。
原理剖析我们有一下几个目标:
1、HBase的存储模式
1.1行式存储与列式存储的介绍
行式存储与列式存储特点:
行式存储:
行式存储维护大量的索引,存储成本比较高,不能做到线性扩展,对于随机读的效率非常高。最大的特点就是对事务的支持能力非常好。
列式存储:
根据同一列数据的相似性原理,对数据进行压缩,存储成本比较低。列式存储每一列的数据都是分开存储的,我们在查找不同列的数据的时候,可以利用并行查询的原理,高效的查询多列的数据。而行式存储是将不同列的数据存储在同一行上的,所以并行查询是不可能实现的。
行式存储与列式存储的应用环境:
列式存储应用场景:
》对于单列或者相对少的列,获取频率较高,推荐列式存储。因为如果你只要某列的数据,那么只需要找某一列存储位置即可。
》如果是对多列查询,使用并行处理的查询也是效率非常高的,这样的情况也可以使用列式存储。
》特别是对于大数据的环境,利于数据压缩和扩展,这样的存储场景比较多的话,同样第一个应该考虑的是列式存储。
》如果你的事务使用率不高、读取场景的频率不高、数据量非常大这样的场景也需要列式存储。
》如果你随机更新某行频率不高的话,列式存储也是不错的选择。
行式存储应用场景:
行式存储实现了一个关系型数据库的解决方案,如果你的表与表之间有很强的关联性,且数据量不大的话,使用行式存储是一个比较好的选择,可以很容易关联查询数据。
》行式存储最大的优势是联机事务处理能力,如果你要存储消费、转账的记录,你就需要强事务关联性。
》但是由于行式存储线性扩展性不高,需要保证我们的数据量存储不能特别大,如果你的数据只有千万量级的话,考虑使用行式存储。
》简单说,行式存储适合事务型应用场景,列式存储更适合分析型应用场景。
hbase的列族(列簇)式的存储
列族的存储概念:多个数据列的组合,hbase表中的每个列都归属于某个列族。列族是表的schema的一部分,而列不是,我们在创建一张hbase表的时候,必须要给出列族的名称,但不需要给出列的名称。列名一般都以列族名称作为前缀,访问控制、磁盘、内存的使用统计都是在列族层面进行的,hbase准确的说是列族数据库,而不是列数据库。
列数据的属性:
默认情况下,一列数据可以保存三个版本,特别对于聊天这类应用,特别方便。比如标识信息是已读的还是未读的。
2、HBase的数据表解析
Name:是数据表的列族名,在建表的时候必须要提供的,且以后在hbase表的工作过程中,最好不要修改。因为很多属性都是基于列族进行配置的,如果你修改那么的话,会导致表的结构发生变化。
Version:是时间版本的个数,这里的b列族就显式声明了3个时间版本。
Replication_scope:是hbase0.9版本的时候,加入了一个重要机制,要多副本机制,他的数据完整性得到进一步保障。当请求发送给master的时候,log的日志放入hdfs的同时会进入到Replication的队列里面,由Slave通过zookeeper去获取信息写入到slave节点的表中,master和slave是主从集群机制。
Compression:数据压缩格式的配置,其中列族b和o都是把压缩格式设置为了SNAPPY,其实SNAPPY就是一种压缩格式。
压缩特性:就是使用cpu资源换区磁盘资源,对读写性能并不会有太大影响,目前hbase支持三种压缩格式,GZip、LZO、SNAPPY
HBase数据存储目录
我们在搭建HBase时,需要指定其数据存储目录,HBase会在指定的目录中构造需要的目录结构,之后我们创建数据表以及存放数据时,也会存放在对应的目录中,我们下面就介绍下,hbase数据存储目录结构。
我这里的目录结构是存放在hdfs上的,如下图,系统级别的目录如下:
.tmp:当对表做创建或删除操作时,会将表移动到.tmp目录下面,然后再去做处理操作他是一个临时交换的表目录,临时存放一些当前需要修改的数据结构。
WALs:它其实是一个预写日志,它是被HLog实例管理的WAL文件,这里可以简单的理解为hbase数据库系统的操作日志文件。
archive:它存储表的归档和快照,hbase在做分隔或者是合并操作完成之后,会将hfile文件移动到archive目录中,然后将之前的hfile删除掉。这个目录是有master中的定时任务定期去处理。
corrupt:这个目录用于存放损坏的日志文件,他一般是空的,如果他不为空代表我们的系统出现了些问题,有些日志文件可能被损坏了。
data:它是hbase存储数据的核心目录,系统表和用户表数据都存储在这里,他是非常重要的目录
hbase.id:hbase在运行起来之后,就会产生集群中的唯一id,是用来标识hbase进程用的。
hbase.version:它表明了hbase的文件格式、版本信息,其实就是表明了hfile的文件版本信息。
oldWALs:当WALs中的log文件被持久化后,日志就不被需要了,就会被移动到oldWALs目录中等待删除。
用户级别的目录:
HBase的元信息表
Row Key:其实Meta Table也是一个普通的hbase数据表,他的结构跟普通的表结构是差不多的。不过他的Row Key和Value有特殊意义:Row Key它包含三个部分,对三个部分进行组合得到一个Row Key,其中table为region table、region Start key、以及region Time,这里的time为存储数据的最早时间。由于Mata Table中包含Start key,所以它很容易找到对应的region,最后找到对应的数据。
Value:value最终指向的是Region Server,它其实保存的是Region Server的地址。表中最主要的列族是info,列族中包含三个主要的列,都是属于value的,三个属性列中第一个属性列是regioninfo,第二个属性列是server,包含了Region Server的服务器地址和端口,第三个属性列是Start code,它包含了Region Server的start time。
Meta Table表的值,会发生变化吗?
答案是可定的,它的值是会发生变化的,当region进行分割、合并、禁用、启用、删除等会导致region重新分配,或者region server服务节点挂掉,也会导致region重新分配,这时候meta表的数据会及时更新,这个动作是由master发起的,正是由于它能够及时的更新,才能保证客户端根据mate表访问到的数据是存在的。其实理解了meta table就理解了hbase存储数据的机制,其实mate Table就是hbase存取数据的第一级索引,是最重要的系统表。
3、HBase的存储设计(核心)
》什么是LSM树?
他的概念是日志结构合并树,是由两个或两个以上存储数据的结构组成的,每一个数据结构各自对应自己的存储介质。但从概念上不太好理解,我们从LSM树的简易模型来介绍,相对好理解些。
LSM树的简易模型是由两个树状结构组成,这两个树分别是C0和C1。C0比较小,并且全部存储于内存之中,而C1则存储于磁盘之上,一条新的记录先从C0中插入,插入时会判断C0中的内存阀值,如果超出了阀值,C0中的某些数据片段会被迁移并合并到C1的树上。由于合并排序算法是批量的且是顺序存储,所以说速度是非常快的。当然这只是简易模型,在实际应用时,是多余两层树结构的。
》LSM思想在HBase中的实现,C0级用户数据到达了region server为了加速随机写,region server不会将数据直接刷写到硬盘里面,是写入log和memorye,写日志的原因是因为内存中的数据是不稳定的,写入内存中的数据在机器宕机或重启的情况下,就会丢失,所以写入log是保证高可用。
》当C0层数据到达阈值时,会被迁移至C1层数据,并将C0层的多个小文件合并成多个大文件,当C1层的数据达到阀值时,异步线程启动,多路合并多个文件刷到磁盘当中,形成一个最终的大文件。这就是HBase借鉴的LSM存储思想,从而达到了高效的存储性能,后续我们会继续详细介绍每个层的实现模块。
HBase存储设计模块简介
Region:一个RegionServer能包含很多个Region,Region是存储用户数据的最小单元,每个Region中包含的数据都是互斥的,存储用户各个行的数据。
Stroe:Region里包含了很多Store,Store对应于HBase表的列族,我们定义HBase表时,为表定义了多少个列族,那么每个Region中就包含了多少个Stroe。
HLog:一个RegionServer中包含唯一的一个HLog实例,HLog用于实现预写日志,用户存储的数据最先会保存在这里,目的就是实现整个系统的高可用,在系统宕机或崩溃的时候回放日志,恢复到原始的状态。
MemStore:Store中包含了一个MemStore,它是内存式的数据结构,用户数据进入Region之后,先会刷写到MemStore中,当MemStore的数据装满之后,会把数据刷写到StoreFile中。StoreFile又会封装成HFile,最后再去往HDFS中真正刷写成HFile文件。MemStore其实是一个Store的内存缓冲服务,用户数据在写入到HLog之后第二步会写入到MemStore中。
Hlog和MemStore就构成了LSM的第一级结构Level0
StoreFile:它其实是在MemStore数据装满以后,由MemStore刷写出来的文件,他是HFile的简单封装。
HFile:它是HBase存储数据的一个组织形式,我们所保存的数据,在HBase中的最终存储格式就是HFile。我们可以简单的认为我们的数据都存储在HFile中。
StoreFile和HFile这两个模块构成LSM中的第二级结构Level1,他实现了内存数据的持久化。
HBase Region解析
》什么是Region?
1、每一个Region都会存储于一个确定的RegionServer上,不会出现同一个region在两个RegionServer上,他们之间的数据是互斥的关系。HBase表在RowKey的方向上分配了多个Region,他是hbase分布式存储和负载均衡的最小单元。Region按照大小进行切分,每个表一行只有一个Region,所以说一行数据不可能分散在多个Region上面。当不断的插入,导致Region的存储或者是某个列族到达一定预值之后,Region会被水平拆分为两个Region,Region在RegionServer运行过程中,可能会出现移动,这是Master的负载均衡策略。或者某一个RegionServer挂了或宕机了,存储于它之上的所有Region都会把Master安排到其他的RegionServer上面。
2、每个Region都有三个重要的属性信息来标识他们,第一个TableName(表的名称)第二个是Start_RowKey(表示从哪个rowkey开始的),所有的rowkey都是排序的,所以根据rowkey容易检索数据。第三个是CreateTime(表示Region中最早的一条数据插入的时间)。
》Region有哪些特点?
1、Region是HBase中分布式存储和负载均衡的最小单元,不同的Region分布到不同的RegionServer上,但并不是存储的最小单元,存储的最小单元是HFile。
2、Region的数量问题,如果RegionServer中Region的数量过多就会导致性能下降,region的数量太少就会妨碍可扩展性,降低并行能力,导致机器压力不够分散。所以有个设计原则,Region的数量一定不能低于集群中节点的数量。
3、Region的拆分策略,Region的拆分操作是不可见的,因为Master不会参与其中,那么RegionServer拆分Region的步骤是什么呢?其实RegionServer是先将要拆分的Region下线,然后对他进行拆分,然后将子Region加入到Meta Table表的元信息中。之后再将他们加入到原本的RegionServer上面。最后同步到Master上面。
总结:Region实现了HBase的负载均衡,所以我们在对表进行设计,特别是对RowKey的设计上,要花一点功夫,尽量让数据分散到多台RegionServer上,避免某个节点成为热点,这样才能高效的发挥HBase的性能。
HBase HFile 解析
Store:一个Store包含了一个MemStroe和0个或多个StoreFile。其中MemStroe是一个内存数据结构,StoreFile是文件系统级别的数据结构。Store是由Region去管理的,用于维护列族的数据。对于一个HBase的表,设计了几个列族,那么对于任何一个Region而言就会有几个Store。这也是HBase被称为列族式数据库的原因。他会把一起访问的数据放在一个Stroe里面。同时HBase会以Store大小来判断,是否需要去切分Region。
MemStore:它是一个内存数据结构,它保存修改的数据,即用户put、delete等请求,因为将数据存储在内存之中,所以需要有预值控制,到达预值,就刷写到文件系统。默认这个预值是64M,为了保证读写线程不被阻塞到,HBase提供了专门的Flush异步线程,去实现数据的刷写。
StoreFile:它是MemStore中的内存数据刷写到文件之后形成的StoreFile,它底层是以HFile的格式保存的。
HFile:HFile文件他是HBase存储数据文件的最基本的存储形式,他底层是HDFS的二进制格式文件。他是用户数据的实际载体。它存储着Key-Value这样的数据。
》Scanned block section:它是在顺序扫描HFile的时候,这个部分的所有数据块将会被读取,用户数据存储于这个部分之中。其实用户数据确切的说就存储在Data Block中
》Nonscanned block section:它是在顺序扫描HFile的时候,这些数据块不会被读取到,主要包含一些Meta block(元数据块)它是在访问用户数据的时候不会被扫描到的。
》Load-on-open section:这部分数据在HBase Region Server启动的时候,会加载到内存中,主要是HFile元数据相关的信息。
》Trailer:这部分记录了HFile的基本信息,各个部分的偏移量,寻址信息,可以简单的理解它也是HFile元数据的一部分。
HBase Data Block
》Data block是HBase中数据存储的最基本单元,它实际存储用户的数据结构,他实际上存储着key-value数据,key-value数据结构是HBase中的核心。每个数据都是以key-value格式在HBase中存储的。接下来我们详细的介绍下Data block的key-value结构。如下图:
》上图可知,Data Block每一个数据都是一个key-value,而每一个key-value包含四个部分
Key Length:key的长度,Value Length:value的长度,他们两个主要起到控制偏移的作用。最后再保存key和对应的value。
我们发现key也有多部分组成,分别是:
Row Length:行键的长度、Row:行键、ColumenFamily Length:列族的长度、ColumnFamily:列族、ColumnQualifier Length:列描述符的长度(这些所有以length命名的数据模块其实是起到偏移控制的作用)、ColumnQualifier:列描述符、Timestamp:时间戳即版本、KeyType:墓碑标记,包含put、delete、deleteColumn、deleteFamily。什么意思呢?
》我们之前介绍过,HBase的插入性能非常高,但是从来没有介绍过,HBase删除数据的时候是怎么做的。这里卖个关子,问下:当你执行delete的时候,HBase会将存储的数据立即删除吗?答案:是不会的,这里有个非常重要的细节就是,内存数据一旦刷写成HFile文件之后,数据将不可做修改,也就是说,当用户存储进来的数据被保存成HFile之后都不能被修改。
》那这时候如果我么确实要删除数据怎么办呢?
这时候KeyType就起作用了,用户正常插入数据使用put,用户删除数据使用delete,delete代表删除整行数据,deleteColumn代表删除对应的列数据,deleteFamily代表删除一个列族之下的数据。其实这就是给数据打上一个标记,这里叫做墓碑标记,标识着这个数据被删除掉了。当我们再对HBase这张表进行扫描的时候,这些被标记的列或行就不会被扫描到了。在后续的运行过程中,HBase会在某个时间点,定时清理掉这些被标记的数据,并不是在执行delete语句的时候,就立即删除。
HBase WAL解析
》WAL最重要的功能就是灾难恢复,类似于mysql的binlog,它记录的所有的数据改动,一旦服务器崩溃,通过重放log,可以恢复到崩溃之前的状态。所以当对Region Server写入数据的时候,如果写入WAL失败,整个操作将被认为是失败的。由上图可以看出,所有的用户写入数据都会经过RegionServer的log syncer,然后写入到Hlog之中,然后在写入到Region之中。
》WAL解决了hbase的什么问题?
1、它解决了HBase的高可用(HA)的问题,就是说当系统故障的时候,可以通过日志重放,恢复到故障前的状态。
2、它实现了HBase的replaction,远程备份功能。具体实现是,当客户端触发一个对数据改动的操作,比如put、delete操作,这些修改操作被封装在一个key-value实例里面,然后通过rpc调用发送给RegionServer,RegionServer接收到调用后,先去写log,再去写Region。从而实现这个远程备份的方案。可以简单的理解为,远程备份其实就是讲log同步到备份的目标机器上去。
》HLog的解析
WAL是通过HLog模块实现的,我们需要对HLog的内在原理做个介绍,便于我们理解HBase日志运行原理。
》HLog是什么?
HLog是实现WAL的类,一个Region Server对应唯一一个HLog的实例,当Region初始化的时候,HLog会作为一个参数,传递给HRegion的构造函数,这样HRegion就获取到了HLog的引用,可以实现日志打印。
》HLog的工作步骤
HLog最核心的是调用它的append方法,完成对日志记录的追加写入。出于对性能的考虑,put、delete、increatement有一个开关函数setWriteToWAL(boolean flag),传递的是boolean类型的值,当设置为false时,则会禁用WAL。HLog通过序列化的number去追踪数据的改变。
》HLogKey
》HLogSyncer
HLogSyncer是日志同步刷写类,数据是以keyvalue的形式到达RegionServer,写入WAL的操作默认是直接写入到文件系统里面的,有些时候为了性能,会暂时将日志保存在内存中,这时候就需要有个专门负责将这部分日志刷写到磁盘的机制,而HLogSyncer就担此重任。刷写日志有两种方式,一种是达到内存预值时去刷写,另一种是定时去刷写。
》HLogRoller
log的滚动间隔时间可以通过配置文件设置,默认是一个小时滚动一个新文件,所以HBase系统运行一段时间之后,会产生一大堆的log文件需要维护,HLogRooller作为后台线程去运行。
它主要实现了两个功能:
第一、特定的时间去滚动日志,形成新的日志文件,避免单个日志文件过大。
第二、根据HLog的序列化number对比已经持久化的HFile的序列号,删除旧的不需要的日志。
HBase日志体系,总体的模块比较多,但并不复杂,而且实现了HBase非常多的重要特性,这在以后我们自己在设计系统的时候也可以好好借鉴一下。不仅仅追求系统的高效,还要保证系统的高可用。
HBase Compaction(合并)解析
》什么是Compaction?
Commpaction会从一个Region的Store中,选择一些HFile文件进行合并,合并的原理很简单。先从这些待合并的文件中读出key-value数据,再按照由小到大排列后写入到一个新的HFile文件中去。之后这个新的文件就会取代之前的文件对外提供服务。
》为什么需要Compaction操作?
随着系统不停的刷写,会导致存储目录中保存过多的存储目录文件,文件太多会导致维护困难而且不利于查找,会大大降低数据查询的效率。所以需要有这样一种文件合并机制,将数据文件由多变少。
假如,你有十个数据文件,如果你想从这十个文件中找出一条数据,那么你需要依次对这10个文件进行打开-关闭的操作。每次对文件进行io的时候是效率非常低的,尤其是打开这个动作非常耗费资源。当我们合并成一个文件时,那么我们就可以节省下来其他9次打开的资源浪费以及时间消耗,从而达到高效查找的目的。
》Compaction的分类
第一类:MinorCompaction
这种合并类型是选取一些小的、相邻的StoreFile,将他们合并成多个更大一些的StoreFile。
第二类:MajorCompaction
1、这种合并类型是将所有的StoreFile合并成一个较大的StoreFile,也就是你Store中不论有多少个StoreFile都无所谓,他只将所有的StoreFile合并成一个大的StoreFile。
2、这个过程还会清理三类没有意义的数据,
第一类:是被删除的数据即KeyType类型为delete类型的数据。
第二类:TTL过期的数据,也将会被删除。
第三类:版本号超过设定版本号的数据,也将会被删除。比如说当你的列族设定版本个数是3,那么当你第四次存放数据的时候,也会保存在系统中,但是当大合并到来的时候,你最早存入的那个版本的数据将会被删除。
一般情况下,MajorCompaction的合并操作耗时比较长,会消耗大量的系统资源,对上层业务有比较大的影响。因此线上业务都会讲自动触发的MajorCompaction功能关闭掉,改为手动在业务低峰期的时候触发。同事这也是一个非常大的系统优化点。
》Compaction的触发时机
触发compaction的因素很多,最常见的因素有三种:
第一种:MemStore Flush触发
Compaction操作源头就是来自于Flush操作,也就是内存中的数据Flush到硬盘上。MemStore Flush会产生HFile文件,文件越来越多就需要Compaction。因此在每次执行完Flush操作之后,都会对当前Store中的文件进行判断,一旦文件的数量超过预值就会触发一个Compaction。这里需要注意的是,Compaction都是以Store为单位进行的。而在Flush触发条件下,这个Region的所有Store都会执行Compaction,所以会在短时间内执行很多次Compaction。
第二种:Compaction checker(后台线程周期性的检查)触发
它会定期的触发检查,当前store是否需要执行compaction,它和Flush有点不同的是,这个线程会优先检查文件数是否大于预值,一旦大于就会触发compaction。如果不满足他就会接着检查是否满足MajorCompaction大合并的条件。简单来说,如果当前Store中HFile的最早更新时间,早于某个值,这个值就叫Mc-time,到了就会触发大合并。HBase通过Mc-time这种机制来定期删除过期数据,Mc-time是个浮动值默认取值区间是
[7-7*0.2,7+7*0.2],七天时间进行一次大合并。
7对应的配置属性:hbase.hregion.majorcompaction,如果想要禁用大合并,只需要在这里设置为0即可。
0.2对应的配置属性:hbase.hregion.majorcompaction.jitter
第三种:手动触发
1、由于担心某些线上业务,会受到大合并的影响,因此会选择低峰期手动完成触发。
2、由于开发人员改变了数据表结构之后,希望立刻生效,执行下手动触发MajorCompaction,让数据清理合并下,让业务重新生效。
3、HBase管理员发现硬盘容量不够的前提下,会手动触发MajorCompaction,这样会删除大量的过期数据。