HBase配置文件详解(一)
HBase使用与Hadoop相同的配置系统,所有配置文件都位于 conf/ 目录中,需要保持群集中每个节点的同步。
在对HBase进行配置,即编辑hbase-sitexml文件时,确保语法正确且XML格式良好。我们可以使用 xmllint 检查XML格式是否正确,默认情况下, xmllint 重新流动并将XML打印到标准输出。如果检查格式是否正确,并且只在存在错误时才打印输出,可以使用以下命令:
当在完全分布式环境下运行HBase时,在对HBase配置文件进行修改后,确保将 /conf/ 目录下的配置文件同步到集群其他节点上,可以使用 rsync 、 scp 或其他安全机制将配置文件复制到其他节点上。(对于大多数配置,服务器需要重新启动配置才能生效)
HBase 是典型的 NoSQL 数据库,通常被描述成稀疏的、分布式的、持久化的,由行键、列键和时间戳进行索引的多维有序映射数据库,主要用来存储非结构化和半结构化的数据。因为 HBase 基于 Hadoop 的 HDFS 完成分布式存储,以及 MapReduce 完成分布式并行计算,所以它的一些特点与 Hadoop 相同,依靠横向扩展,通过不断增加性价比高的商业服务器来增加计算和存储能力。
HBase 虽然基于 Bigtable 的开源实现,但它们之间还是有很多差别的,Bigtable 经常被描述成键值数据库,而 HBase 则是面向列存储的分布式数据库。
下面介绍 HBase 具备的显著特性,这些特性让 HBase 成为当前和未来最实用的数据库之一。
容量巨大
HBase 的单表可以有百亿行、百万列,可以在横向和纵向两个维度插入数据,具有很大的弹性。
当关系型数据库的单个表的记录在亿级时,查询和写入的性能都会呈现指数级下降,这种庞大的数据量对传统数据库来说是一种灾难,而 HBase 在限定某个列的情况下对于单表存储百亿甚至更多的数据都没有性能问题。
HBase 采用 LSM 树作为内部数据存储结构,这种结构会周期性地将较小文件合并成大文件,以减少对磁盘的访问。
扩展性强
HBase 工作在 HDFS 之上,理所当然地支持分布式表,也继承了 HDFS 的可扩展性。HBase 的扩展是横向的,横向扩展是指在扩展时不需要提升服务器本身的性能,只需添加服务器到现有集群即可。
HBase 表根据 Region 大小进行分区,分别存在集群中不同的节点上,当添加新的节点时,集群就重新调整,在新的节点启动 HBase 服务器,动态地实现扩展。这里需要指出,HBase 的扩展是热扩展,即在不停止现有服务的前提下,可以随时添加或者减少节点。
高可靠性
HBase 运行在 HDFS 上,HDFS 的多副本存储可以让它在岀现故障时自动恢复,同时 HBase 内部也提供 WAL 和 Replication 机制。
WAL(Write-Ahead-Log)预写日志是在 HBase 服务器处理数据插入和删除的过程中用来记录操作内容的日志,保证了数据写入时不会因集群异常而导致写入数据的丢失;而 Replication 机制是基于日志操作来做数据同步的。
上图是HBase的存储架构图。
由上图可以知道,客户端是通过Zookeeper找到HMaster,然后再与具体的Hregionserver进行沟通读写数据的。
具体到物理实现,细节包括以下这些:
首先要清楚HBase在hdfs中的存储路径,以及各个目录的作用。在hbase-sitexml 文件中,配置项 <name> hbaserootdir</name> 默认 “/hbase”,就是hbase在hdfs中的存储根路径。以下是hbase096版本的个路径作用。10以后的版本请参考这里: https://blogbcmengcom/post/hbase-hdfshtml
1、 /hbase/archive
HBase 在做 Split或者 compact 操作完成之后,会将 HFile 移到archive 目录中,然后将之前的 hfile 删除掉,该目录由 HMaster 上的一个定时任务定期去清理。
2、 /hbase/corrupt
存储HBase损坏的日志文件,一般都是为空的。
3、 /hbase/hbck
HBase 运维过程中偶尔会遇到元数据不一致的情况,这时候会用到提供的 hbck 工具去修复,修复过程中会使用该目录作为临时过度缓冲。
4、 /hbase/logs
HBase 是支持 WAL(Write Ahead Log) 的,HBase 会在第一次启动之初会给每一台 RegionServer 在log 下创建一个目录,若客户端如果开启WAL 模式,会先将数据写入一份到log 下,当 RegionServer crash 或者目录达到一定大小,会开启 replay 模式,类似 MySQL 的 binlog。
5、 /hbase/oldlogs
当logs 文件夹中的 HLog 没用之后会 move 到oldlogs 中,HMaster 会定期去清理。
6、 /hbase/snapshot
hbase若开启了 snapshot 功能之后,对某一个用户表建立一个 snapshot 之后,snapshot 都存储在该目录下,如对表test 做了一个 名为sp_test 的snapshot,就会在/hbase/snapshot/目录下创建一个sp_test 文件夹,snapshot 之后的所有写入都是记录在这个 snapshot 之上。
7、 /hbase/tmp
当对表做创建或者删除操作的时候,会将表move 到该 tmp 目录下,然后再去做处理操作。
8、 /hbase/hbaseid
它是一个文件,存储集群唯一的 cluster id 号,是一个 uuid。
9、 /hbase/hbaseversion
同样也是一个文件,存储集群的版本号,貌似是加密的,看不到,只能通过web-ui 才能正确显示出来
10、 -ROOT-
该表是一张的HBase表,只是它存储的是META表的信息。通过HFile文件的解析脚本 hbase orgapachehadoophbaseiohfileHFile -e -p -f 可以查看其存储的内容,如下所示:
以上可以看出,-ROOT-表记录的META表的所在机器是dchbase2,与web界面看到的一致:
11、 META
通过以上表能找到META表的信息,该表也是一张hbase表,通过以上命令,解析其中一个region:
以上可以看出,adt_app_channel表的数据记录在dchbase3这台reginserver上,也与界面一致,如果有多个region,则会在表名后面加上rowkey的范围:
通过以上描述,只要找到-ROOT-表的信息,就能根据rowkey找到对应的数据,那-ROOT-在哪里找呢?从本文一开始的图中可以知道,就是在zookeeper中找的。进入zookeeper命令行界面:
可以看出-ROOT-表存储在 dchbase3 机器中,对应界面如下:
以上就是HBase客户端根据指定的rowkey从zookeeper开始找到对应的数据的过程。
那在Region下HBase是如何存储数据的呢?
以下就具体操作一张表,查询对应的HFile文件,看HBase的数据存储过程。
在HBase创建一张表 test7,并插入一些数据,如下命令:
查看wal日志,通过 hbase orgapachehadoophbaseregionserverwalHLog --dump -p 命令可以解析HLog文件,内容如下:
查看HFile文件,内容如下:
由此可见,HFile文件就是存储HBase的KV对,其中Key的各个字段包含了的信息如下:
由于hbase把cf和column都存储在HFile中,所以在设计的时候,这两个字段应该尽量短,以减少存储空间。
但删除一条记录的时候,HBase会怎么操作呢?执行以下命令:
删除了rowkey为200的记录,查看hdfs,原来的HFile并没有改变,而是生成了一个新的HFile,内容如下:
所以在HBase中,删除一条记录并不是修改HFile里面的内容,而是写新的文件,待HBase做合并的时候,把这些文件合并成一个HFile,用时间比较新的文件覆盖旧的文件。HBase这样做的根本原因是,HDFS不支持修改文件。
Region内每个ColumnFamily的数据组成一个Store。每个Store内包括一个MemStore和若干个StoreFile(HFile)组成。
HBase为了方便按照RowKey进行检索,要求HFile中数据都按照RowKey进行排序,Memstore数据在flush为HFile之前会进行一次排序
为了减少flush过程对读写的影响,HBase采用了类似于两阶段提交的方式,将整个flush过程分为三个阶段:
要避免“写阻塞”,貌似让Flush操作尽量的早于达到触发“写操作”的阈值为宜。但是,这将导致频繁的Flush操作,而由此带来的后果便是读性能下降以及额外的负载。
每次的Memstore Flush都会为每个CF创建一个HFile。频繁的Flush就会创建大量的HFile。这样HBase在检索的时候,就不得不读取大量的HFile,读性能会受很大影响。
为预防打开过多HFile及避免读性能恶化,HBase有专门的HFile合并处理(HFile Compaction Process)。HBase会周期性的合并数个小HFile为一个大的HFile。明显的,有Memstore Flush产生的HFile越多,集群系统就要做更多的合并操作(额外负载)。更糟糕的是:Compaction处理是跟集群上的其他请求并行进行的。当HBase不能够跟上Compaction的时候(同样有阈值设置项),会在RS上出现“写阻塞”。像上面说到的,这是最最不希望的。
提示 :严重关切RS上Compaction Queue 的size。要在其引起问题前,阻止其持续增大。
想了解更多HFile 创建和合并,可参看 Visualizing HBase Flushes And Compactions 。
理想情况下,在不超过hbaseregionserverglobalmemstoreupperLimit的情况下,Memstore应该尽可能多的使用内存(配置给Memstore部分的,而不是真个Heap的)。下图展示了一张“较好”的情况:
hbase使用的是jdk提供的ConcurrentSkipListMap,并对其进行了的封装,Map结构是<KeyValue,KeyValue>的形式。Concurrent表示线程安全。
SkipList是一种高效的数据结构,之前专门写过文章,这里就不表了
写入MemStore中的KV,被记录在kvset中。根据JVM内存的垃圾回收策略,在如下条件会触发Full GC。 1、内存满或者触发阈值。 2、内存碎片过多,造成新的分配找不到合适的内存空间。 RS上服务多个Region,如果不对KV的分配空间进行控制的话,由于访问的无序性以及KV长度的不同,每个Region上的KV会无规律地分散在内存上。Region执行了MemStore的Flush操作,再经过JVM GC之后就会出现零散的内存碎片现象,而进一步数据大量写入,就会触发Full-GC。
为了解决因为内存碎片造成的Full-GC的现象,RegionServer引入了MSLAB(HBASE-3455)。MSLAB全称是MemStore-Local Allocation Buffers。它通过预先分配连续的内存块,把零散的内存申请合并,有效改善了过多内存碎片导致的Full GC问题。 MSLAB的工作原理如下: 1、在MemStore初始化时,创建MemStoreLAB对象allocator。 2、创建一个2M大小的Chunk数组,偏移量起始设置为0。Chunk的大小可以通过参数hbasehregionmemstoremslabchunksize调整。 3、 当MemStore有KeyValue加入时,maybeCloneWithAllocator(KeyValue)函数调用allocator为其查找KeyValuegetBuffer()大小的空间,若KeyValue的大小低于默认的256K,会尝试在当前Chunk下查找空间,如果空间不够,MemStoreLAB重新申请新的Chunk。选中Chunk之后,会修改offset=原偏移量+KeyValuegetBuffer()length。chunk内控制每个KeyValue大小由hbasehregionmemstoremslabmaxallocation配置。 4、 空间检查通过的KeyValue,会拷贝到Chunk的数据块中。此时,原KeyValue由于不再被MemStore引用,会在接下来的JVM的Minor GC被清理。
MSLAB解决了因为碎片造成Full GC的问题,然而在MemStore被Flush到文件系统时,没有reference的chunk,需要GC来进行回收,因此,在更新操作频繁发生时,会造成较多的Young GC。 针对该问题,HBASE-8163提出了MemStoreChunkPool的解决方案,方案已经被HBase-095版本接收。它的实现思路: 1、 创建chunk池来管理没有被引用的chunk,不再依靠JVM的GC回收。 2、 当一个chunk没有引用时,会被放入chunk池。 3、chunk池设置阈值,如果超过了,则会放弃放入新的chunk到chunk池。 4、 如果当需要新的chunk时,首先从chunk池中获取。 根据patch的测试显示,配置MemStoreChunkPool之后,YGC降低了40%,写性能有5%的提升。如果是095以下版本的用户,可以参考HBASE-8163给出patch。
MemStore 是 HBase 非常重要的组成部分,MemStore 作为 HBase 的写缓存,保存着数据的最近一次更新,同时是HBase能够实现高性能随机读写的重要组成。
HBase Table 的每个 Column family 维护一个 MemStore,当满足一定条件时 MemStore 会执行一次 flush,文件系统中生成新的 HFile。 而每次 Flush 的最小单位是 Region 。
MemStore的主要作用:
如果一个 HRegion 中 MemStore 过多(Column family 设置过多),每次 flush 的开销必然会很大,并且生成大量的 HFile 影响后续的各项操作,因此建议在进行表设计的时候尽量减少 Column family 的个数。
MemStore 无论是对 HBase 的写入还是读取性能都至关重要,其中 flush 操作又是 MemStore 最核心的操作。MemStore 在多种情况下会执行一次 Flush 操作:
再次注意,MemStore 的 最小 flush 单元是 HRegion 而不是单个 MemStore 。
更新被阻塞对单个节点和整个集群的影响都很大,需要关注 MemStore 的大小和 Memstore Flush Queue 的长度。
为了减少 flush 过程对读写的影响,HBase 采用了类似于两阶段提交的方式,将整个 flush 过程分为三个阶段:
上述 flush 流程可以通过日志信息查看:
整个 flush 过程可能涉及到 compact 操作和 split 操作,因为过于复杂,不做详细讲解。
正常情况下,大部分 Memstore Flush 操作都不会对业务读写产生太大影响,比如:定期刷新 MemStore、手动触发、单个 MemStore flush、Region 级别的 flush 以及超过 HLog 数量限制等情况,这几种场景只会短暂的阻塞对应 Region 上的写请求,阻塞时间很短,毫秒级别。
然而 一旦触发 Region Server 级别的限制导致 flush,就会对用户请求产生较大的影响 。会阻塞所有落在该 RegionServer 上的更新操作,阻塞时间很长,甚至可以达到分钟级别。
导致触发 RegionServer 级别限制的主要因素:
- Region Server 上运行的 Region 总数
Region 越多,Region Server 上维护的 MemStore 就越多。根据业务表读写请求量和 RegionServer 可分配内存大小,合理设置表的分区数量(预分区的情况)。
- Region 上的 Store 数(表的 Column family 数量)
每个 Column family 会维护一个 MemStore,每次 MemStore Flush,会为每个 Column family 都创建一个新的 HFile。当其中一个CF的 MemStore 达到阈值 flush 时,所有其他CF的 MemStore 也会被 flush,因此不同CF中数据量的不均衡将会导致产生过多 HFile 和小文件,影响集群性能。很多情况下,一个CF是最好的设计。
频繁的 MemStore Flush 会创建大量的 HFile。在检索的时候,就不得不读取大量的 HFile,读性能会受很大影响。为预防打开过多 HFile 及避免读性能恶化(读放大),HBase 有专门的 HFile 合并处理(HFile Compaction Process),根据一定的策略,合并小文件和删除过期数据。后续的文章会有详细介绍。
0条评论