在前面的文章里,介绍过HBase的入门操作知识,但对于正考虑将HBase用于生产系统的项目来说还是远远不够。
尽管HBase可以工作在本地文件系统之上,但在生产环境中,HBase需要依托HDFS作为其底层的数据存储,而HDFS提供了默认的3副本来实现数据文件的高可靠。整个HBase集群主要由Zookeeper、Master、RegionServer、HDFS所组成,如下图:
集群角色MasterHBaseMaster用于管理多个RegionServer,包括监测各个RegionServer的状态、分配Region及自动均衡等。具体职责包括:
负责管理所有的RegionServer,实现RegionServer的高可用
管理所有的数据分片(Region)到RegionServer的分配,包括自动均衡
执行建表/修改表/删除表等DDL操作
HBase允许多个Master节点共存,当多个Master节点共存时,只有一个Master是提供服务的,这种主备角色的"仲裁"由ZooKeeper实现。
RegionServerRegionServer是真正的数据读写服务器,即客户端会直接连接RegionServer进行操作。一个RegionServer会包括了多个Region,这里的Region则是真正存放HBase数据的区域单元,当一个表很大时,会拆分成很多个Region进行存放。可以说,Region是HBase分布式的基本单位。
ZookeeperZookeeper对于HBase的作用是至关重要的。
Zookeeper提供了HBaseMaster的高可用实现,并保证同一时刻有且仅有一个主Master可用。
Zookeeper保存了Region和RegionServer的关联信息(提供寻址入口),并保存了集群的元数据(Schema/Table)。
Zookeeper实时监控Regionserver的上线和下线信息,并实时通知Master。
就目前来说,Zookeeper已经成为了分布式系统架构中最常用的标准框架之一。除了HBase之外,有许多分布式大数据相关的开源框架,都依赖于Zookeeper实现HA。
HDFSHDFS是Hadoop的分布式文件系统,提供了高可靠高扩展的文件存储能力。其内部也是一个集群结构,包含NameNode、DataNode角色。其中NameNode存储的是HDFS文件目录树的元数据,包含文件与Block的关联信息,而DataNode则是HDFS的数据存放节点。
HDFS作为一个分布式文件系统,自然需要文件目录树的元数据信息,另外,在HDFS中每一个文件都是按照Block存储的,文件与Block的关联也通过元数据信息来描述。NameNode提供了这些元数据信息的存储。
工作机制在一个完整的HBase分布式集群中,各个组件的交互工作如下图所示:
首先Zookeeper维护了Master与各个RegionServer之间的关系及状态。当一个Client需要访问HBase集群时,会先和Zookeeper进行通信,并得到Master和RegionServer的地址信息。对于创建表/删除表/修改表来说,客户端会通过Master来进行操作,而正常的表数据读写,则是通过找到对应的RegionServer来操作。
RegionServer的作用
每一个RegionServer会管理很多个Region(区域),这个就是之前提到的HBase数据分布式及高可靠的一个单元。每个Region只会存储一个表中的的一段数据,这是按RowKey的区间来分隔的。而且,一个Region的存储空间有一个上限(Threshold),当存放数据的大小达到该上限时,Region就会进行分裂并产生多个新的Region,随着一个RegionServer上的Region越来越多,Master可以监测到不均衡的情况,并自动将Region进行重新分配。因此,数据可以源源不断的写入到HBase时,通过这种Region的分裂、自动均衡来支持海量数据的存储。
对于一个Region来说,其内部是由多个Store构成的,而一个Store对应于一个ColumnFamily(列族)。在实现上一个Store对象会包含一个MemStore以及多个StoreFile。当数据写入Region时,先写入MemStore(保持有序)。当MemStore写满了之后就会Flush到StoreFile,这里的StoreFile就对应了一个HDFS中的HFile文件。最终Region会对应到多个持久化的HFile,当这些HFile越来越多时,RegionServer会执行合并操作(Compaction)来合并为一个大的HFile,以此来优化读取性能,这个机制则是基于LSM的原理实现的。
HLog和可靠性
HBase提供了HLog来保证数据写入的可靠性,HLog本质上就是一种WAL(事务机制中常见的预写日志),其同样是通过HDFS来持久化的(对应于一个Sequence文件)。每个RegionServer都包含一个HLog,其中记录了所有发生在RegionServer上的数据变更操作。当数据发生写入时,会先记录日志到HLog中,然后再写入MemStore,最终才持久化到HFile中。这样当RegionServer宕机时,尽管MemStore中的数据会丢失,但还可以通过HLog来恢复之前的数据,从而保证了高可靠。那么,HFile是否可靠呢?这点则是由HDFS来保证的,一个HFile默认会有3个副本。
除此之外,HLog也是HBase实现集群同步复制的关键手段。
二、存储机制接下来,我们看看HBase中的数据是怎样被存储及管理的。
A.存储模型首先需要澄清的一点是,Region和ColumnFamily(简称CF)的区别。
Region是HBase分布式存储的基本单位,其本质上是一种水平切分单位,可以理解为数据的分片;而ColumnFamily(列族)则是垂直切分的单位,可理解为一种列的分组。这两者的区别可以参考下图:
无论是Region、还是CF,都是逻辑上的一个概念,对于物理上的实现则如下图所示:
存储模块说明
一个Region包含多个Store,一个store对应一个CF
Store包括位于内存中的Memstore和多个持久化的Storefile;
写操作先写入Memstore,当Memstore中的数据大小达到某个阈值后会Flush到一个单独的Storefile
当Storefile文件的数量增长到一定阈值后,系统会进行合并,形成更大的Storefile(Compaction)
当一个Region所有Storefile的大小总和超过一定阈值后,会把当前的Region分割为两个(分裂)
Master自动检测RegionServer上Region的分配情况,自动进行均衡迁移
客户端检索数据,优先从Memstore查询,然后再查询Storefile
HFile结构HFile是HDFS的存储单元,其结构如下图:
HFile由很多个数据块(Block)组成,并且有一个固定的结尾块。其中的数据块是由一个Header和多个Key-Value的键值对组成。在结尾的数据块中包含了数据相关的索引信息,系统也是通过结尾的索引信息找到HFile中的数据。
HFile中的数据块大小默认为64KB。如果访问HBase数据库的场景多为有序的访问,那么建议将该值设置的大一些。如果场景多为随机访问,那么建议将该值设置的小一些。一般情况下,通过调整该值可以提高HBase的性能
与CompactionHBase在存储上是基于LSM树实现的,与传统的B/B+树原理不同的是,LSM树非常适用于写入要求非常高的场景。
LSM的原理将一个大的B(B+)树拆分成N棵小树,数据首先写入内存中(有序),随着数据写入越来越多,内存中的数据会被flush到磁盘中形成一个文件;在读取数据时,则需要合并磁盘中历史数据和内存中最近修改的操作后返回。由于数据是顺序写入的,因此LSM的写入性能非常高,但读取时可能会访问较多的磁盘文件,性能较差。为了缓解读性能低下的问题,LSM树会定时将磁盘中的多个文件(小树)进行合并,以优化读性能。
在HBase的实现中,内存中的数据则是对应于MemStore,而磁盘中的数据则对应于StoreFile(HFile实现)。当MemStore写满后会Flush到一个HFile中。随着HFile文件的不断增多,Region的读性能就会受到影响(IOPS增加)。因此HBase的RegionServer会定期进行Compaction操作,将多个HFile合并为一个大的有序的HFile。HBase中运行的Compaction动作有两种:
MinorCompaction,列族中小范围的HFile文件合并,一般较快,占用IO低
MajorCompaction,列族中所有的HFile文件合并,同时清理TTL过期以及延迟删除的数据,该过程会产生大量IO操作,性能影响较大。
具体的过程如下图所示:
Flush性能影响如果Memstore很小,意味着Flush的次数会很多,一旦Compaction的速度跟不上就会产生大量的HFile文件,这会导致读性能恶化,为了减缓这个问题,HBase使用了InMemoryFlushAndCompact的方法,即数据在MemStore中先经分段(Segement)、Flush、Compaction过程,到达一定大小后再Flush到HFile。在这里,MemStore使用了ConcurrentSkipListMap(并发跳跃表)来保证一定的读写并发能力。ConcurrentSkipListMap在容量超过一定大小后性能下降明显,因此MemStore也不能设置得太大,当前的默认值在128MB。
触发Flush行为的条件包括:
Memstore级别:Region中任意一个MemStore达到了控制的上限(默认128MB),会触发Memstore的flush。
Region级别:Region中Memstore大小之和达到了控制的上限(默认2128M=256M),会触发Memstore的flush。
RegionServer级别:RegionServer中所有Region的Memstore大小总和达到了*hbaseheapsize控制的上限(默认0.4,即RegionServer40%的JVM内存),将会按Memstore由大到小进行flush,直至总体Memstore内存使用量低于*hbaseheapsize控制的下限(默认0.38,即RegionServer38%的JVM内存)。
RegionServer中HLog数量达到上限:将会选取最早的HLog对应的一个或多个Region进行flush(通过参数配置)。
HBase定期flush:确保Memstore不会长时间没有持久化,默认周期为1小时。为避免所有的MemStore在同一时间都进行flush导致的问题,定期的flush操作有20000左右的随机延时。
手动执行flush:用户可以通过shell命令flush‘tablename’或者flush‘regionname’分别对一个表或者一个Region进行flush。
除此以外,为了保证Compaction的进度与Flush对齐,HBase会在HFile数量到达一定阀值后阻塞Flush操作,如下面的参数:
,触发Flush阻塞等待的StoreFiles数量上限,2.x版本默认为16
,阻塞Flush的时间,2.x版本默认为90s
Compaction策略Compaction的目的是优化读性能,但会导致IO放大,这是因为在合并过程中,文件需要不断的被读入、写出,加上HDFS的多副本复制,则会再一次增加多次的IO操作。此外,Compaction利用了缓冲区合并来避免对已有的HFile造成阻塞,只有在最后合并HFile元数据时会有一点点的影响,这几乎可以忽略不计。但Compaction完成后会淘汰BlockCache,这样便会造成短期的读取时延增大。
在性能压测时通常可以看到由Compaction导致的一些"毛刺"现象,但这是不可避免的,我们只能是根据业务场景来选择一些合理的Compaction策略。
一般,MinorCompaction会配置为按需触发,其合并的范围小,时间短,对业务性能的影响相对可控。但MajorCompaction则建议是在业务闲时手动触发,以避免业务造成严重的卡顿。
关于如何选择合并文件的范围,HBase提供了以下几种策略:
StripeCompaction将一个Region划分为多个子区域(Stripes),Compaction严格控制在单个Stripe范围内发生,这样可以有效降低Compaction对IO资源的占用。Stripe范围是根据RowKey来设定的,因此这适用于RowKey单调递增写入的场景
DateTieredCompaction
与StripeCompaction类似,但却是基于时间戳来决定子区域的范围,适合时序数据的场景(仅按时间范围检索)
MOBCompaction与MOB特性绑定的Compaction,MOB用于存储文件数据,其将元数据与文件数据分离存储,其中文件数据不参与Compaction,这样就大大减少了Compaction带来的IO放大的影响。
RatioBasedCompaction
基于比例计算的Compaction策略,在0.96之前默认的策略,该策略会根据下列参数来选择Compaction的文件。
:最小待合并文件数
:最小待合并文件数
:最大待合并文件数
最小合并文件总大小
一般由于旧文件都是经过Compaction的会比较大,因此通常会基于新文件来做合并。关于该策略的详细讨论可参考这里
ExploringCompactionRatioBasedCompaction演进后的默认版本,基本算法类似,但Exploring会根据性价比来进一步筛选,其考虑的因素包括:文件数量较多(读性能增益)、总体大小偏小(降低IO放大)
分裂假设某个Region增长到了极限,将会切分为两个子Region(原来的Region称为父Region)
该过程的步骤如下:
RegionServer初始化两个子Region信息,写入一个ZK节点数据:/hbase/region-in-transition/region-name=SPLITING
Master通过Watch机制获知该region状态改变,此时可通过Master的UI界面观察到;
RegionServer创建.split临时目录,用于保存split后的子Region信息;
RegionServer关闭父Region并执行Flush操作,此后在短暂的时间内对于父Region的读写操作都会失败;
RegionServer在.split文件夹下新建两个子Region的目录,同时分别生成拆分后的reference文件(仅仅是引用信息);
RegionServer将.split文件夹下的子Region的目录拷贝到HBase根目录下,形成两个新的Region;
RegionServer修改表(连接该元数据表对应的RegionServer),将父Region标记为Split完成,Offline=true,即表示分裂完成后下线;
RegionServer打开两个子Region,表示可接收读写操作;
RegionServer修改表,将子Region上线的信息写入;
RegionServer修改ZK节点数据:/hbase/region-in-transition/region-name=SPLIT,此后Master获知分裂完成。如果有正在运行的均衡任务,则会考虑进一步处理;
可以发现,整个分裂过程仅仅是创建了一些数据文件的引用及元数据更新操作,对于业务的影响是非常微小的。那么,在分裂后的一段时间内,引用数据文件还会持续存在,一直到当子Region发生Compaction操作时,才会将父Region的HFile数据拷贝到子Region目录。
关于Region切分的细节分析进一步参考
;uypslg=eyr0j3
D.自动均衡HBase的Region分配和自动均衡是由Master节点控制的,在初始化表时会先分配一个Region,然后指定给某个RegionServer。如果使用预分区,那么Master会按照轮询的方式平均分配到每个RegionServer。此后,随着Region不断的增大和裂变,RegionServer上的Region数量开始变得不均衡。如果开启了自动均衡开关,Master会通过定时器来检查集群中的Regions在各个RegionServer之间的负载是否是均衡。一旦检测到不均衡的情况,就会生成相应的Region迁移计划。
关于均衡的方式,HBase提供以下两种策略:
DefaultLoadBalancer默认的策略,根据Region个数来进行均衡
StochasticLoadBalancer根据读写压力评估来进行均衡
由于HBase的的数据(包括HLog、StoreFile等)都是写入到HDFS文件系统中的,因此HBase的Region迁移是非常轻量级的。在做Region迁移时,Region所对应的HDFS文件是不变的,此时只需要将Region的元数据重新分配到目标RegionServer就可以了。迁移过程的步骤包含:
创建Region迁移计划,指定RegionID、源RegionServer和目标RegionServer;
源RegionServer解绑,此时会关闭Region;
目标RegionServer绑定,重新打开Region;
三、访问机制HBase支持多种读写客户端访问方式,具体包括:
基于JavaClient,一般是通过RPC调用HBase。
基于RestFulAPI,需要启用RestServer代理组件,该组件通过JavaClient实现。
基于ThriftAPI,需要启用ThriftServer代理组件,该组件通过JavaClient实现。
基于MapReduce的批处理API
基于HBaseShell,其内部也是通过JavaClient实现的。
无论使用何种调用方式,始终还是离不开最基础的RPC调用流程。该流程的交互逻辑如下图所示:
连接ZooKeeper在进行数据操作之前,客户端首先需要接入ZooKeeper,并初始化一个ZooKeeperSession。该Session由ZooKeeperClient与ZooKeeperServer端之间创建,并通过心跳机制保持长连接
获取metaRegion路由信息
HBase将Region分布的元数据存放在这个表中,该表记录了每一个用户表Region的路由以及状态信息,它的RowKey包含了表名TableName、RegionStartKey以及RegionID。客户端通过Zookeeper先找到metaRegion所在的RegionServer,然后获得metaRegion信息。之后根据操作的RowKey就可以定位到对应的RegionID,最后再通过Zookeeper的映射表就可以得到Region所在的RegionServer了。需要注意的是,客户端一般会对metaRegion信息进行缓存,避免每次都要耗费时间读取。
读写RegionServer在得到真实数据所在的RegionServer之后,客户端便通过RPC接口向目标RegionServer发起访问。对于一些批量请求,客户端会先通过Region进行分组,再并发的向多个RegionServer发出请求。
对于使用RestServer或是ThriftServer等中间组件的情况,调用流程如下图:
四、鉴权HBase的安全同时依赖于Zookeeper、HDFS。
ACL权限HBase支持RWXCA权限模型设置:
读取(R)-可以读取给定范围的数据。
写入(W)-可以在给定范围写入数据。
执行(X)-可以在给定范围内执行协处理器端点。
创建(C)-可以在给定范围内创建表或删除表(甚至不创建它们)。
管理员(A)-可以执行群集操作,例如在给定的范围内平衡群集或分配区域。
需要以最小权限原则为数据库表配置对应的用户权限
同样,为了保证整体的安全性,需要对ZooKeeper、HDFS都设定合理的ACL控制,包括文件系统。
身份认证和授权HBase集群中可使用KerberOS来实现节点之间的身份鉴权,包括:
节点接入Zookeeper
节点连接Master、RegionServer
节点接入HDFS
外加的RestServer、ThriftServer
Kerberos是一个常见的身份认证及鉴权协议系统,使用Kerberos的系统在设计上采用C/S结构及AES对称加密技术,并且能够进行双方认证。支持防止窃听、防止replay攻击、保护数据完整性等特性。Kerberos认证过程需要依赖于单独的KerberosServer(KDC),一个认证过程如下图:
KerberosAuthentication:客户端请求认证服务器(AS),获得TicketGrantingTicket(TGT)
KerberosAuthorization:客户端通过TGT票据请求TGS(Ticket授权服务),通过后会获得一个授权的ServiceTicket
ServiceRequest:客户端使用ServiceTicket访问目标服务,目标服务会对ServiceTicket进行本地校验,如果通过则表示认证成功。
关于KerberOS的详细原理,可以参考NoSQL漫谈-图解KerberOS这篇文章
对于HBase集群来说,各个节点使用KerberOS认证时,需要先配置keytab文件,该文件中就记录了实体ID(pricipal)、以及密钥的信息。而这些实体ID及密钥都是由KerberOS服务生成并管理的。
传输层安全对客户端RPC设置=privacy可以开启RPC加密功能,这对性能存在一定损失(约10%)
还可以使用TLS传输协议进一步提升安全性。
五、高可靠1.集群高可靠Zookeeper高可靠Zookeeper本身是集群多节点的架构,其内部使用Paxos算法来实现选举和数据的强一致性。在部署上通常可以选择3节点的架构来保证可靠性。
Master高可靠HBase可以开启BackupMaster来实现Master节点高可用,同一时间内只有主Master可以工作,Master宕机后由备Master自动接管Master的HA机制是借助Zookeeper完成的
RegionServer高可靠RegionServer通常会部署为多个节点,每个节点分别接管不同的Region而Master会对RegionServer的状态进行检测,一旦发现RegionServer宕机,则会将该Server上的Region列表重新指派给一个新的RegionServer。此外,Master还会将已宕机的RegionServer的HLog做一定拆分,并分发到新的RegionServer上做数据恢复。
该过程不涉及数据迁移,只是元数据的变更,操作数据量少并不会对业务造成很大的影响。
数据高可靠RegionServer本身提供了HLog(WAL)来提供断电保护,当Server异常宕机时,MemStore内丢失的数据可以通过HLog来回放恢复。
HDFS高可靠HDFS本身提供了一系列的可靠性机制,包括:
NameNode可以部署多个
DataNode可以部署多个
HFile存在多副本(默认3个),保证了数据文件可靠性
2.隔离性在部署上,通常依据一些原则策略来保证可靠性:
控制节点与数据节点分离部署
主备Master、Region节点分离部署
NameNode之间、DataNode之间分离部署
数据节点磁盘物理隔离
3.容灾尽管HDFS提供了三副本的机制,但对于关键业务来说,往往需要支持跨机房的容灾能力。
HBase支持Replication机制,该机制设计的主导思想是基于操作日志(put/get/delete)做数据同步的功能,这类似于MySQL的BinLog,或者是MongoDB的OpLog。Replication的关键就在于前面所提到过的HLog,这个日志除了用作数据断电保护之外,还被用来实现集群复制的功能。
如下图:
客户端的put/delete操作会先被RegionServer写入本地的HLog,之后由一个独立的线程将HLog内容以缓冲写的形式推送到Slave集群中的某个RegionServer上。整个复制的HLog信息、包括复制偏移量都会保存在Zookeeper上,同时复制动作是异步的,即不会阻塞当前的客户端读写。





