HBASE_OFFHEAPSIZE=5G
HBase是一种 "NoSQL" 数据库。"NoSQL"是一个通用词表示数据库不是RDBMS ,后者支持 SQL 做为主要访问语言。有许多种 NoSQL 数据库: BerkeleyDB 是本地 NoSQL 数据库例子, 而 HBase 是大型分布式数据库。技术上来讲, HBase 更像是"数据存储(Data Store)" 多于 "数据库(Data Base)",由于缺乏不少RDBMS特性, 如列类型,第二索引,触发器,高级查询语言等。html
然而, HBase 有许多特征同时支持线性化和模块化扩充。HBase 集群经过增长RegionServers进行扩充,RegionServer能够运行在普通的服务器中。例如,若是集群从10个扩充到20个RegionServer,存储空间和处理容量都同时翻倍。RDBMS 也能很好扩充,但仅对一个点——特别是对一个单独数据库服务器的大小 - 同时,为了更好的性能,须要特殊的硬件和存储设备。 HBase的特性有:前端
强一致性读写: HBase 不是 "最终一致性(eventually consistent)" 数据存储. 这让它很适合高速计数聚合类任务。java
自动分片(Automatic sharding): HBase 表经过region分布在集群中。数据增加时,region会自动分割并从新分布。node
RegionServer 自动故障恢复web
Hadoop/HDFS 集成: HBase 支持本机外HDFS 做为它的分布式文件系统。正则表达式
MapReduce: HBase 经过MapReduce支持大并发处理, HBase 能够同时作源和目标。算法
Java 客户端 API: HBase 支持易于使用的 Java API 进行编程访问。sql
Thrift/REST API: HBase 也支持Thrift 和 REST 做为非Java 前端.shell
Block Cache 和 Bloom Filters: 对于大容量查询优化, HBase支持 Block Cache 和 Bloom Filters。数据库
运维管理: HBase提供内置网页用于运维视角和JMX 度量.
HBase不适合全部问题。
首先,确信有足够多数据,若是有上亿或上千亿行数据,HBase是很好的备选。 若是只有上千或上百万行,则用传统的RDBMS多是更好的选择,由于全部数据能够在一两个节点保存,集群其余节点可能闲置。
其次,确信能够不依赖全部RDBMS的额外特性 (e.g., 列数据类型, 第二索引, 事务,高级查询语言等.) 一个创建在RDBMS上应用,如不能仅经过改变一个JDBC驱动移植到HBase。相对于移植, 需考虑从RDBMS 到 HBase是一次彻底的从新设计。
第三, 确信你有足够硬件。甚至 HDFS 在小于5个数据节点时,干很差什么事情 (根据如 HDFS 块复制具备缺省值 3), 还要加上一个 NameNode.
HBase 能在单独的笔记本上运行良好。但这应仅当成开发配置。
在HBase0.96以前,-ROOT- 保存 .META. 表(这是以前的名字,如今.META表的名字是 hbase:meta)存在哪里的踪影。 -ROOT- 表结构以下:
.META. region key (.META.,,1
)
info:regioninfo
(序列化hbase:meta的 HRegionInfo 实例)
info:server
(保存hbase:meta的RegionServer的server:port)
info:serverstartcode
(保存hbase:meta的RegionServer进程启动时间)
hbase:meta表(以前名为 .META.)保存系统中全部region列表。hbase:meta的位置以前是在 -ROOT-表中跟踪的,可是如今存储在ZooKeeper里了。
hbase:meta表结构以下:
Region key的格式 ([table],[region start key],[region id])
info:regioninfo
(当前Region的序列化的 HRegionInfo 实例)
info:server
(含有这个Region的RegionServer的server:port)
info:serverstartcode
(含有这个Region的RegionServer进程的启动时间)
当一个表在分割过程当中,会建立额外的两列, info:splitA
和 info:splitB
表明两个女儿 region。这两列的值一样是序列化HRegionInfo 实例。在Region最终分割完毕后,这行会被删除。
![]() |
HRegionInfo的备注:
空 key 用于指示表的开始和结束。具备空开始键值的region是表内的首region。 若是 region 同时有空起始和结束key,说明它是表内的惟一region。 |
在须要编程访问(但愿不要)目录元数据时,参考 Writables 工具。
首先,hbase:meta 地址在ZooKeeper中查询。其次,hbase:meta会更新 server 和 startcode 的值。
须要 region-RegionServer 分配信息, 参考Region-RegionServer Assignment。
HBase客户端找到服务于特定行范围的RegionServer。它经过查询hbase:meta表来作这件事。参见hbase:meta获取细节。在定位到须要的Region后,客户端会联系服务这个Region的的RegionServer,而不通过master,并发起读写请求。这些信息会缓存在客户端,这样就不用每发起一个请求就去查一下。由于master load balance或者RegionServer已死,一个Region会被从新分配,客户端就会从新查询目录表,以决定要去访问的这个用户Region的新地址。
参考Runtime Impact 以获取更多关于Master对HBase客户端通讯的影响的信息
管理集群操做是经由 Admin 实例发起的。
相关的API在HBase 1.0中有改变。关于链接配置的信息,参考 Client configuration and dependencies connecting to an HBase cluster。
它已经被清理干净,返回给用户的是用来工做的接口,而不是特定的类型。
在HBase 1.0,从ConnectionFactory获取一个Connection对象,而后根据须要从这个Connection对象中获取Table、Admin和RegionLocator的实例。作完了,就关掉得到的实例。最终,在退出以前确保清理你的Connection实例。Connections是重量级的对象,但它是线程安全的,因此你能够为你的应用建立一个Connection实例,而后保留它。Table, Admi和
RegionLocator实例是轻量级的。要建立它们就去作,用完了就当即关闭它们。参考Client Package Javadoc Description,以获取新的HBase1.0 API的使用例子。
65.1.2. HBase 1.0.0以前的API
在1.0.0以前,HTable实例是与一个HBase集群交互的方式。Table实例不是线程安全的。在任意时间,只有一个线程能够使用Table的一个实例。
当建立Table实例时,推荐使用相同的HBaseConfiguration实例。这将确保将ZooKeeper和套接字实例共享给RegionServer,这一般是您想要的。首选的例子是:
HBaseConfiguration conf = HBaseConfiguration.create(); HTable table1 = new HTable(conf, "myTable"); HTable table2 = new HTable(conf, "myTable");
与之相反:
HBaseConfiguration conf1 = HBaseConfiguration.create(); HTable table1 = new HTable(conf1, "myTable"); HBaseConfiguration conf2 = HBaseConfiguration.create(); HTable table2 = new HTable(conf2, "myTable");
参考ConnectionFactory 以获取关于链接在HBase客户端是如何处理的信息。
对须要高端多线程访问的应用 (如网页服务器或应用服务器须要在一个JVM服务不少应用线程),你能够事先建立一个Connection,像下面的例子展现的那样:
Connection
// Create a connection to the cluster. Configuration conf = HBaseConfiguration.create(); try (Connection connection = ConnectionFactory.createConnection(conf)) { try (Table table = connection.getTable(TableName.valueOf(tablename)) { // use table as needed, the table returned is lightweight } }
构建HTableInterface的实现是很是轻量级的,而且能够控制资源。
![]() |
HTablePool被弃用了
这个guide以前的版本讨论了在HBase 0.9四、0.95和0.96中弃用的 |
在HBase1.0及之后,HTable被弃用了以支持使用Table。Table不使用自动flush。要缓冲写,使用BufferedMutator类。
在Table或HTable实例被废弃前,调用close()
或 flushCommits(),使'Put'不会丢失。
关于非Java客户端和自定义协议信息,在Apache HBase External APIs。
66. 客户端请求过滤器 Client Request Filters
Get 和 Scan 实例能够用 filters 配置,以应用于 RegionServer.
过滤器可能会搞混,由于有不少类型的过滤器, 最好经过理解过滤器功能组来了解他们。
结构过滤器包含其余过滤器。
FilterList 表明一个过滤器列表,过滤器间具备 FilterList.Operator.MUST_PASS_ALL
或 FilterList.Operator.MUST_PASS_ONE
关系。下面示例展现两个过滤器的'或'关系(检查同一属性的'my value' 或'my other value' ).
FilterList list = new FilterList(FilterList.Operator.MUST_PASS_ONE); SingleColumnValueFilter filter1 = new SingleColumnValueFilter( cf, column, CompareOp.EQUAL, Bytes.toBytes("my value") ); list.add(filter1); SingleColumnValueFilter filter2 = new SingleColumnValueFilter( cf, column, CompareOp.EQUAL, Bytes.toBytes("my other value") ); list.add(filter2); scan.setFilter(list);
SingleColumnValueFilter (参见: http://hbase.apache.org/1.2/apidocs/org/apache/hadoop/hbase/filter/SingleColumnValueFilter.html) 用于测试列值相等 (CompareOp.EQUAL
), 不相等 (CompareOp.NOT_EQUAL
), 或范围 (如 CompareOp.GREATER
)。下面的例子是测试一个列值等于字符串值"my value"…
SingleColumnValueFilter filter = new SingleColumnValueFilter( cf, column, CompareOp.EQUAL, Bytes.toBytes("my value") ); scan.setFilter(filter);
过滤器包内有好几种比较器类须要特别说起。这些比较器和其余过滤器一块儿使用, 如SingleColumnValueFilter。
RegexStringComparator 支持值比较的正则表达式 。
RegexStringComparator comp = new RegexStringComparator("my."); // any value that starts with 'my' SingleColumnValueFilter filter = new SingleColumnValueFilter( cf, column, CompareOp.EQUAL, comp ); scan.setFilter(filter);
参考 Oracle JavaDoc 了解 supported RegEx patterns in Java.
SubstringComparator 用于检测一个子串是否存在于值中。大小写不敏感。
SubstringComparator comp = new SubstringComparator("y val"); // looking for 'my value' SingleColumnValueFilter filter = new SingleColumnValueFilter( cf, column, CompareOp.EQUAL, comp ); scan.setFilter(filter);
See BinaryComparator.
因为HBase 内部采用键值对保存数据,键值元数据过滤器评估一行的键是否存在(如 ColumnFamily:Column qualifiers) , 对应前节所述值的状况。
FamilyFilter 用于过滤列族。 一般,在Scan中选择ColumnFamilie优于在过滤器中作。
QualifierFilter 用于基于列名(即 Qualifier)过滤。
ColumnPrefixFilter 可基于列名(即Qualifier)前缀过滤。
ColumnPrefixFilter在前面的第一个列中寻找每一行的前缀和每一个相关的列族。它能够用于在很是宽的行中有效地获取列的子集。
注意:相同的列标识符能够在不一样的列族中使用。这个过滤器返回全部匹配的列。
例: 找出一行中全部的名字开头为"abc"的列
HTableInterface t = ...; byte[] row = ...; byte[] family = ...; byte[] prefix = Bytes.toBytes("abc"); Scan scan = new Scan(row, row); // (optional) limit to one row scan.addFamily(family); // (optional) limit to one family Filter f = new ColumnPrefixFilter(prefix); scan.setFilter(f); scan.setBatch(10); // set this if there could be many columns returned ResultScanner rs = t.getScanner(scan); for (Result r = rs.next(); r != null; r = rs.next()) { for (KeyValue kv : r.raw()) { // each kv represents a column } } rs.close();
MultipleColumnPrefixFilter 和 ColumnPrefixFilter 行为差很少,但能够指定多个前缀。
和ColumnPrefixFilter类似,MultipleColumnPrefixFilter有效地寻求提早第一列匹配的最低位的前缀,而且在前缀之间寻找列的范围。它能够用来有效地从很是宽的行中获取不连续的列。
例: 找出行中全部的列名开头为"abc" 或 "xyz"的列
HTableInterface t = ...; byte[] row = ...; byte[] family = ...; byte[][] prefixes = new byte[][] {Bytes.toBytes("abc"), Bytes.toBytes("xyz")}; Scan scan = new Scan(row, row); // (optional) limit to one row scan.addFamily(family); // (optional) limit to one family Filter f = new MultipleColumnPrefixFilter(prefixes); scan.setFilter(f); scan.setBatch(10); // set this if there could be many columns returned ResultScanner rs = t.getScanner(scan); for (Result r = rs.next(); r != null; r = rs.next()) { for (KeyValue kv : r.raw()) { // each kv represents a column } } rs.close();
ColumnRangeFilter 能够进行高效内部扫描。
ColumnRangeFilter能够为每一个相关的列族查找第一个匹配列。它能够用来有效地在一个很宽的行中获得一个列“切片”。例如,你一行有一百万个列但你只想查看列bbbb到列bbdd的值。
注意:相同的列限定符能够在不一样的列族中使用。这个过滤器返回全部匹配的列。
例如: 找出一行中全部的列和列族,列范围在"bbbb" (含bbbb) 和 "bbdd" (含bbdd)之间
HTableInterface t = ...; byte[] row = ...; byte[] family = ...; byte[] startColumn = Bytes.toBytes("bbbb"); byte[] endColumn = Bytes.toBytes("bbdd"); Scan scan = new Scan(row, row); // (optional) limit to one row scan.addFamily(family); // (optional) limit to one family Filter f = new ColumnRangeFilter(startColumn, true, endColumn, true); scan.setFilter(f); scan.setBatch(10); // set this if there could be many columns returned ResultScanner rs = t.getScanner(scan); for (Result r = rs.next(); r != null; r = rs.next()) { for (KeyValue kv : r.raw()) { // each kv represents a column } } rs.close();
注意: 这个过滤器在HBase 0.92中引入
一般认为行选择时Scan采用 startRow/stopRow 方法比较好。然而RowFilter 也能够用。
这主要用于rowcount做业。参考 FirstKeyOnlyFilter.
HMaster
是Master Server的实现。主服务器(Master Server)负责监控集群中的全部RegionServer实例,而且是全部元数据更改的接口。在一个分布式集群,Master一般会运行在NameNode节点上。
J Mohamed Zahoor 在发表的blog HBase HMaster Architecture中探究了关于Master结构更多的细节。
若是在一个一主多备的环境中运行,全部的备(standby Master)都争着运行集群。若是激活状态的主(Master)失去了它在ZooKeeper中的租约(或者这个主关闭了),那么剩下的备争夺成为主。
一个常见的问题是到当主服务器Master宕机时,HBase集群会发生什么状况。由于HBase客户端直接与RegionServer通讯,集群仍然能够在“稳定状态”中运行。
另外,每一个Catalog Tables,hbase:meta
做为HBase表存在,并不常驻Master。可是,Master控制关键的功能,好比RegionServer故障恢复和完成Region分裂。
所以,虽然集群在没有主服务器的状况下仍然能够运行很短的时间,但仍是应该尽快从新启动主服务器。
由HMasterInterface公开的方法主要是面向元数据的方法
:
表 (createTable, modifyTable, removeTable, enable, disable)
列族 (addColumn, modifyColumn, removeColumn)
Region (move, assign, unassign)
例如, 当 Admin
的方法 disableTable
被调用时, 它由主服务器(Master server)提供服务。
Master 后台运行几种线程:
周期性的以及在过渡期间,当没有任何Region时,负载平衡器将运行并移动各个Region以平衡集群的负载。参见Balancer设置这个属性。
参考 Region-RegionServer Assignment 以获取更多关于Region分配的信息。
周期性地检查和清理hbase:meta
表。参见 <arch.catalog.meta> 得到更多关于元数据表的信息。
HRegionServer
是RegionServer的实现,负责Region的服务和管理。在分布式集群中,一个RegionServer运行在一个DataNode上。
由HRegionRegionInterface声明的方法中包含 面向数据的 和 面向Region维护的。
contain both data-oriented and region-maintenance methods:
数据 (get, put, delete, next, etc.)
Region (splitRegion, compactRegion, etc.)
例如,当 Admin
的方法 majorCompact
在一个表上被调用时, 客户端其实是对指定表的全部Region进行迭代,并要求对每一个Region直接进行major compaction(紧缩)操做。
RegionServer 后台运行几种线程:
检查分割并处理minor compactions。
检查major compactions。
周期性地将写到MemStore的内容刷到存储文件(StoreFiles)。
周期性地检查RegionServer的WAL。
协处理器在0.92版添加。有一个详细帖子 Blog Overview of CoProcessors 供参考。文档最终会放到本参考手册,但该blog是当前能获取的大部分信息。
LruBlockCache
是最初的实现, 彻底缓存在Java堆内存中。BucketCache
主要把数据缓存到堆外内存, 然而它也能够把数据缓存到堆内存,也能够为缓存在文件中的数据提供服务。
![]() |
BucketCache 在HBase 0.98.6可用
要启用 BucketCache, 你须要了解 HBASE-11678. 它在0.98.6中引入。 |
与使用本机堆内存的LruBlockCache相比,从BucketCache取回数据老是较慢。可是,延迟的时间趋于稳定,由于在使用BucketCache时使用的垃圾回收量较少,由于它管理的是块缓存分配,而不是GC。
若是BucketCache被部署在堆外内存,这块内存就不禁GC管理了。这就是为何你使用了BucketCache,所以您的延迟不那么不稳定,并减小了gc和堆的碎片。
参考 Nick Dimiduk的BlockCache 101中块缓存在堆内存和堆外内存的比较测试。
参考Comparing BlockCache Deploys,能够发现:若是您的数据集适合您的LruBlockCache部署,请使用它;不然若是你在经受缓存不稳定的状况(或你想你的缓存尽可能避免java GC引起的不稳定),使用BucketCache。
当你启用BucketCache时,你就启用了两层缓存系统,L1缓存由LruBlockCache的一个实例实现,使用堆外内存的L2缓存由BucketCache实现。
这两个层的管理,以及决定块在这两层之间如何移动的策略由CombinedBlockCache完成。
它将全部的DATA块保存在L2 BucketCache,将元数据块——INDEX和BLOOM块放在使用堆内存的L1 LruBlockCache。参考Off-heap Block Cache获取关于堆外内存的更多信息。
除了缓存自己的实现以外,您还能够设置一些通用配置选项来控制缓存的执行状况。
参考http://hbase.apache.org/1.2/devapidocs/org/apache/hadoop/hbase/io/hfile/CacheConfig.html。在设置了这些选项以后,从新启动或依次从新启动你的集群以使配置生效。出现错误或异常行为就检查日志。
也能够参考Prefetch Option for Blockcache,它讨论了一个由HBASE-9857引入的新的配置选项。
LruBlockCache是一个LRU缓存,它包含三个级别的块优先级,以应对Scan操做带来的Cache频繁颠簸和in-memory的列族。
The LruBlockCache is an LRU cache that contains three levels of block priority to allow for scan-resistance and in-memory ColumnFamilies:
Single 访问优先级: 一个块第一次从HDFS加载,它一般拥有这个优先级,在缓存回收时它将是首先被考虑要回收的组。这样作的好处是,相比于获得更多使用的块,被扫描的块更可能被回收。
Multi 访问优先级: 若是Single优先级组中的一个块再次被访问,它将升级到这个优先级。所以,在缓存回收时,它是第二位被考虑回收的。
In-memory 访问优先级: 若是一个块所在的列族被配置为 "in-memory", 它将是这个优先级的一部分,不考虑它被访问的次数。目录表被配置成这样。这个组是最后被考虑回收的。
若是用java建立表的话,要标记一个列族是 in-memory的,调用
HColumnDescriptor.setInMemory(true);
在shell中建立或修改一个表的话,设置 IN_MEMORY ⇒ true
hbase(main):003:0> create 't', {NAME => 'f', IN_MEMORY => 'true'}
获取更多信息,参考 LruBlockCache source
对于用户表来讲,块缓存是默认启用的,这意味着任意的读操做都会被加载的LRU缓存。这对于大量的用例来讲多是好的,可是为了达到更好的性能,一般须要进一步的调优。
一个重要的概念是working set size,也称做WSS,它是“计算一个问题的答案所需的内存数量”。对于一个网站来讲,这是须要在短期内回答这些问题所需的数据。
计算HBase中有多少内存可用来作缓存的方式是:
number of region servers * heap size * hfile.block.cache.size * 0.99
块缓存(block cache)的默认值是0.25,表示是可用堆的25%。最后一个值(99%),在回收启动后的LRU缓存中,是默认的可接受的加载因子。它被包含在这个公式的缘由是,使用100%的可用内存作缓存是不现实的,由于这会使进程从加载新块的地方阻塞。如下有一些例子:
一个具备堆大小为1GB的RegionServer,默认块缓存大小将有253 MB。
20个具备堆大小为8GB的RegionServer,默认的块缓存大小为39.6GB。
100个具备堆大小为24GB的RegionServer,且 block cache size 为0.5,那么块缓存约为1.16TB。
你的数据并非在块缓存中惟一驻留的数据。如下是你可能须要考虑的其余问题:
目录表 Catalog Tables
-ROOT-
(存在于HBase 0.96以前的版本, 参考 arch.catalog.root) 和 hbase:meta
表 被强制使用块缓存,且拥有in-memory 优先级,这意味着它们的缓存很难被回收。-ROOT-不会使用超过几百字节的缓存,而hbase:meta可能占用几兆字节(取决于Region的个数)。
HFile 是HBase用来把数据存储到HDFS上的文件格式。它包含一个多层索引,它容许HBase在不用读取整个文件的状况下查找数据。这些索引的大小是块大小的一个因子(默认为64KB),键的大小和存储的数据量。对于大数据集来讲,每一个RegionServer,HFile索引的大小有1GB左右也并很多见,然而并非全部的HFile索引都将在缓存中,由于LRU会回收那些不使用的索引的缓存。
每一个值都与它的键一块儿存储(行键、列族、列标识符和时间戳) 。参考 Try to minimize row and column sizes.
就像HFile索引同样,这些数据结构(在启用时)存储在LRU中。
目前,衡量HFile索引和bloom过滤器大小的推荐方法是查看RegionServer web UI,并检查相关的度量标准。对于键,能够使用HFile命令行工具来进行抽样,并查找平均键大小的度量。从HBase 0.98.3开始,你能够在UI的 Block Cache 部分查看BlockCache统计的细节和度量。
当WSS没有装到内存时,使用块缓存一般是很差的。例如,全部的RegionServer共有都有40GB可用的块缓存空间,可是你须要处理1TB的数据。其中一个缘由是,缓存回收过程所产生的混乱会致使没必要要的垃圾收集。这里有两个用例:
彻底随机读取模式: 这个状况是,您几乎不可能在短期内两次访问同一行,这样就能够访问缓存块的概率接近于0。在这样的表上设置块缓存是对内存和CPU周期的浪费,更严重的是这样的配置将产生更多的须要由JVM收拾的垃圾。更多关于监控GC的内容,参考JVM Garbage Collection Logs。
Mapping a table: 在典型的MapReduce做业中,须要输入一个表,每一行只读取一次,所以不须要将它们放入块缓存中。Scan对象经过setCaching方法(设置它为false)禁用缓存。若是您须要快速随机读取访问,您仍然能够在该表上保留启用块缓存的配置。例如,一个示例将计算一个服务于实时通讯的表中的行数,缓存这个表的每一个块都会形成大量的混乱,而且确定会将当前正在使用的数据的缓存回收。
一个有趣的设置是,咱们只缓存META块,并在每一个访问中读取DATA块。若是数据块放到fscache,那么当访问在一个很是大的数据集上彻底随机时,这种选择多是有意义的。要启用这样的设置,修改你的表为每一个列族设置 BLOCKCACHE ⇒ 'false'
。您正在禁用这个列家族的块缓存。你不能禁用META块的缓存。由于 HBASE-4683 Always cache index and bloom blocks, 咱们会缓存META块即便BlockCache被禁用。
一般BucketCache的部署是经过一个管理类,它创建了两个缓存层:L1 on-heap缓存由LruBlockCache实现,L2 cache经过BucketCache实现。这个管理类默认是CombinedBlockCache。前面的连接描述了由CombinedBlockCache实现的缓存“策略”。简单说,它把元数据块 INDEX 和 BLOOM放到L1 on-heap LruBlockCache层,把DATA块放到L2 BucketCache层。在HBase 1.0之后,能够调整这个行为,例如,经过设置cacheDataInL1使一个列族的元数据和DATA数据块缓存在L1层的堆内存里。配置方法:
Java代码中 HColumnDescriptor.setCacheDataInL1(true)
,或者在shell中 建立修改列族时把 CACHE_DATA_IN_L1
设置成 true:
hbase(main):003:0> create 't', {NAME => 't', CONFIGURATION => {CACHE_DATA_IN_L1 => 'true'}}
BucketCache Block Cache能够被部署到堆内存、对外内存或者文件中。你的设置在 hbase.bucketcache.ioengine
setting。
把它设置成 heap,将
部署BucketCache到分配的Java堆中。把它设置成 offheap,将使
BucketCache把缓存分配到堆外内存。把它设置成 file:PATH_TO_FILE
将部署BucketCache使用一个文件作缓存(这个配置颇有用,特别是当你的机器中有一些快速的I/O设备,如SSD固态硬盘)。
咱们能够部署L1+L2的设置而不使用CombinedBlockCache策略,将BucketCache做为严格的L2缓存、LruBlockCache做为L1缓存。要设置成这样,设CacheConfig.BUCKET_CACHE_COMBINED_KEY
为false
。在这个模式中,从L1移出的块将放到L2中。当一个块被缓存时,它首先被缓存到L1。当查找一个缓存的块时,首先去L1找,若是找不到再去L2找。称这样的部署格式为原始的L1+L2。
其余的BucketCache配置包括:指定一个位置以便在从新启动时保持缓存,有多少个线程用来写缓存,等等。参考CacheConfig.html 类获取配置项和描述信息。
这个例子配置了 4GB off-heap BucketCache缓存、1GB on-heap缓存。
配置在RegionServer上执行。
设置 hbase.bucketcache.ioengine
和 hbase.bucketcache.size
> 0 以启用 CombinedBlockCache
. 假定RegionServer已经设置为以5G的堆内存运行:如 HBASE_HEAPSIZE=5g。
首先, 编辑RegionServer的hbase-env.sh,设置 HBASE_OFFHEAPSIZE
为一个大于给定的off-heap大小的值,在这个例子中,off-heap大小为4GB,咱们给HBASE_OFFHEAPSIZE设置成5GB。这样的配置表示给off-heap缓存4GB,给其它用途的堆外内存设置1GB(堆外内存除了BlockCache还有其余的使用者,如RegionServer中的DFSClient能够利用off-heap内存)。参考 Direct Memory Usage In HBase。
HBASE_OFFHEAPSIZE=5G
其次,添加如下配置到RegionServer的 hbase-site.xml中。
<property> <name>hbase.bucketcache.ioengine</name> <value>offheap</value> </property> <property> <name>hfile.block.cache.size</name> <value>0.2</value> </property> <property> <name>hbase.bucketcache.size</name> <value>4196</value> </property>
重启或者依次重启你的集群,出现问题就查看日志。
上述内容中,咱们把BucketCache设置为4G。咱们配置了on-heap LruBlockCache为20%(0.2)的RegionServer堆内存大小(0.2 * 5G = 1G)。换句话说,您能够像往常同样配置L1 LruBlockCache(好像没有L2缓存存在)。
HBASE-10641 介绍了为BucketCache的buckets配置多种大小的能力,已经在HBase0.98及之后的版本中实现。要配置多个bucket的大小,就配置新属性 hfile.block.cache.sizes
(替代 hfile.block.cache.size
) 给它一个由逗号分隔的块大小的列表,从小到大排序,不能有空格。这个配置的目的是根据你的数据访问方式优化bucket的大小。如下的例子中,配置了buckets的大小为4096和8192.
<property> <name>hfile.block.cache.sizes</name> <value>4096,8192</value> </property>
![]() |
默认最大的直接内存因JVM而异。传统上,它是64M,但可能和与分配的堆大小(-Xmx程序运行期间最大可占用的内存大小)有关,也可能无关(这显然说的是JDK7)。HBase服务器使用直接内存,特别在short-circuit reading时,服务器上的DFSClient将分配直接内存缓冲区。若是你启用了off-heap块缓存,你将利用直接内存。启动你的JVM,确保在conf/hbase-env.sh中的 你能够在UI的Server Metrics: Memory tab中查看一个RegionServer被配置了能使用多少内存,包括on-heap和off-heap/direct,以及这个RegionServer在任一时刻正在使用多少内存。它也能够经过JMX获得。特别地,当前被RegionServer使用的直接内存能够在 |
直接内存(Direct Memory)并非虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,可是这部份内存也被频繁地使用,并且也可能致使OutOfMemoryError 异常出现。
这个的出现主要和java1.4后出现的NIO相关,一个基于通道和缓冲区的io方式,它能够使用Native函数库来直接分配堆外内存,而后经过一个存在java堆中的DirectByteBuffer这个对象来对这个java堆外的内存的引用来进行操做,能够提升相关性能,由于避免了java堆和native堆中的来回复制数据。
![]() |
hbase.bucketcache.percentage.in.combinedcache
这是一个HBase1.0以前的配置,已经被删除了,由于它引发了困惑。它是一个浮点数值,你能够设置0.0到1.0之间的数值,默认是0.9。若是部署使用 CombinedBlockCache,那么 LruBlockCache L1 的大小的计算公式就是: 在HBase 1.0中,这个设置应该更加直接。L1 LruBlockCache的大小被设置成 java heap 与 |
HBASE-11331 引入了惰性的BlockCache解压缩,更是简单说起了压缩的BlockCache。当压缩的块缓存被启用时,数据和被编码的数据的块会以它们的磁盘格式缓存在BlockCache中,而不是在缓存以前进行解压缩和解码。
对于存储了量多到比缓存还要大的数据的RegionServery来讲,启用这个功能并使用SNAPPY压缩,能够提升50%的吞吐量,平均延迟时间提升30%,然而使垃圾收集增长80%,整体CPU负载增长2%。参考HBASE-11331 以得到关于如何度量性能和实现的更多细节。对于存储了量能够放到缓存里的数据的RegionServer,或者若是您的工做负载对额外的CPU或垃圾收集负载很敏感,那么启用这个功能您可能会获得更少的好处。
压缩的BlockCache默认是被禁用的。要启用它,把全部RegionServer上的hbase-site.xml中的hbase.block.data.cachecompressed
设置为 true
。
因为写请求是由RegionServer处理的,因此它们会把数据写到一个被称为memstore的内存存储系统。一旦memstore填满,它的内容就会被写到磁盘上,做为额外的存储文件。这个事件被称为刷新(memstore flush)。随着存储文件的累积,区域服务器将把它们紧缩(compact)成更大的文件,从而使文件数更少。每次刷新或紧缩完成后,该Region内存储的数据量就发生了变化。RegionServer根据Region分割策略,以肯定该Region是否增加过大,或者是否应该因另外一个特定的策略而被分割。若是策略建议分割,Region分割请求就会被排到队列里。
逻辑上,分割一个Region的处理是简单的。咱们在这个Region的行键域(keyspace)里找一个合适的点,这个点是咱们应该把这个Region分红两半的点,而后在这个点把这个Region的数据分割到两个新的Region中去。可是处理的细节并不简单。当一个分割发生时,新建立的子Region不会当即将全部数据从新写入新文件。它们建立相似于符号连接文件的小文件,命名为Reference文件,它们根据分割点,指向父存储文件的顶部或底部部分。引用文件就像常规的数据文件同样,可是只有一半的记录。若是没有对父Region中的不可变数据文件的引用,这个子Region就能够被分割了。那些Reference文件被紧缩操做逐渐清理,以至于这个子Region将中止对它的父文件的引用,而后它就能够被进一步分割了
尽管分割Region是由RegionServer作出的本地决策,但分割过程自己必须与许多参与者协调。在分割Region以前和以后,RegionServer通知Master更新 .META. 表,以使客户端可以发现新的子Region,而且RegionServer在HDFS上从新安排目录结构和数据文件。分割(也叫作分裂)是一个由多个任务组成的过程。要启用在错误时回滚的功能,RegionServer在内存中保留一个关于执行状态的journal。RegionServer执行分割Region的步骤在RegionServer Split Process中阐述。每一步都标注了一个步骤号,RegionServers和Master内的action标红,client的action标绿。
RegionServer决定分割Region,并为分割作准备。分割事务就开始了。做为第一步,RegionServer获取表上共享的读锁,以防止在分割过程当中对schema进行修改。而后它在ZooKeeper的 /hbase/region-in-transition/region-name 下建立一个znode,并设置这个znode的状态为
SPLITTING。
Master经过父Region的region-in-transition znode的watcher了解到在步骤1中建立的znode。
RegionServer在HDFS中的父region的目录下建立名为“.split”的子目录。
RegionServer关闭父region,并强制刷新缓存内的数据,以后在本地数据结构中将标识为下线状态。正在被分割的REGION如今下线了。此时来自Client的对父region的请求会抛出NotServingRegionException ,Client将经过重试机制而从新尝试发送请求。博主注:backoff是一种重试策略,是指失败后多长时间进行重试。
RegionServer在步骤3中建立的.split目录下为子regionA和B建立目录和相关的数据结构。而后RegionServer分割store文件,这种分割是指,为父region的每一个store文件建立两个Reference文件。这些Reference文件将指向父region中的文件。
RegionServer在HDFS中建立实际的region目录,并移动每一个子region的Reference文件。
RegionServer向.META.表发送Put请求,并在.META.中将父region改成下线状态,添加子region的信息。此时表中并单独存储没有子region信息的条目。Client扫描.META.时会看到父region为分裂状态,但直到子region出如今.META.表中,Client才知道他们的存在。若是Put请求成功,那么父region将被有效地分割。若是在这条RPC成功以前RegionServer死掉了,那么Master和打开这个region的下一个RegionServer会清理关于该region分裂的脏状态。在.META.更新以后,region的分裂将被Master前滚。
RegionServer打开子region,并行地接受写请求。
RegionServer将子region A和B的相关信息和它存储这两个Region的信息写入.META.。分割好的Region(含有对父Region引用的子Region)如今上线了。此后,Client即可以扫描到新的region,而且能够向其发送请求。Client会在本地缓存.META.的条目,但当她们向RegionServer或.META.发送请求时,这些缓存便无效了,他们竟从新学习.META.中新region的信息。
RegionServer将zookeeper中 /hbase/region-in-transition/region-name下的znode更改成SPLIT状态,以便Master能够监测到。若是有须要,Balancer能够自由地将子region分派到其余RegionServer上。分割事务如今结束了。
分裂以后,.META. 和HDFS中依然包含着指向父region的引用。这些引用将在子region发生紧缩操做重写数据文件时被删除掉。Master中的垃圾回收任务会周期性地检测子Region是否仍指向父region的文件,若是没有,将删除父region。
Write Ahead Log (WAL)记录了HBase中数据的全部变化,把这些变化基于文件进行存储。在正常操做下,并不须要WAL,由于数据更改从MemStore转移到storefile。可是,若是在MemStore刷新以前,某个RegionServer崩溃或变得不可用,那么WAL将确保对数据的修改能够从新进行。若是向WAL写入失败,那么修改数据的整个操做就会失败。
HBase使用WAL 接口的一个实现。一般状况下,一个RegionServer中,一个WAL只有一个实例。在把Puts和Deletes操做记录到受影响的Store对应的MemStore 以前,RegionServer把Puts和Deletes操做记录到WAL中。
![]() |
The HLog
在HBase2.0以前, HBase中WALs的接口名为HLog。在 0.94, HLog 是WAL的实现的名字。你将可能在旧版本的文档中找到HLog的内容。 |
WAL 保存在HDFS 的 /hbase/WALs/ 目录里(在HBase0.94 之前,它们被存储在/hbase/.logs/中),每一个region有一个子目录。
要想知道更多的信息,能够访问维基百科 Write-Ahead Log 的文章.
每一个RegionServer有一个单一的WAL,RegionServer必须连续地向WAL中写入,由于HDFS文件必须是连续的。这会致使WAL成为性能瓶颈。
HBase1.0在HBASE-5699中引入了对多个WAL(MultiWAL)的支持。MultiWAL经过在底层的HDFS实例中使用多个管道,容许一个RegionServer并行地往多个WAL流里写,这增长了写入期间的总吞吐量。这个并行是根据表的分区,进而根据表的Region实现的。所以,当前的实现对单个Region增长吞吐量没有帮助。
使用原有的WAL实现和使用MultiWAL实现的RegionServer均可以处理任意一种WALs的恢复,所以,经过依次重启能够实现零停机配置更新。(不明白这个结论的缘由)
RegionServers using the original WAL implementation and those using the MultiWAL implementation can each handle recovery of either set of WALs, so a zero-downtime configuration update is possible through a rolling restart.?????
要为一个RegionServer配置MultiWAL,按下面的XML设置属性 hbase.wal.provider
为 multiwal
便可:
<property> <name>hbase.wal.provider</name> <value>multiwal</value> </property>
重启RegionServer使配置更改生效。
要在一个RegionServer上禁用MultiWAL, 不设置这个属性而后重启这个RegionServer便可。
TODO (describe).
一个RegionServer能够给许多Region提供服务。一个RegionServer中全部的Region共享相同的激活状态下的WAL文件。在这个WAL文件的每一次编辑都包含关于此次编辑属于哪一个Region的信息。当一个Region被打开时,在WAL文件里属于这个Region的编辑须要被重作。所以,WAL文件中的编辑必须按Region分组,以便特定编辑的集合能够在特定的Region内被重作以从新生成数据。根据Region将WAL编辑分组的过程被称做日志分割(log splitting)。若是RegionServer失败,它是恢复数据的一个关键的过程。
日志分割由HMaster在集群启动时完成,或者由ServerShutdownHandler在RegionServer关闭时完成。因此一致性是能够保障的,受影响的Regions直到数据恢复以前处于不可用状态。在一个给定的Region再次成为可用状态以前,全部的WAL编辑内容须要被恢复和重作。所以,在这个过程完成以前,受日志分割影响的Region是不可用的。
目录/hbase/WALs/<host>,<port>,<startcode> 被重命名
重命名这个目录是重要的,由于一个RegionServer可能仍处于启动状态并在接受请求即便HMaster认为它已经宕了。若是这个RegionServer没有即便响应,而且没有和它的ZooKeeper会话保持心跳,HMaster可能会认为这个RegionServer失败了。重命名日志目录会确保存在这样的状况,仍被一个忙碌但活的RegionServer使用的合法的WAL文件不能被偶然的写入。
新的目录根据如下模式被命名:
/hbase/WALs/<host>,<port>,<startcode>-splitting
一个重命名目录的例子以下:
/hbase/WALs/srv.example.com,60020,1254173957298-splitting
每个日志文件都会被分割,一次一个。
日志分割器每次读取日志文件的一个条目,并把每一个编辑条目放到和这个编辑条目的Region相对应的缓冲区中。同时,日志分割器启动几个写入器线程。写入器线程获取相应的缓冲区,并将缓冲区中的编辑条目写入一个临时恢复的编辑文件。这个临时的编辑文件如下面的命名方式存储在磁盘上:
/hbase/<table_name>/<region_id>/recovered.edits/.temp
该文件用于存储WAL日志中属于该Region的的全部编辑条目。在日志分割完成后,这个.temp文件被重命名为写入到这个文件的第一条日志的序列ID(sequence ID)。
为了肯定是否全部的编辑都已经被写入了,新的文件名中的序列ID将与被写入到HFile的最后一个编辑条目的序列ID进行比较。若是最后编辑条目的序列ID大于等于文件名中的序列ID,对这个文件的全部的写操做就已经完成了。(博主注:不明白为何比较文件名中的序列ID <= 文件中的最后一条序列ID,就能证实全部的条目都写入成功了)
日志分割完成后,每个受影响的Region会被分配给一个RegionServer
当Region打开时,检查recovered.edits文件夹中恢复的编辑文件。若是存在这样的文件,经过读取编辑条目并把编辑条目存储到MemStore,它们会被重作。当全部的编辑文件都被重作以后,MemStore中的内容被写入磁盘(HFile),编辑文件被删除。
若是你把 hbase.hlog.split.skip.errors
设置为 true
, 错误会被以下处理:
分割期间所遇到的任何错误都会被记录。
这个出问题的WAL日志会被移动到HBase rootdir下的 .corrupt 目录中
对WAL的处理过程将继续
若是 hbase.hlog.split.skip.errors
option 被设置为 false
, 这是默认的设置, 异常将被抛出,这个分割操做被记录成failed。参考 HBASE-2958 When hbase.hlog.split.skip.errors is set to false, we fail the split but that’s it。 若是设置了这个标志,咱们须要作的不只仅是使分割操做失败。
若是一个EOFException发生在分割日志时,分割操做继续进行,即便 hbase.hlog.split.skip.errors
被设置成 false。在读取要分割的日志文件集中的最后的日志时,可能会遇到
EOFException 由于RegionServer可能在崩溃时正在写入一条记录。关于这个背景,参考 HBASE-2643 Figure how to deal with eof splitting logs
WAL日志分割和恢复多是资源密集型的而且须要很长时间,这取决于崩溃的RegionServer的数量和Regions的大小。[distributed.log.splitting]被开发出来用以改善日志分割时的性能。
分布式日志处理从HBase 0.92开始默认为启用。这个设置由属性 hbase.master.distributed.log.splitting
控制, 能够被设置成 true
或 false
, 但默认为 true。
配置了分布式日志分割以后,HMaster控制这个过程。HMaster把每个RegionServer记入到日志分割的过程,实际的分割日志的工做由RegionServer去作。非分布式日志分割的通常过程,如在 Distributed Log Splitting, Step by Step 描述的那样,也适用于分布式日志分割。
若是分布式日志分割被启用,HMaster在集群启动时建立一个 分割日志管理器 实例。
这个分割日志管理器 管理全部的须要被扫描和分割日志文件。
这个分割日志管理器 把全部的日志做为任务放到ZooKeeper的 splitlog node (/hbase/splitlog) 。
你能够经过下列zkCli命令查看splitlog的内容。输出的例子以下。
ls /hbase/splitlog [hdfs%3A%2F%2Fhost2.sample.com%3A56020%2Fhbase%2F.logs%2Fhost8.sample.com%2C57020%2C1340474893275-splitting%2Fhost8.sample.com%253A57020.1340474893900, hdfs%3A%2F%2Fhost2.sample.com%3A56020%2Fhbase%2F.logs%2Fhost3.sample.com%2C57020%2C1340474893299-splitting%2Fhost3.sample.com%253A57020.1340474893931, hdfs%3A%2F%2Fhost2.sample.com%3A56020%2Fhbase%2F.logs%2Fhost4.sample.com%2C57020%2C1340474893287-splitting%2Fhost4.sample.com%253A57020.1340474893946]
输出含有一些 non-ASCII 字符。解码后, 他看起来更简单:
[hdfs://host2.sample.com:56020/hbase/.logs/host8.sample.com,57020,1340474893275-splitting/host8.sample.com%3A57020.1340474893900, hdfs://host2.sample.com:56020/hbase/.logs/host3.sample.com,57020,1340474893299-splitting/host3.sample.com%3A57020.1340474893931, hdfs://host2.sample.com:56020/hbase/.logs/host4.sample.com,57020,1340474893287-splitting/host4.sample.com%3A57020.1340474893946]
清单表示要扫描和拆分的WAL文件名,这是一个日志分割任务列表。
分割日志管理器监控日志分解任务和workers。
分割日志管理器负责如下正在执行的任务:
一旦分割日志管理器将全部任务发布到splitlog znode,它就会监视这些任务节点,并等待它们被处理。
检查是否有死的分割日志的worker排队。若是它发现由没有响应的worker所声明的任务,它将从新提交这些任务。若是因为某些ZooKeeper的异常而从新提交失败,则dead worker将再次排队等待重试。
检查是否有未分配的任务。若是发现有未分配的任务,它就建立一个临时的重扫描节点(rescan node),以便每一个分裂日志的worker经过nodeChildrenChanged的ZooKeeper事件被通知去从新扫描未分配的任务。
检查分配但过时的任务。若是找到,它们会被再次设置成TASK_UNASSIGNED
state 以便它们可一个被重试。这些“过时的”任务有可能被分配给慢的workers,或者可能已经完成了。这是没问题的,由于日志分割任务具备幂等性。换句话说,相同的日志分割任务能够屡次处理,而不会形成任何问题。
分割日志管理器时常监视HBase的分割日志znodes。若是有任何分裂的日志任务节点数据被更改,那么分割日志管理器将获取节点数据。节点数据包含任务的当前状态。你能够使用 zkCli
的get
命令去获取一个任务的当前状态(state)。在下面的示例输出中,输出的第一行显示当前任务是未分配的。
get /hbase/splitlog/hdfs%3A%2F%2Fhost2.sample.com%3A56020%2Fhbase%2F.logs%2Fhost6.sample.com%2C57020%2C1340474893287-splitting%2Fhost6.sample.com%253A57020.1340474893945 unassigned host2.sample.com:57000 cZxid = 0×7115 ctime = Sat Jun 23 11:13:40 PDT 2012 ...
根据数据被更改的任务的状态,分割日志管理器会执行下列之一的操做:
若是未分配任务,则从新提交任务。
若是任务被分配,则经过心跳监控该任务的执行。(Heartbeat the task if it is assigned)
若是任务被委托,则从新提交或使该任务失败。 (参考 Reasons a Task Will Fail)
若是任务完成了但有错误,则从新提交或使该任务失败 (参考 Reasons a Task Will Fail)
若是任务因错误不能完成,则从新提交或使该任务失败 (参考 Reasons a Task Will Fail)
若是任务已经成功完成或失败了,则删除该任务。
每一个RegionServer的负责分割日志的worker去执行日志分割任务。
每一个RegionServer运行一个被称做分割日志工做器(split log worker)的守护线程,由这个线程作分割日志的工做。当RegionServer启动时,守护线程将启动,并注册自身以监视HBase znode。若是任何一个splitlog znode的子节点发生变化,它就会通知一个正在休眠的工做线程把它唤醒并让它获取更多的任务。若是一个worker当前的任务节点数据发生变化,这个worker将检查这个任务是否被被另外一个worker处理。若是是这样的,这个worker线程将中止在这个当前任务上的工做。
worker不断地监视splitlog znode。当出现一个新任务时,分割日志工做器将获取任务路径并检查每一个任务路径,直到找到一个未声明的任务,并试图声明该任务。若是声明成功,它将尝试执行任务,并根据分割结果更新任务的状态(state)属性。此时,分割日志的worker会扫描另外一个未声明的任务。
它查询任务状态,只有在任务处于`TASK_UNASSIGNED `状态时才会工做.
若是任务处于 TASK_UNASSIGNED
状态, worker尝试本身把任务的状态设置成 TASK_OWNED
。若是设置状态失败, 另外一个worker将会尝试捕获这个任务。若是这个任务仍旧为unassigned状态,事后,分割日志管理器将会要求全部的worker去从新扫描。
若是worker成功地获取这个任务的全部权,它会尝试再次获取这个任务的状态,以确保它真正地得到异步。If the worker succeeds in taking ownership of the task, it tries to get the task state again to make sure it really gets it asynchronously. 同时,它启动了一个拆分任务执行器(split task executor)来完成实际工做。
获取HBase root文件夹,在root文件夹下建立一个临时文件夹,分割日志文件到这个临时文件夹中。
若是分割成功,这个task executor会把这个任务的状态设置为 TASK_DONE。
若是worker捕获了一个预期外的IOException, 这个任务的状态被设置为 TASK_ERR
。
若是worker正在关闭,会设置这个task的状态为 TASK_RESIGNED。
若是这个任务被另外一个worker取得,只记录这件事便可。
分割日志管理器监视未完成的任务。
当全部的任务都成功完成后,分割日志管理器返回。若是全部的任务都完成了,但有一些失败,则分割日志管理器抛出一个异常,以便日志分割能够被重试。由于是一个异步实现,在及其罕见的状况下,分割日志管理器会失去对一些已完成任务的跟踪。出于这个缘由,它会按期检查Task Map或ZooKeeper里未完成的任务。若是没有找到,它会抛出一个异常,这样就能够当即重试日志分割,而不是挂在那里等待发生不会发生的事情。(博主注:这句话不理解,既然前面说失去对已完成任务的跟踪,为何后面要去找未完成的任务,并且既然没有找到未完成任务,为何要重试呢)
在一个RegionServer失败之后,它的失败的Regions会被分配到其余RegionServer中去,这些Region在ZooKeeper里被标记成 "recovering"。一个分割日志worker直接把失败的RegionServer的WAL中的编辑条目重作到这些在的新位置上的Regions中。当一个Region处于"recovering" 状态时, 它能接受写但不接受 读(包括Append和Increment)、Region分割或合并。
分布式日志重作继承了 [distributed.log.splitting] 框架。它直接把WAL重作到另外一个 RegionServer上,而不是建立 recovered.edits 文件。对比分布式日志分割,它提供了如下优势:
它消除了读写recovered.edits 文件中大量数据的开销。在RegionServer恢复的过程当中,成千上万的recovered.edits文件被建立和写入是很常见的。许多小的随机写入能够下降整体系统性能。
即便在一个Region处于恢复状态时,它也容许写操做。恢复中的Region只须要几秒钟就能够再次接受写操做。
要启用分布式日志重作,把 hbase.master.distributed.log.replay
设置成 true
. 在HBase 0.99中,这是默认配置 (HBASE-10888).
你还必需要启用HFile版本数为3(从HBase 0.99开始,这是默认的HFile格式。参考 HBASE-10855)。 分布式日志重作对于滚动升级是不安全的。
为了在某些特殊状况下改善性能,禁用WAL是可能的。然而,禁用WAL会把你的数据置于风险之中。推荐的禁用WAL的状况,只有在作批量加载(Bulk Load)的期间。这是由于,在出现问题时,能够从新运行批量加载,不存在数据丢失的风险。
经过调用HBase客户端中 field 的Mutation.writeToWAL(false)能够禁用WAL。使用
Mutation.setDurability(Durability.SKIP_WAL)
和 Mutation.getDurability() 方法来设置和获取域(field)的值。只是一个指定的表的话,没法禁用 WAL。
![]() |
若是你在批量加载之外的状况下禁用WAL,你的数据会处于风险之中。 |
Regions对于表来讲,是可用和分布的基本要素,它由每一个列族的Store组成。这些对象的层级关系以下:
Table (HBase table) Region (Regions for the table) Store (Store per ColumnFamily for each Region for the table) MemStore (MemStore for each Store for each Region for the table) StoreFile (StoreFiles for each Store for each Region for the table) Block (Blocks within a StoreFile within a Store for each Region for the table)
关于HBase文件再写入HDFS时是什么样子的描述,参考 Browsing HDFS for HBase Objects.
通常来讲,HBase的设计目的是在每一个服务器上运行一组少许的(20-200)Region,且Region相对较大(5-20gb)。对此的考虑以下:
一般,您想在HBase上保持Region数量的低水平,缘由有不少。一般,每一个RegionServer大约有100个Region会产生最好的结果。如下是保持Region数量低的一些缘由:
MSLAB (MemStore-local allocation buffer) 每一个MemStore须要 2MB (每一个Region中的每一个列族要 2MB缓存)。那么1000个Region、且每一个Region含有2个列族就须要3.9G的堆内存,而且尚未存储数据。注意: 这个2MB 的值是能够配置的。
若是你以某种程度上相同的速率去填充全部的Region,那么全局内存使用(global memory usage)会在因Region太多而依次产生紧缩的时候产生微小的刷新。屡次重复写相同的数据是你最不想要的事情。例如,一个例子是公平地向1000个Region(每一个Region有一个列族)中填充数据,让咱们考虑一个更低的范围来使用5GB的全局MemStore可用(该RegionServer有一个大的堆)。一旦填充的数据量达到5GB,它就会强制刷新数据量最大的Region,在那时这些Region都含有将近5M的数据,因此全局MemStore会刷新这个量的数据。5M数据刷新到文件后,会去刷新另外一个Region,这个Region这会儿有5MB多一点儿的数据,如此等等。这是目前Region数量的主要限制因素;参考 Number of regions per RS - upper bound 获取更详细的公式。
Master原本就对大量的Region过敏,它将花费大量的时间分配这些Region而且批次量地移动这些Region。缘由是它在ZK的使用上很重,并且如今还不是很异步(已经在HBase0.96被改善了)。
在旧版HBase(pre-HFile v2, 0.90 and previous)中,大量的Region放置在不多量的RegionServer上可能引起存储文件索引的增加、增长堆内存的使用、潜在地形成RegionServer的内存压力或OutOfMemoryException。
另外一个问题是Region数量对MapReduce做业的影响;典型的是每一个HBase Region有一个mapper。所以,一个RegionServer有5个Region可能对一个MapReduce做业来讲不能知足任务的数量,而有1000个Region将会生成很是多的任务。
参考 Determining region count and size 获取配置指导。
本节描述了Region是怎样被分配到RegionServer上的。
当HBase启动时,Region被以下分配(这是简短的描述):
Master启动时调用 AssignmentManager
。
AssignmentManager
在hbase:meta中查看现有的Region分配状况
若是Region的分配依然有效(例如,若是Region被分配到的RegionServer依然在线),则保持这个分配安排。
若是Region的分配无效了,则LoadBalancerFactory
被调用以分配Region。负载均衡器(Load Balancer) (HBase1.0中默认是StochasticLoadBalancer
) 分配Region到RegionServer中去。
把分配到的RegionServer(若是有须要的话)和这个RegionServer的启动代码(RegionServer进程的启动时间)更新到hbase:meta
中。 and the RegionServer start codes (start time of the RegionServer process) upon region opening by the RegionServer.
当一个 RegionServer 失效:
RegionServer中的 regions 当即成为不可用状态,由于这个 RegionServer 宕了.
Master 将检测到这个RegionServer已经失效了。
Region原有的分配失效,并采用跟启动时一样顺序的步骤从新分配Region
正在进行的查询操做会从新执行,不会丢失。
Region切换到新RegionServer动做要在如下时间内完成:
ZooKeeper session timeout + split time + assignment/replay time
Region能够被 LoadBalancer 按期移动。
HBase为每一个Region维护一个状态,并把这个状态持久化到 hbase:meta。
hbase:meta
Region 的状态自己被持久化到 ZooKeeper中。你能够在Master Web UI中看到Region状态的转变。如下是Region可能的状态的清单。
OFFLINE
: Region处于offline 且没有打开
OPENING
: Region处于正在被打开的过程当中
OPEN
: Region已经打开了,RegionServer已经通知了 Master
FAILED_OPEN
: RegionServer没能打开
CLOSING
: Region处于正在被关闭的过程
CLOSED
: RegionServer已经关闭了 Region,并通知了Master
FAILED_CLOSE
: RegionServer没能关闭 Region
SPLITTING
: RegionServer 通知了 Master,Region正在分裂(分割 split)
SPLIT
: RegionServer 通知了 Master,Region已经分裂完毕
SPLITTING_NEW
: 这个Region正在被一个处理中的分割操做建立(this region is being created by a split which is in progress)
MERGING
: RegionServer通知了 Master,这个Region正在与另外一个Region合并(Merge)
MERGED
: RegionServer通知了 Master,这个Region已经被合并了。
MERGING_NEW
: 这个Region正在被两个Region的合并操做(Merge)操做建立
棕色: Offline 状态, 是一个特殊的状态,它能够是瞬间的状态 (Closed以后,Opening以前), 也能够是一个最终的状态 (被禁用表的Regions), 或者是一个初始的状态 (新建表的Regions)
淡绿色: Online 状态,代表这个状态的Regions能够为请求提供服务
淡蓝色: Transient(瞬间)状态
红色: Failure(失败)的状态,须要OPS注意。什么是OPS?
金色: 分裂或合并的Regions的最终状态
灰色: 经过分裂或合并所建立的Region的初始状态
Master控制把一个region从OFFLINE的状态迁移到OPENING 的状态,而且尝试把该region分配到一个regionServer上。RegionServer可能会也可能不会接收到打开Region的请求。直到RPC到达RegionServer或者Master的重试达到最大次数(11次)以前,master会一直尝试发送打开Region的请求。等到RegionServer接收到打开Region的request以后,就会开始打开region。
若是master超过请求重试的次数,master经过把这个Region的状态迁移到CLOSING并尝试关闭它,以制止RegionServer打开Region,即便RegionServer已经开始打开这个Region。(注意,这里即便某个regnionserver已经开始打开该region,也会强制再中止的)。
在RegionServer打开Region以后,它会一直尝试通知Master,直到Master把该Region的状态更改成OPEN而且通知全部的RegionServer。这个Region如今是打开的状态了。
若是RegionServer不能打开Region, 它通知Master。Master把Region的状态迁移为CLOSED
状态,而且尝试在其它RegionServer里打开这个Region。
若是Master不能在必定数量的RegionServer上打开Region,它会把Region的状态迁移为 FAILED_OPEN,直到hbase shell发出相关的命令(如 从新assign)或者在该服务死掉,对这个Region将再也不会有任何操做。
Master会把Region从OPEN状态迁移到CLOSING的状态。这个Region所在的RegionServer可能会也可能不会收到关闭Region的请求。直到RPC到达RegionServer或者Master的重试达到最大次数以前,master会一直向这个RegionServer尝试发送关闭Region的请求。
若是RegionServer不在线, 或者抛出 NotServingRegionException
, Master 把Region的状态迁移为 OFFLINE
,而且把它从新分配给其余的 RegionServer。
若是RegionServer在线, 可是在Master重试屡次后仍然不可达,Master会把该Region迁移为 FAILED_CLOSE
的状态,而且直到HBase shell发出命令(如 从新assign),或者在该服务死掉,对这个Region将再也不会有任何操做。
若是Regionserver获得关闭Region的请求,它会关闭Region,而且通知Master。Master会把该Region标记为 CLOSED
的状态,而且会把它从新分派到其它的RegionServer中去。
在分派一个Region以前,Master会自动把一个CLOSED状态的Region标记为OFFLINE的状态。
当一个RegionServer准备split一个Region,它会通知Master。Master会把将要分割的Reigon从 OPEN
状态迁移动到 SPLITTING 状态,而后把这两个要建立的Region添加到RegionServer中去。这两个Region被初始化成
SPLITTING_NEW
的状态。
通知了Master之后,RegionServer开始分割Region。若是返回超时,RegionServer会再次通知Master以便Master来更新hbase:meta表。可是Master在被RegionServer通知分割已经完成以前,Master不会更新Region的状态。若是Region分割成功,正在被分割的Region会从 SPLITTING 状态切换到 SPLIT 状态,而后被分割出来的两个新Region会从SPLITTING_NEW切换到OPEN状态。
若是分割失败,被切分的Region从 SPLITTING 状态切换回 OPEN 状态,而后两个新建立出来的Region的状态从SPLITTING_NEW被切换到OFFLINE的状态。
若是RegionServer要合并两个Region,会先通知Master。Master会把要合并的两个Region从 OPEN 迁移到 MERGING 的状态,而后增长一个新的Region到RegionServer,这个新的Region将用来保存被合并的Regions的内容。这个新的Region 的初始状态是MERGING_NEW。
通知了Master以后,RegionServer开始合并两个Region。一旦超时,RegionServer会再次通知Master以便Master来更新hbase:meta表(META)。可是Master在被RegionServer通知合并已经完成以前,Master不会更新Region的状态。若是合并成功,两个正在合并的region会从 MERGING 迁移到 MERGED 状态,而新的Region会从 MERGING_NEW 迁移到 OPEN 状态。
若是合并失败,两个须要合并的Region会从 MERGING 迁移回 OPEN 的状态,而后被建立用来保存要合并的Region的内容的新的Region从 MERGING_NEW 迁移到 OFFLINE 状态。
对于处于 FAILED_OPEN 或者 FAILED_CLOSE 状态的Regions,当它们被HBase shell中的命令从新分配时,Master会再次尝试关闭这些节点。
随着时间的推移,Region-RegionServer的位置选择经过HDFS的块复制机制完成。HDFS客户端在选择写入副本的位置时,默认步骤以下:
第一个副本写在本地节点
第二个副本写在另外一个机架上的任意节点
第三个副本写在与第二个节点相同的机架上的另外一个任意的节点
后续的副本将写到集群中的任意节点中。 参考 Replica Placement: The First Baby Steps on this page: HDFS Architecture
所以,HBase Region在刷新或紧缩(Compaction)后最终达到某个位置,即选址是在flush或compaction以后执行的。在一个RegionServer失效转移的状况下,被分配到另外一个RegionServer的Regions的StoreFiles可能并不在这个RegionServer本地(由于没有副本在本地),可是当新数据被写入到这个Region,或者表被紧缩(Compact),StoreFiles被重写时,数据文件块的副本对于这个RegionServer将会“本地化”。
获取更多信息,参考 Replica Placement: The First Baby Steps on this page: HDFS Architecture and also Lars George’s blog on HBase and HDFS locality.
当Region到达设置的阈值时发生分裂。如下咱们简短地介绍这个话题。更详细的阐述,参考Enis Soztutar所作的 Apache HBase Region Splitting and Merging
Region分裂由RegionServer单独运行,Master不参与。RegionServer分割一个Region,使被分割的Region下线,而后给它添加两个子Region到 hbase:meta
中,在这个父Region所在的RegionServer中打开子Region,而后向Master报告分割操做。参考 Managed Splitting 获取如何手动控制Region分割 (以及你为何要这么作)。
你能够使用一个自定义的RegionSplitPolicy(HBase 0.94+)复写默认的分割策略。典型的一个自定义分割策略应该继承HBase默认的分割策略: IncreasingToUpperBoundRegionSplitPolicy。
策略能够设置成HBase全局的配置,也能够设置成基于表的配置
在hbase-site.xml中配置全局分割策略
<property> <name>hbase.regionserver.region.split.policy</name> <value>org.apache.hadoop.hbase.regionserver.IncreasingToUpperBoundRegionSplitPolicy</value> </property>
使用Java API 给一个表配置分割策略
HTableDescriptor tableDesc = new HTableDescriptor("test"); tableDesc.setValue(HTableDescriptor.SPLIT_POLICY, ConstantSizeRegionSplitPolicy.class.getName()); tableDesc.addFamily(new HColumnDescriptor(Bytes.toBytes("cf1"))); admin.createTable(tableDesc); ----
使用HBase Shell给一张表配置一个分割策略
hbase> create 'test', {METHOD => 'table_att', CONFIG => {'SPLIT_POLICY' => 'org.apache.hadoop.hbase.regionserver.ConstantSizeRegionSplitPolicy'}}, {NAME => 'cf1'}
默认的分割策略能够用自定义的 RegionSplitPolicy(HBase 0.94+) 复写。典型的一个自定义分割策略应该继承 HBase默认的分割策略: ConstantSizeRegionSplitPolicy。
这个策略能够经过HBaseConfiguration被设置成全局的,或者只针对一张表的:
HTableDescriptor myHtd = ...; myHtd.setValue(HTableDescriptor.SPLIT_POLICY, MyCustomSplitPolicy.class.getName());
![]() |
策略 DisabledRegionSplitPolicy 会阻止手动分割Region。 |
手动分割你的表是能够的,能够在建立表的时候(预分割),也能够在之后的管理操做中。你选择手动分割你的Region可能会由于如下理由中的一个或多个。也可能会有其余合理的理由,可是手动分割表的须要也可能会代表你的schema设计有问题。
手动分割表的理由
你的数据按时间顺序或其它相似的算法(把新数据排到表的结尾)排序。这意味着保存着最后的Region的RegionServer一直有数据加载进来,而其它的RegionServer会空闲。参考 Monotonically Increasing Row Keys/Timeseries Data.
在你的表的一个Region中,你已经开发出了一个意想不到的热点。例如,一个跟踪网络搜索的应用程序可能会被针对一个名人大量搜索出新闻所淹没。参考 perf.one.region 获取更多的关于这种情形的讨论。
在集群中的RegionServer的数量大幅增长以后,就能够快速地展开负载。
bulk-load可能会引发Region间不一样寻常和不均匀的负载,在bulk-load以前你可能须要手动分割Region
参考 Managed Splitting 获取关于彻底手动管理分割所带来的危险和可能的收益的讨论。
![]() |
DisabledRegionSplitPolicy 会阻止手动分割Region |
手动分割你的表的目的是为了在只经过良好的行键设计并不能达到这个平衡负载的状况下,提升在集群中平衡负载的机会。记住这一点,你分割Region的方式很是依赖于数据的特征。也许你已经知道分割你的表的最好的方法了,若是没有,分割你的表的方法要依赖于你的行键。
若是你的行键以字母或数字开头,你能够在字母或数字边界上分割你的表。例如,下面的命令建立了一个有多个Region的表,这些Region以元音做为分割点,因此第一个Region有A-D,第二个Region有 E-H, 第三个Region有 I-N, 第四个Region有 O-V, 第五个Region有 U-Z.
HBase提供了工具 RegionSplitter,使用一个 SplitAlgorithm 来决定你的分割点。做为参数,你给它一个算法,想要的Region数和列族。它包含了2个分割算法。第一个是 HexStringSplit
算法, 这个算法假定Row keys十六进制字符串。第二个, UniformSplit
, 假定Row keys 是随机的字节数组。你可能须要使用提供的这些参数做为模型开发一个属于本身的SplitAlgorithm
。
Master和RegionServer都参与了在线合并Region。客户端向Master发出合并操做的RPC,而后Master把要合并的Region一块儿移动到这些Region中数据量更大的Region所在的RegionServer。最后,Master向这个RegionServer发出merge请求,这个RegionServer是要运行merge操做的。和Region分裂的过程相似,Region合并在RegionServer上以本地事务的方式运行。它下线要merge的Region,而后在文件系统上合并这两个Region,原子地从 hbase:meta
中删除被合并的Region的信息,把合并操做新生成 Region 添加到 hbase:meta 中
,在这个RegionServer上打开这个新的Region并把合并的状况报告给Master。
在HBase shell中合并Region的例子
$ hbase> merge_region 'ENCODED_REGIONNAME', 'ENCODED_REGIONNAME' $ hbase> merge_region 'ENCODED_REGIONNAME', 'ENCODED_REGIONNAME', true
它是一个异步操做,而且在不等待合并完成的状况下当即调用返回。
传递 true
做为第三个参数值(可选的)给上述命令,将会强制一个合并(merge)。一般只有相邻的区域能够合并。force
参数复写了这个行为,仅为专家使用。
一个存储包含了一个内存存储(MemStore)和0或多个文件存储(StoreFile--HFile)。一个存储能够定位到一个Region的表中的一个列族。
MemStores是Store中的内存Store,能够进行修改操做。修改的内容是Cell/KeyValues。当flush操做被请求时,现有的memstore会生成快照,而后被清空。HBase继续为重新的MemStore的编辑信息提供服务,而且直到flusher报告刷新成功以前,保存快照。flusher报告刷新成功以后,快照才被删除。注意,当刷新发生时,属于同一个Region的MemStores都会被刷新。
能够在下面列出的任何条件下触发MemStore刷新。最小的刷新单元是一个个Region,而不是在单独的MemStore层。
MemStore的大小达到 hbase.hregion.memstore.flush.size
, 全部属于这个MemStore的Region下的MemStores都将被刷新到磁盘。
RegionServer中全部MemStore的使用率超过RegionServer中MemStore上限值(由 hbase.regionserver.global.memstore.upperLimit指定)
,则该RegionServer上不一样Region的MemStore都会被刷新到磁盘上,以减小所有的MemStore使用量。
刷新的顺序是以Region的MemStore使用量倒序排列的。
Regions将会把它们的MemStore刷新到磁盘,知道所有MemStore使用率降到或略小于 hbase.regionserver.global.memstore.lowerLimit。
当RegionServer中的WAL日志条目数量达到 hbase.regionserver.max.logs时
, RegionServer上的不一样的Region的将会被刷新到磁盘以减小WAL中日志的数量。
刷新顺序是基于时间的。
有时间最先的MemStore的Region最新被刷新,直到WAL的数量降低到 hbase.regionserver.max.logs。
当客户端发出在一个表上的扫描时,HBase产生 RegionScanner
对象, 每一个Region一个,用来服务于扫描请求。
RegionScanner
对象包含一个 StoreScanner
对象的列表,每一个列族一个。
每一个 StoreScanner
对象进一步包含了一个 StoreFileScanner
对象的列表,对应每一个列族的 StoreFile 和 HFile,还包含了一个MemStore中 KeyValueScanner
对象的列表。
这两个列表合并为一个,它以升序排序,顺序是以列表结尾的MemStore的扫描对象进行排列的。
当一个 StoreFileScanner
对象被构造,它就和一个 MultiVersionConcurrencyControl
的读取位置(read point)有关, 这个位置就是当前的 memstoreTS,过滤掉在这个读取位置之后任意新的更新。
StoreFiles 是数据存在的地方。
HFile文件格式是基于BigTable [2006]论文中的SSTable,以及构建在Hadoop上的TFile(单元测试suite和压缩工做能够直接从TFile中获取)。 Schubert Zhang 的博客HFile: A Block-Indexed File Format to Store Sorted Key-Value Pairs详细介绍了HBase的HFile。Matteo Bertozzi也提供了有帮助的介绍HBase I/O: HFile。
获取更多信息,参考 HFile source code。也能够参考 HBase file format with inline blocks (version 2) 获取关于 0.92 引入的HFile v2 格式的信息。
要想看到hfile内容的文本化版本,你能够使用 org.apache.hadoop.hbase.io.hfile.HFile
工具。能够这样用:
$ ${HBASE_HOME}/bin/hbase org.apache.hadoop.hbase.io.hfile.HFile
例如,你想看文件 hdfs://10.81.47.41:8020/hbase/TEST/1418428042/DSMP/4759508618286845475, type the following:
$ ${HBASE_HOME}/bin/hbase org.apache.hadoop.hbase.io.hfile.HFile -v -f hdfs://10.81.47.41:8020/hbase/TEST/1418428042/DSMP/4759508618286845475
若是你没有输入-v,就仅仅能看到一个hfile的汇总信息。其余功能的用法能够看HFile
的文档。
关于StoreFiles在HDFS上是什么样子、目录结构如何的信息参考 Browsing HDFS for HBase Objects。
StoreFiles 由块组成。块大小是基于每一个列族配置的。
压缩发生在StoreFiles的块级别上。更多关于压缩的信息,参考 Compression and Data Block Encoding In HBase。
更多关于块的信息,参考 HFileBlock source code.
KeyValue 类 是数据存储在HBase的关键。KeyValue包装了一个字节数组,并将偏移量和长度做为传递给数组,指定了从哪里开始是KeyValue的内容。
字节数组中的KeyValue格式:
keylength
valuelength
key
value
Key被进一步分解为
rowlength
row (i.e., the rowkey)
columnfamilylength
columnfamily
columnqualifier
timestamp
keytype (e.g., Put, Delete, DeleteColumn, DeleteFamily)
KeyValue实例不会跨越块。例如,若是有一个8M的KeyValue,即便block-size是64Kb,这个KeyValue也会被做为一个一致的块读取。更多信息,参考 KeyValue source code。
为了强调上面的观点,咱们来看看在同一行中,两个不一样的列都发生Put操做时会发生什么:
Put #1: rowkey=row1, cf:attr1=value1
Put #2: rowkey=row1, cf:attr2=value2
即便是在同一行的,为每一个列建立了一个KeyValue
Key portion for Put #1:
rowlength -----------→ 4
row -----------------→ row1
columnfamilylength --→ 2
columnfamily --------→ cf
columnqualifier -----→ attr1
timestamp -----------→ server time of Put
keytype -------------→ Put
Key portion for Put #2:
rowlength -----------→ 4
row -----------------→ row1
columnfamilylength --→ 2
columnfamily --------→ cf
columnqualifier -----→ attr2
timestamp -----------→ server time of Put
keytype -------------→ Put
重要的是,要理解RowKey、ColumnFamily、column(列标识符) 嵌入在KeyValue实例中。这些惟一标识越长,KeyValue就越大。
StoreFile 是HFile的外观门面。在紧缩的术语中,StoreFile的使用彷佛在过去更流行。
Store 和 ColumnFamily是同样的。StoreFiles 和 Store 或者 ColumnFamily 相关联。
若是你想阅读更多关于 StoreFiles versus HFiles 和 Stores versus ColumnFamilies, 参考 HBASE-11316.
当MemStore达到一个给定的大小(hbase.hregion.memstore.flush.size
),它会把本身的内容刷新到一个 StoreFile。随着时间,一个Store的StoreFiles的数量会增加。紧缩(Compaction) 是一个操做,它能经过把StoreFiles合并在一块儿以减小Store中StoreFiles的数量,为了增长读取操做的性能。紧缩(Compactions)能够是资源密集型的,能够帮助或阻碍性能,这取决于许多因素。
有两种类型的紧缩:次紧缩和主紧缩。次紧缩和主紧缩有如下不一样的方式。
小紧缩(Minor compactions) 一般会选择数个小的相邻的文件,把它们重写成一个大的StoreFile。Minor紧缩不会删除(过滤掉)打上删除标记的数据或过时版本的数据,由于会有潜在的反作用。参考 Compaction and Deletions 和 Compaction and Versions 获取关于删除和数据版本在紧缩中如何处理的信息。一个Minor紧缩的最终结果是为给定的Store生成更少的、更大的StoreFile。
大紧缩(major compaction) 的最终结果是Store的一个单独的StoreFile。Major紧缩还会处理带有删除标记的数据和超过最大版本数的数据(过时数据)。参考Compaction and Deletions和Compaction and Versions 获取关于删除和数据版本在紧缩中如何处理的信息。
当在HBase中出现显式删除时,数据实际上不会被删除。相反,只是在数据上打了一个 墓碑(tombstone) 标记。墓碑标记能够避免这个数据在查询时返回。在一个Major紧缩过程当中,数据被真正地删除了,而且从StoreFile中删除了墓碑标记。若是删除是由于一个过时的TTL而发生的,则没有建立一个墓碑。相反,过时的数据会被过滤掉,不会被写回紧缩好的StoreFile。
![]() |
Major 紧缩会影响查询结果
在一些情景下,若是一个较新的版本被显式删除,旧版本可能会被无心中恢复。参考 Major compactions change query results 获取更深刻的解释。这个情景只会发生在Major紧缩完成以前。 |
从理论上讲,主要的压缩能提升性能。可是,在一个高度负载的系统上,主要的压缩操做可能须要不适当的资源,而且会对性能产生负面影响。在默认配置中,Major紧缩会自动在7天内运行一次。这有时对于生产系统来讲是不合适的。你能够手动管理Major紧缩操做。参考 Managed Compactions.
Compactions不执行Region合并。参考 Merge 获取更多关于Region合并的信息。
紧缩大的StoreFiles,或者一次性紧缩太多的StoreFiles,会致使更多的负载,这个负载比集群在不形成性能问题时可以处理的IO负载更大。HBase选取哪些StoreFiles进行紧缩操做(不管是Minor的仍是Major的)的方法被称为紧缩策略。
HBase 0.96.x以前, 只有一种紧缩策略。原有的紧缩策略 RatioBasedCompactionPolicy
仍然可用。新的默认的紧缩策略被称为 ExploringCompactionPolicy
, 已被打到了HBase 0.94 和 HBase 0.95上,而且是HBase 0.96及之后版本的默认策略。它在 HBASE-7842 被实现。简单来讲,ExploringCompactionPolicy
试图去选择最有可能的StoreFiles集合来以最小的工做量进行紧缩,而 RatioBasedCompactionPolicy
选取知足条件的第一个StoreFiles集合。
无论使用的紧缩策略是什么,文件选择都是由几个可配置参数控制的,而且在多步骤方法中进行。这些参数将在文中进行解释,而后将在一个表中给出它们的描述、默认值以及更改它们的含义。
当MemStore变得过大时,它须要把本身的内容刷新到一个StoreFile中。然而,一个Store只能拥有 hbase.hstore.blockingStoreFiles
个文件,因此MemStore须要等待这个Store的StoreFiles的数量经一次或屡次紧缩而变少。然而,若是MemStore 变得比 hbase.hregion.memstore.flush.size 还大的话,它将不能把本身的内容刷新到一个StoreFile中。
若是MemStore过大,并且StoreFiles的数量过多,这种算法就被称为“被卡住”了。紧缩算法检查这种“卡住”的状况,并提供机制缓解这个问题。
ExploringCompactionPolicy 算法在选择能带来最大好处的StoreFiles的紧缩组合以前,考虑每一种可能的相邻的StoreFiles组合。
ExploringCompactionPolicy特别好用的一个状况是,当你批量加载数据(bulk-loading)且批量加载操做建立的StoreFiles比存有和此次批量加载的数据相比更老的数据的StoreFiles时。这能够“欺骗”HBase在每次须要紧缩时选择执行一个Major紧缩,并形成大量额外的开销。使用ExploringCompactionPolicy,Major紧缩发生的频率要低得多,由于Minor紧缩更有效。
通常来讲,ExploringCompactionPolicy对于大多数状况是正确的选择,所以它是默认的压缩策略。你能够一块儿使用 ExploringCompactionPolicy 和 Experimental: Stripe Compactions。
这个策略的逻辑能够在 hbase-server/src/main/java/org/apache/hadoop/hbase/regionserver/compactions/ExploringCompactionPolicy.java 中查看。下面是一个ExploringCompactionPolicy的演练逻辑。
建立一个Store中全部的现有的StoreFiles的列表。该算法的其他部分将过滤该列表,以得到用于紧缩的HFiles子集。
若是这是用户请求的紧缩,则尝试执行所请求的紧缩类型,而不考虑一般选择的紧缩类型。注意,即便用户请求一个Major紧缩,也可能不执行Major压缩。这多是由于列族中不是全部的StoreFiles均可用来作紧缩,或者由于列族中有太多的Stores。
一些StoreFiles会自动被排除在考虑以外,这些StoreFiles包括:
大于 hbase.hstore.compaction.max.size 的StoreFiles
由批量加载操做(bulk-load)建立的StoreFile,它显式地排除了紧缩。您可能决定把由批量加载生成的StoreFiles排除在紧缩以外。要这么作的话,在Bulk-Load操做时指定参数 hbase.mapreduce.hfileoutputformat.compaction.exclude
。
遍历第1步生成的列表,并生成一个列表,这个列表包含了全部的能够额紧缩在一块儿的StoreFiles的潜在集合。一个潜在的集合是一组由 hbase.hstore.compaction.min
个相邻的在第1步生成的列表中的 StoreFiles。对于每一组集合,执行一些检查并肯定这是不是能够完成的最好的紧缩:
若是这个集合中的StoreFiles的数量 (不是这些StoreFiles的大小) 小于 hbase.hstore.compaction.min
或者多于 hbase.hstore.compaction.max
, 把这个集合排除在外。
将这一集合中的StoreFiles的大小与目前列表中能找到的最小的可能紧缩的StoreFiles集合的大小进行比较。若是这个StoreFils集合的大小是能够完成的最小的紧缩,那么把这个集合存储起来用做当算法被“卡住”时的一个fall-back,而且不须要选择其余的存储文件。参考Being Stuck.
在这一组存储文件中对每一个StoreFile进行基于大小的完整性检查。
若是这个StoreFile的大小比 hbase.hstore.compaction.max.size 大
,把它排除在外。
若是这个StoreFile的大小大于等于 hbase.hstore.compaction.min.size
, 基于文件的比率检查它是否太大而不能被考虑。
若是知足如下条件,这个检查就是成功的:
只有一个StoreFile在这个集合里,或者
对于每一个StoreFile, 它的大小 乘以 hbase.hstore.compaction.ratio
(或者 hbase.hstore.compaction.ratio.offpeak,若是非高峰时间被设置,且在非高峰时间段
)的值比这个集合中其余HFiles的大小的和还要小。
若是这个StoreFiles的集合仍然在被考虑之中,拿它和前一个选出来的最好的紧缩组合进行比较。若是它更好,就由它来替代前一个选出来的最好紧缩组合。
当潜在紧缩的组合的整个列表都被处理完后,在找出的最好的紧缩组合上作紧缩。若是没有StoreFiles被选出作紧缩,可是存在了不少StoreFiles,假定这个算法被卡住了(参考Being Stuck),若是这样就在第3步找到的最小的紧缩集合执行紧缩。
RatioBasedCompactionPolicy 是 HBase 0.96以前仅有的紧缩策略, 而 ExploringCompactionPolicy 如今已经被打到 HBase 0.94 和 0.95中去了。要使用 RatioBasedCompactionPolicy 而不使用ExploringCompactionPolicy, 在hbase-site.xml中设置 hbase.hstore.defaultengine.compactionpolicy.class
为 RatioBasedCompactionPolicy。要切换回
ExploringCompactionPolicy, 删除在hbase-site.xml中的这个设置。
如下部分介绍的算法在RatioBasedCompactionPolicy中用于选择作紧缩的StoreFiles。
首先建立一个紧缩候选StoreFiles的列表。建立的一个列表中包含全部不在紧缩队列中的StoreFiles,以及全部 比如今正在被紧缩的最新的文件还要新的 StoreFiles。这个StoreFiles的列表以sequence ID作排序。Sequence ID是在一个Put被添加到WAL中时生成的,它被存在HFile的metadata里。
查看算法是否被卡住(参考Being Stuck),若是被卡住,会强制执行一个Major紧缩。这是 The ExploringCompactionPolicy Algorithm 常做为一个比RatioBasedCompactionPolicy更好的选择的关键部分。
若是这是用户请求的紧缩,则尝试执行所请求的紧缩类型。注意,Major紧缩可能不被执行,若是全部的HFiles不可用来作紧缩,或者,若是过多的StoreFile存在(比hbase.hstore.compaction.max还要多
)。
一些StoreFiles会自动被排除在考虑以外,这些StoreFiles包括:
大于 hbase.hstore.compaction.max.size 的StoreFiles
由批量加载操做(bulk-load)建立的StoreFile,它显式地排除了紧缩。您可能决定把由批量加载生成的StoreFiles排除在紧缩以外。要这么作的话,在Bulk-Load操做时指定参数 hbase.mapreduce.hfileoutputformat.compaction.exclude
。
被容许进行Major紧缩的StoreFiles的最大文件数由 hbase.hstore.compaction.max
参数控制。若是这个列表包含有大于这个数值的StoreFiles,一个Minor紧缩被执行,即便原本一个Major紧缩该被执行的。可是,即便比 hbase.hstore.compaction.max
还多的StoreFiles要作紧缩时,用户请求的Major紧缩仍然会发生。这不是和第2条矛盾吗?
若是这个列表包含的要作紧缩的StoreFiles 比 hbase.hstore.compaction.min
少,Minor紧缩会被停止。注意,一个Major compaction 能够在单个HFile上执行。它的功能是移除被删除的和过去版本的数据,并在StoreFile中重置位置。
在一个Minor紧缩过程当中, hbase.hstore.compaction.ratio
参数的值 乘以 比给定文件要小的StoreFiles的和, 用来决定这个给定的StoreFile是否被选取作紧缩。例如,若是 hbase.hstore.compaction.ratio 是 1.2, FileX 是 5MB, FileY 是 2MB, and FileZ 是 3MB:
5 <= 1.2 x (2 + 3) or 5 <= 6
在这种状况下,FileX就能够用来作Minor紧缩了。若是FileX是7MB,它就不会用来作Minor紧缩。这个比例支持较小的StoreFile。若是你配置了 hbase.offpeak.start.hour
和 hbase.offpeak.end.hour
,你能够使用参数 hbase.hstore.compaction.ratio.offpeak
,为在非高峰时段的使用配置一个不一样的比例值。
若是上一次的Major紧缩是好久之前作的,如今有多于一个StoreFile要被紧缩,一个Major紧缩就会运行,即便它应该运行Minor紧缩。默认状况下,Major紧缩的最大间隔时间是7天加或减4.8小时,而且经过这些参数随机决定。在 HBase 0.96以前,Major紧缩周期是 24 hours。参考下表中的 hbase.hregion.majorcompaction
调整或禁用基于时间的Major紧缩。
该表包含了用于紧缩的主要配置参数。这个列表并不详尽。要调整这些参数的默认值,编辑 hbase-default.xml 文件。要获取完整的可用配置参数列表,参考 config.files
hbase.hstore.compaction.min
在紧缩能运行前,能够作紧缩操做的StoreFiles的最小文件数。调整 hbase.hstore.compaction.min
的目的是为了不有大量的小StoreFiles进行紧缩。设置这个值为2,会致使每次在一个Store中有两个StoreFiles时就会发生Minor紧缩,这多是不合适的。若是你把这个值设置得过高,其余全部值都须要相应地调整。对于大多数状况,默认值是合适的。在HBase以前的版本,参数 hbase.hstore.compaction.min
被称做 hbase.hstore.compactionThreshold。
默认值: 3
hbase.hstore.compaction.max
不管符合作紧缩的存储文件的数量如何,将被选择作单个Minor紧缩的StoreFiles的最大文件数。 Effectively, hbase.hstore.compaction.max
的值控制了完成一个单个紧缩所需的时间长度。把它设置大了,意味着更多的StoreFiles能够被紧缩。对于大多数状况,默认值是合适的。
Default: 10
hbase.hstore.compaction.min.size
大小小于这个值的一个StoreFile将一直符合Minor紧缩。大小等于或大于这个值的 StoreFiles被 hbase.hstore.compaction.ratio
评估以决定他们是否符合作Minor紧缩。由于这个限制表明了比这个值小的全部store文件的“自动include”限制,因此这个值在写操做密集的环境中可能须要减少,在这个环境中,1-2 MB范围内的许多文件都被刷新了,由于每一个StoreFile都将是紧缩的目标,紧缩后生成的StoreFile可能仍然比最小的大小要小,而且须要进一步的紧缩。若是减少了这个参数,则会更快地触发比率检查。这解决了早期版本的HBase中出现的一些问题,可是在大多数状况下,更改这个参数再也不是必需的。
Default:128 MB
hbase.hstore.compaction.max.size
一个大小大于这个值的StoreFile 将被排除在紧缩操做以外。增长 hbase.hstore.compaction.max.size
值的效果很小,更大的 StoreFiles 不常常被紧缩。若是你以为紧凑的事情发生得太频繁而没有太多的好处,你能够试着提升这个值。
Default: Long.MAX_VALUE
hbase.hstore.compaction.ratio
对于Minor紧缩,这个比例被用来决定给定的StoreFile(大小大于hbase.hstore.compaction.min.size)是否符合紧缩操做
。它的做用是限定大的StoreFile的紧缩。这个参数的值是一个浮点小数。
一个大的比率,例如10,将会产生一个巨大的StoreFile。相反,它的值是0.25的话,将产生与BigTable紧缩算法相似的行为,生成4个StoreFiles。
推荐使用介于1.0到1.4之间的中等值。 在调优这个值时,您须要将写成本与读成本进行平衡。 提升值(好比1.4)将会有更多的写成本,由于您将紧缩更大的存储文件。可是,在读取过程当中,HBase须要经过更少的存储文件来完成读取。若是你不能使用 Bloom Filters,能够考虑这个方法。
或者,您能够将该值下降到1.0这样的值,以减小写操做的成本,并限制读时接触StoreFiles的数量。在大多数状况下,这个默认值是合适的。
Default: 1.2F
hbase.hstore.compaction.ratio.offpeak
若是非高峰时段被配置的话(见如下配置),这个紧缩比例参数在非高峰时段被使用。参数表达是一个浮点型小数值。在一个设定的时间周期,这个参数容许更积极的(或积极性少地,若是你把它设置成低于hbase.hstore.compaction.ratio的值)紧缩。若是非高峰被禁用的话(默认状况下)就忽略它。这个参数和 hbase.hstore.compaction.ratio 的工做方式同样。
Default: 5.0F
hbase.offpeak.start.hour
非高峰时段的开始时点,数值为一个在0到23之间含0和23的整数。设置为-1可禁用非高峰功能。
Default: -1
(disabled)
hbase.offpeak.end.hour
非高峰时段的结束时点,数值为一个在0到23之间含0和23的整数。设置为-1可禁用非高峰功能。
Default: -1
(disabled)
hbase.regionserver.thread.compaction.throttle
紧缩操做有两个不一样的线程池,一个用来作大的紧缩,另外一个作小的紧缩。这有助于保持瘦表(如hbase:meta)快速的紧缩。若是一个紧缩操做量(要处理的StoreFiles大小总和)大于这个阈值,它会被提交到大紧缩池。在大多数状况下,这个默认值是合适的。
Default: 2 x hbase.hstore.compaction.max x hbase.hregion.memstore.flush.size
(which defaults to 128
)
hbase.hregion.majorcompaction
作Major紧缩的时间间隔,以毫秒为单位。设置为0则禁用基于时间的自动Major紧缩。用户请求的和基于大小(size-based)的Major紧缩将会仍然执行。这个值 乘以 hbase.hregion.majorcompaction.jitter
会在给定的时间窗口中的任意时间开始Major紧缩。
Default: 7 days (604800000
milliseconds)
hbase.hregion.majorcompaction.jitter
hbase.hregion.majorcompaction 的乘数,使Major紧缩发生在给定的时间间隔内(hbase.hregion.majorcompaction设置了时间间隔的base)
。这个数值越小,紧缩发生的间隔将越接近 hbase.hregion.majorcompaction
。这个值是一个浮点小数。
Default: .50F
![]() |
遗留信息 Legacy Information
因为历史缘由,这一节被保留了下来,并提到了在HBase.0.96.x以前紧缩的工做方式。若是你启用了RatioBasedCompactionPolicy Algorithm,你仍然能够使用它的行为。获取关于HBase0.96及之后版本的紧缩的工做方式,参考 Compaction。 |
要理解选择StoreFile的核心算法,在Store source code中有一些很是有用的代码,能够做为有用的参考。
它已经拷贝以下:
/* normal skew: * * older ----> newer * _ * | | _ * | | | | _ * --|-|- |-|- |-|---_-------_------- minCompactSize * | | | | | | | | _ | | * | | | | | | | | | | | | * | | | | | | | | | | | | */
hbase.hstore.compaction.ratio
用于紧缩的文件选择算法中的比例值 (默认 1.2f).
hbase.hstore.compaction.min
(在 HBase v 0.90 时被称为 hbase.hstore.compactionThreshold
) (files) 为发生一个紧缩而选取的每一个Store中的StoreFiles的最小数目 (默认 2).
hbase.hstore.compaction.max
(files) 要进行Minor紧缩的StoreFiles的最大数目 (默认 10).
hbase.hstore.compaction.min.size
(bytes) 任何小于这个参数值的StoreFile自动成为紧缩的候选文件。默认为 hbase.hregion.memstore.flush.size的值
(128 mb).
hbase.hstore.compaction.max.size
(.92) (bytes) 任何大于这个参数值的StoreFile自动被排除在紧缩操做以外 (默认 Long.MAX_VALUE).
Minor紧缩的StoreFile的选择逻辑是基于文件大小的,当 文件大小 <= sum(文件大小小于hbase.hstore.compaction.min.size的文件的大小) * hbase.hstore.compaction.ratio时,这个文件被选择进行紧缩操做
。
这个例子反映了单元测试 TestCompactSelection 中的一个例子
hbase.hstore.compaction.ratio
= 1.0f
hbase.hstore.compaction.min
= 3 (files)
hbase.hstore.compaction.max
= 5 (files)
hbase.hstore.compaction.min.size
= 10 (bytes)
hbase.hstore.compaction.max.size
= 1000 (bytes)
存在下列 StoreFiles: 各自大小为 100, 50, 23, 12, and 12 bytes (从旧到新排列)。基于以上的参数设置,被选中进行Minor紧缩的文件是2三、十二、12。
为何?
100 → No, 由于 sum(50, 23, 12, 12) * 1.0 = 97.
50 → No, 由于 sum(23, 12, 12) * 1.0 = 47.
23 → Yes, 由于 sum(12, 12) * 1.0 = 24.
12 → Yes, 由于前面的文件已经被包含,且这个文件的加入没有超过最大文件数5的限制
12 → Yes, 由于前面的文件已经被包含,且这个文件的加入没有超过最大文件数5的限制
这个例子反映了单元测试 TestCompactSelection 中的例子。
hbase.hstore.compaction.ratio
= 1.0f
hbase.hstore.compaction.min
= 3 (files)
hbase.hstore.compaction.max
= 5 (files)
hbase.hstore.compaction.min.size
= 10 (bytes)
hbase.hstore.compaction.max.size
= 1000 (bytes)
存在下列 StoreFiles: 各自大小为 100, 25, 12, and 12 bytes (从旧到新排列)。 基于以上的参数设置,紧缩不会发生。
Why?
100 → No, 由于 sum(25, 12, 12) * 1.0 = 47
25 → No, 由于 sum(12, 12) * 1.0 = 24
12 → No. 为候选文件,由于 sum(12) * 1.0 = 12, 只有2个文件能够进行紧缩操做,小于阈值3。
12 → No. 为候选文件,缘由和前一个StoreFile同样,可是没有足够数量的文件作紧缩操做。
这个例子反映了单元测试 TestCompactSelection 中的例子。
hbase.hstore.compaction.ratio
= 1.0f
hbase.hstore.compaction.min
= 3 (files)
hbase.hstore.compaction.max
= 5 (files)
hbase.hstore.compaction.min.size
= 10 (bytes)
hbase.hstore.compaction.max.size
= 1000 (bytes)
The following StoreFiles exist: 7, 6, 5, 4, 3, 2, and 1 bytes apiece (oldest to newest). With the above parameters, the files that would be selected for minor compaction are 7, 6, 5, 4, 3.
Why?
7 → Yes, because sum(6, 5, 4, 3, 2, 1) * 1.0 = 21. Also, 7 is less than the min-size
6 → Yes, because sum(5, 4, 3, 2, 1) * 1.0 = 15. Also, 6 is less than the min-size.
5 → Yes, because sum(4, 3, 2, 1) * 1.0 = 10. Also, 5 is less than the min-size.
4 → Yes, because sum(3, 2, 1) * 1.0 = 6. Also, 4 is less than the min-size.
3 → Yes, because sum(2, 1) * 1.0 = 3. Also, 3 is less than the min-size.
2 → No. Candidate because previous file was selected and 2 is less than the min-size, but the max-number of files to compact has been reached.
1 → No. Candidate because previous file was selected and 1 is less than the min-size, but max-number of files to compact has been reached.
![]() |
关键配置项的影响 Impact of Key Configuration Options
这个内容限制在 Parameters Used by Compaction Algorithm 的配置参数表里 |
日期分层紧缩是一个基于时间的存储文件紧缩的策略,这个策略对时间序列的数据作基于时间范围的扫描有好处。
考虑对限定了时间范围的数据进行的读取操做使用日期分层紧缩,特别是扫描近期的数据。
不要在如下情景使用
没有时间范围限定的随机读取 random gets without a limited time range
频繁的删除和更新 frequent deletes and updates
频繁地从订单数据中建立长尾,特别是用将来的时间戳写 Frequent out of order data writes creating long tails, especially writes with future timestamps 不理解
频繁的带有大量重叠的时间范围的批量加载 frequent bulk loads with heavily overlapping time ranges
性能测试展现了基于时间范围扫描的性能在限定的时间范围内有很大改善,特别是扫描近期的数据。
你能够对一个表或列族启用时间分层紧缩,经过设置它的hbase.hstore.engine.class
参数为 org.apache.hadoop.hbase.regionserver.DateTieredStoreEngine。
你还须要设置 hbase.hstore.blockingStoreFiles
为一个大的数值, 例如 60, 若是使用全部的默认设置,而不是12的默认值。若是改变这个参数值的话,使用 1.5到2的值 乘以 Projected 文件数量, Projected file 数量 = windows per tier x tier count + incoming window min + files older than max age
你还须要把 hbase.hstore.compaction.max
设置成和 hbase.hstore.blockingStoreFiles
同样的值以消除Major紧缩的障碍。
在HBase shell中运行如下其中一个命令。用你的表名替代 orders_table
。
alter 'orders_table', CONFIGURATION => {'hbase.hstore.engine.class' => 'org.apache.hadoop.hbase.regionserver.DateTieredStoreEngine', 'hbase.hstore.blockingStoreFiles' => '60', 'hbase.hstore.compaction.min'=>'2', 'hbase.hstore.compaction.max'=>'60'} alter 'orders_table', {NAME => 'blobs_cf', CONFIGURATION => {'hbase.hstore.engine.class' => 'org.apache.hadoop.hbase.regionserver.DateTieredStoreEngine', 'hbase.hstore.blockingStoreFiles' => '60', 'hbase.hstore.compaction.min'=>'2', 'hbase.hstore.compaction.max'=>'60'}} create 'orders_table', 'blobs_cf', CONFIGURATION => {'hbase.hstore.engine.class' => 'org.apache.hadoop.hbase.regionserver.DateTieredStoreEngine', 'hbase.hstore.blockingStoreFiles' => '60', 'hbase.hstore.compaction.min'=>'2', 'hbase.hstore.compaction.max'=>'60'}
配置其余的属性项,参考 Configuring Date Tiered Compaction 获取更多信息.
把 hbase.hstore.engine.class
设置为 nil 或 org.apache.hadoop.hbase.regionserver.DefaultStoreEngine,设置成哪一个值都是相同的效果
。确保你把你改变的其余属性项设置成原有的设置。
alter 'orders_table', CONFIGURATION => {'hbase.hstore.engine.class' => 'org.apache.hadoop.hbase.regionserver.DefaultStoreEngine', 'hbase.hstore.blockingStoreFiles' => '12', 'hbase.hstore.compaction.min'=>'6', 'hbase.hstore.compaction.max'=>'12'}}
当你经过任意一种方式改变存储引擎时,一个Major紧缩可能在大多数Regions上执行。这在新表上不是必须的。
Table 9. Date Tier 参数
设置 | 注意 |
hbase.hstore.compaction.date.tiered.max.storefile.age.millis | 最大时间戳小于这个值的文件不会被紧缩。默认是 Long.MAX_VALUE. |
hbase.hstore.compaction.date.tiered.base.window.millis | 基本的时间窗口大小,以毫秒为单位,默认是6小时。 |
hbase.hstore.compaction.date.tiered.windows.per.tier | 每层窗口的数量,默认是4。 |
hbase.hstore.compaction.date.tiered.incoming.window.min | 在即将到来的窗口中要紧缩的文件的最小数目。设置它为窗口中文件的预期数量,以免浪费的紧缩。默认为6。 |
hbase.hstore.compaction.date.tiered.window.policy.class | 在相同的时间窗口选择存储文件的策略。它不该用到即将到来的窗口。默认是Exploring Compaction。这个设置能够避免浪费的紧缩。 |
在分层紧缩的状况下,集群中的全部服务器同时会将窗口提高到更高的级别,所以建议使用紧缩临界点:
设置 hbase.regionserver.throughput.controller
为 org.apache.hadoop.hbase.regionserver.compactions.PressureAwareCompactionThroughputController。
![]() |
关于 date tiered compaction更多的信息, 请参考 https://docs.google.com/document/d/1_AmlNb2N8Us1xICsTeGDLKIqL6T-oHoRLZ323MG_uy8中的设计规格 |
Stripe compactions 在HBase0.98是一个实验性的功能,目的在于改善大的Regions或行键分布不均的Region的紧缩。为了实现更小更细粒度的紧缩,该Region内的StoreFiles是分为几个行键子范围或Region的“条带(Stripe)”进行单独维护的。这些Strips对HBase的其余部分是透明的,所以HFile或数据上的其余操做不须要修改就能工做。
Stripe compactions 改变了HFile的布局,在Regions内建立了子Regions。这些子Regions容易紧缩,致使不多的Major紧缩。这种方法缓解了较大Regions的一些挑战。
Stripe compaction与Compaction彻底兼容,而且能够和ExploringCompactionPolicy或RatioBasedCompactionPolicy协同工做。它能够为现有的表启用,若是之后禁用它,该表将继续正常运行。
若是遇到如下状况,考虑使用Stripe Compaction:
较大的Regions。在没有附加的MemStore开销和Region管理开销的状况下,你能够获得较小Regions的积极影响。
非统一的键(分布不均的键?),例如键中的时间维度。只有收到新键的Stripes才会被紧缩。旧数据将不会常常紧缩。
性能测试显示出,读的性能稍微改善了,读和写性能的可变性大大下降了。在大型的非均匀的行键的Region上,例如一个hash前缀的时间戳键,整体的长期的性能获得了改善。这些性能收益在一个已经很大的表中是最显著的。性能改进可能会扩展到Region分割。
你能够为一个表或一个列族启用Stripe Compaction,经过设置它的 hbase.hstore.engine.class
为 org.apache.hadoop.hbase.regionserver.StripeStoreEngine进行配置
。你还须要设置 hbase.hstore.blockingStoreFiles
为一个大的数值,如 100 (而不是默认值 10).
Run one of following commands in the HBase shell. Replace the table name orders_table
with the name of your table.
alter 'orders_table', CONFIGURATION => {'hbase.hstore.engine.class' => 'org.apache.hadoop.hbase.regionserver.StripeStoreEngine', 'hbase.hstore.blockingStoreFiles' => '100'} alter 'orders_table', {NAME => 'blobs_cf', CONFIGURATION => {'hbase.hstore.engine.class' => 'org.apache.hadoop.hbase.regionserver.StripeStoreEngine', 'hbase.hstore.blockingStoreFiles' => '100'}} create 'orders_table', 'blobs_cf', CONFIGURATION => {'hbase.hstore.engine.class' => 'org.apache.hadoop.hbase.regionserver.StripeStoreEngine', 'hbase.hstore.blockingStoreFiles' => '100'}
Configure other options if needed. See Configuring Stripe Compaction for more information.
Enable the table.
Set the hbase.hstore.engine.class
option to either nil or org.apache.hadoop.hbase.regionserver.DefaultStoreEngine
. Either option has the same effect.
alter 'orders_table', CONFIGURATION => {'hbase.hstore.engine.class' => 'rg.apache.hadoop.hbase.regionserver.DefaultStoreEngine'}
Enable the table.
当你在以某方式改变了存储引擎后,启用一个大的表时,一个Major紧缩可能会在大部分Regions上执行。这在新表上不是必须的。
Stripe紧缩的每个设置应该被配置在表或列族上。若是你使用HBase shell, 通常的命令模式以下:
alter 'orders_table', CONFIGURATION => {'key' => 'value', ..., 'key' => 'value'}}
你能够基于你的Region大小配置你的Stripe 大小。默认状况下,你的新的Region会含有一个Stripe启动。在Stripe增加过大(16 * MemStore flushes size)后的下次紧缩,它被拆分红两个Stripes。随着Region的增加,Stripe拆分会持续进行,直到这个Region大到被拆分。
您能够为本身的数据改进这种模式。一个好的规则是,1个Stripe至少有1GB的大小,以及大约8-12个用于统一行键的Stripes。例如,若是你的Regions有30GB,12 * 2.5GB 的Stripes多是好的开端。
Table 10. Stripe Sizing Settings
Setting | Notes |
---|---|
|
当Stripe紧缩被启用时,建立Stripe的数量。你能够以下使用它:
|
|
Stripe在分裂以前的最大尺寸。根据以上的大小调整考虑,将这个参数和 |
|
在分割Stripes时要建立的新Stripes的数量。默认为2,这对于大多数状况是合适的。对于非均匀的行键,您能够尝试将数字增长到3或4,从而将到达的更新隔离到更窄的区域,而不须要额外的分割。 |
默认状况下,根据现有的Stripes边界和要刷新的行键,刷新将从一个MemStore建立多个文件。这种方法能够最小化写放大,可是若是MemStore很小,而且有不少Stripes,则多是不受欢迎的,由于文件过小了。
在这种情景下,你能够设置 hbase.store.stripe.compaction.flushToL0
为 true。这将使一个MemStore刷新到一个单个文件
。当至少有 least hbase.store.stripe.compaction.minFilesL0
个文件 (默认为 4) 被生成时, 它们将被紧缩成Striped files。
全部适用于普通紧缩的设置 (参考 Parameters Used by Compaction Algorithm) 都适用于Stripe紧缩。除了最小文件数和最大文件数,这两个值默认设置大一些,由于Stripes的文件要小一些。要控制Stripe紧缩的最小和最大文件数,使用 hbase.store.stripe.compaction.minFiles
和 hbase.store.stripe.compaction.maxFiles
, 而不是 hbase.hstore.compaction.min
和 hbase.hstore.compaction.max。
HBase 有几种把数据加载到表中的方法。最直接的方法是在MapReduce做业中使用 TableOutputFormat
类,或者使用普通的客户端APIs;可是这些一般不是最有效率的方法。
批量加载功能使用一个MapReduce做业,以HBase内部数据的格式输出表数据,而后直接把生成的StoreFiles加载到运行中的集群里去。和简单地使用HBase API相比,使用bulk load将会占用更少的CPU和网络资源。
因为批量加载绕过了写路径,因此WAL并无做为过程当中的一部分被写下来。副本经过读取WAL进行工做,因此它不能看到批量加载进来的数据 - 对编辑使用 Put.setDurability(SKIP_WAL)也会有相同的效果。处理的一种方式是把原始的文件或HFile传到其余集群,而且以其余的方式处理
。
HBase bulk load 过程有两个主要步骤组成。
批量加载的第一步是经过一个使用了 HFileOutputFormat2的MapReduce做业生成HBase数据文件(StoreFiles)。这个输出格式以HBase内部的存储格式写出数据,以便于它们事后被有效地加载到集群中
。
为了有效率地运行,HFileOutputFormat2
必须配置成每一个输出的HFile都要放在一个单独的Region内。为了作到这一点,输出将要被加载到HBase里的做业使用Hadoop的 TotalOrderPartitioner
类来把map的输出划分到为分开的键空间中不相交的范围里去,对应于表中Regions的键的范围。
HFileOutputFormat2
有一个方便的函数, configureIncrementalLoad()
, 能够基于一个表里当前的Region边界自动地创建一个 TotalOrderPartitioner
。
经过结合“importtsv.bulk.output”配置项使用 importtsv
工具 或者 经过一些使用了HFileOutputFormat2的其余MapReduce做业,把数据导入准备完以后,
completebulkload
工具被用来把数据导入到运行中的集群中。这个命令行工具遍历准备好的数据文件,为每一个文件划分它所属的Region。而后,它将与适合接收HFile的RegionServer联系,将HFile移动到这个RegionServer的存储目录中,并使数据对客户端可用。
若是Region的边界在批量加载数据的准备过程当中发生了变化,或者在准备和完成步骤之间发生了变化,那么 completebulkload
实用程序会自动将数据文件分割成与新边界相对应的部分。这个过程不是最优的,所以用户应该当心地减小准备批量加载和将其导入集群之间的延迟,尤为是在其余客户机同时经过其余方式加载数据的状况下。
$ hadoop jar hbase-server-VERSION.jar completebulkload [-c /path/to/hbase/config/hbase-site.xml] /user/todd/myoutput mytable
-c config-file
参数项能够被用来指定一个含有适当的hbase参数的文件(如 hbase-site.xml),若是没有在CLASSPATH (此外,若是ZooKeeper不禁HBase管理的话,CLASSPATH 必须包含拥有ZooKeeper配置文件的目录)中提供这个文件的话。
![]() |
若是目标表在HBase中还不存在,这个工具将会自动生成这个表。 |
要获取更多关于所引用工具的信息,参考 ImportTsv 和 CompleteBulkLoad.
参考 How-to: Use HBase Bulk Loading, and Why 获取最近关于批量加载的最新博客。
虽然 importtsv
工具在不少状况下都有用,高级用户可能想以编程的方式生成数据,或者从其余格式导入数据。要开始这么作,先研究 ImportTsv.java
,查看关于HFileOutputFormat2的JavaDoc。
批量加载的Import步骤能够编写程序完成。参考 LoadIncrementalHFiles
类以获取信息。
因为 HBase 在 HDFS 上运行(每一个存储文件也被写为HDFS的文件),必须理解 HDFS 结构,特别是它如何存储文件,处理故障转移,备份块。
参考 Hadoop 文档 HDFS Architecture 获取更多信息。
HBase架构从一开始就保证了强一致性,全部的读写都是经过一个region server,保证全部的写按顺序发生,全部的读都会看到最近提交的数据。
然而,因为在单一的位置读取,若是服务器不可用,表的那些在不可用的RegionServer中的region在某些时间是不可用的。Region恢复进程须要三个阶段:检测、分配和恢复。检测一般是耗时最长的,根据Zookeeper会话超时,目前在20-30秒。在此期间和完成恢复以前,客户端不能读取到region数据。
然而对于某些使用案例来讲,数据多是只读的,或者能够接受读取陈旧的数据。有了基于时间轴一致性的高可用读取,HBase能够用于这些对延迟敏感的用例,在这些用例中,应用程序能够指望给读完成时设置一个时间限制。
为实现高可用的读取,HBase提供了一个特性叫作region副本。在这个模型中,一个表的每一个region会在不一样的RegionServer中有多个副本。默认状况下,Region副本数为1,因此只有一个region副本被部署,这样原来的模型中不会有任何的变化。若是region副本被设置为2或者更多,master就会分配这个表的Regions的副本。负载均衡器会保证region副本不会都存储在相同的region server上但会在相同的机架上(若是可能的话)。
一个单独的Region的全部的副本上都会有一个惟一的replica_id,从0开始。Region副本有replica_id==0被称为主region,其余的为二级region。客户端的写只有主region能够接收,主region包含着最新的变化。全部的写都会先经过主region,从必定意义上讲这不会是高可用的表现(意味着它们可能在Region不可用时会有一段时间阻碍客户端的写操做)。
有了这个功能,HBase引入了一致性定义,这个一致性能够被提供给每一次读取操做 (get or scan).
public enum Consistency { STRONG, TIMELINE }
Consistency.STRONG
是HBase提供的默认的一致性模型(强一致性)。若是region复制=1,或者region副本只在一个表中,可是读取的一致性是这么作的,读取老是会从主region来执行,因此和前一次的行为相比不会有任何变化,客户端老是能观察到最新的数据。
若是执行读操做是经过 Consistency.TIMELINE
, 而后读RPC将会首先发送到主region服务器上。在短期内 (hbase.client.primaryCallTimeout.get
, 默认10ms ), 若是主region没有响应,并行的RPC会被发送到二级region。以后结果会从第一个完成RPC的返回。若是响应是来自主region副本,咱们就会知道数据是最新的。Result.isStale() API是检查过时数据。若是结果是从二级region返回,那么Result.isStale()为true,而后用户就能够检查关于过时数据可能的缘由。
HBase实现的 时间轴一致性和纯粹的最终一致性在如下方面有所不一样:
单宿主和有序的更新:在写方面,只有一个主region副本被定义能够接收写操做,此副本负责有序的编辑和防止冲突。这可以保证两个不一样的写操做经过不一样的副本和数据发散的状况下不能在相同的时间提交。这样就没有必要作read-repair或者last-timestamp-wins这两种冲突的解决方法。(read-repair:从CassandraWiki中找到这个概念。当对给定键进行查询时,咱们将对全部键的副本执行摘要查询,并将最新版本推到任何过时的副本上。若是比ALL弱的一致性级别被指定的话,这个操做将在从最近的副本返回数据到客户端后在后后台进行;不然它在返回数据前进行。)
二级Region也按照主Region提交的编辑顺序,将这些编辑写入本身。经过这种方式,在任什么时候间二级region包含主region上的一个数据镜像,相似于关系型数据库的复制,无论HBase在多数据中心仍是在单个集群中。
在读取端,客户端能够发现读取的是来自最新的数据或者是陈旧的数据。此外,客户端能够在每一个操做的基础上发出不一样一致性要求的读请求,以确保本身的语义获得保证。
此外,客户端在每一个操做客户端仍然能够观察到无序的编辑,能够追溯到过去的时间点,若是它首先看到一个从副本的读,而后是另外一个从副本。对于Region副本或基于事务id的保证,不存在粘性。若是须要,这能够在稍后实现。
上图能够帮助咱们更好的理解TIMELINE的语义。假定有两个客户端,最早的写是x=1,而后是x=2,最后是x=3。如上,全部的写操做都被主Region副本处理。写会被保存到预写日志 (WAL)中, 而且会被异步复制到其余的副本中。在上图能够注意到replica_id=1的接收到了两个更新操做,它显示为x=2,而replica_id=2的则只接收到1个更新操做,它显示为x=1。
若是Client1是经过STRONG一致性(强一致性)进行读取,它只会从replica_id=0读取从而保证观察到最新的值x=3。若是客户端使用TIMELINE方式(时间轴一致性)读取,那么RPC会被发送到全部的副本中(在主region超时后),从第一个响应获得的结果会被返回。所以客户端能够看到1,2或者3之中的任意一个值做为x的值。假设主Region已经失败了,那么日志复制就有一段时间不能持续进行。若是客户端使用时间轴一致性从2个二级Region进行多路读取,它能够先看到 x=2,而后是 x=1,等等。
拥有二级Regions而得到的读取可用性会有一些权衡取舍,对于每一个应用案例来讲都应该对这里的权衡取舍进行当心地评估。如下是它的优点和劣势。
只读表的高可用性。
旧数据读取的高可用性。
以很是低的延时读取新数据,以高百分比(99.9%)的延迟读取旧数据。Ability to do very low latency reads with very high percentile (99.9%+) latencies for stale reads 延迟究竟是低仍是高?
表的Region复制量 > 1时,两倍/三倍的MemStore的使用量 (取决于region复制的数量)
增长block缓存的使用
对于log复制会有额外的网络传输
为副本备份额外的RPCs
要为从多个副本里返回的Region数据进行服务,HBase在RegionServers以Secondary模式打开Regions。以Secondary模式打开的Regions将和主Region副本共享相同的数据文件,可是每一个Secondary Region副本将拥有本身的MemStore以保存未刷新的数据(只有主Region能够作刷新)。为了从Secondary Region读取数据,数据文件块也能够缓存在Secondary Region的块缓存中。
副本Region在主Region可用的状况下不在生成新的HFile?
该特性由两个阶段交付,第一阶段和第二阶段。第一阶段在HBase-1.0.0发布时完成,意味着使用HBase-1.0.0的话,你能够使用第一阶段的全部特性。第二阶段在HBase-1.1.0被提交,意味着全部在1.1.0以后的HBase版本都包含第二阶段的特性。
正如上面所讨论的,写入操做只到主Region副本中进行。要把写入操做从主Region副本传播到二级Region副本中,有两个不一样的机制。对于只读表,你不须要使用下列任何方法。禁用和启用该表应该使全部Region的副本里的数据都是可用的。对于变化的表,你必须只能下列机制中的一种:StoreFile Refresher, 或 Async WAL replication。 推荐使用后者。
第一种机制是 store file refresher,在 HBase-1.0+ 引入。Store file refresher 是在每个RegionServer中的线程,周期性地运行,而且为二级Region副本作主Region存储文件的刷新操做。若是它被启用,这个refresher将保证二级Region副本及时地看到主Region中新刷新的、紧缩的或批量加载的文件。可是,这意味着只有刷新的数据能够从二级Region副本中读取到,而且在这个refresher运行以后,正在较长的一段时间内,二级Region副本中的数据将会落后主Region副本。
要调整这个功能特性,你应该配置 hbase.regionserver.storefile.refresh.period
为一个非零的值。参考下面的配置小节。
把写入传播到二级Region副本的第二种机制经过“异步WAL复制(Async WAL Replication)”的功能完成,而且只在HBase-1.1+中可用。这个功能的工做方式相似于HBase的多数据中心复制,但不一样的是数据从一个Region被复制到二级Regions中。每一个二级副本一直以和主Region提交的写入操做相同的顺序接收和观察写入操做。在某种程度上,这个设计能够被认为是“集群中复制”,而不是复制到一个不一样的数据中心,数据被复制到二级Region以使二级Region内存中的数据保持是最新的。数据文件在主Region和其它副本中共享,以便没有额外的存储开销。可是,二级Region将在它们的MemStore中保有最近的没有被刷新到磁盘的数据,这会增长内存的开销。主Region把刷新到磁盘、紧缩和批量加载的事件写入它的WAL,这些内容也会经过WAL复制操做复制到二级Regions。当它们观察到 刷新/紧缩 或 批量加载事件时,二级Regions重放事件以获取新文件并删除旧文件。
和主Region中同样的顺序提交写入操做,保证二级Region不会与主Region中的数据分离,可是由于log复制是异步的,在二级Regions中的数据可能仍然是旧的。因为该特性是以端点复制的方式工做,所以性能和延迟特性将相似于跨集群复制。
Async WAL Replication 默认被禁用。你能够经过设置 hbase.region.replica.replication.enabled
为 true 启用这个功能
。当你第一次建立一个Region复制大于1的表,Asyn WAL Replication 功能将添加一个新的名为 region_replica_replication
复制点。一旦被启用,若是你想禁用这个功能,你须要作2件事:1. 在hbase-site.xml中把属性 hbase.region.replica.replication.enabled
设置为 false (参考下面的 Configuration section) ;2. 使用HBase shell或ReplicationAdmin类,在集群中,禁用名为 region_replica_replication
的复制点:
hbase> disable_peer 'region_replica_replication'
在上面提到的两种写传播方法中,主服务器的存储文件将在独立于主Region的二级Regions中打开。所以,对于那些被主Region紧缩的文件,二级Region可能仍然是引用这些文件供读取,这就致使文件已经被compact掉而读取不到。这两种功能都使用HFlieLinks引用文件,可是对于文件不会过早地删除的保证(目前)没有任何保障。所以,你应该设置属性参数 hbase.master.hfilecleaner.ttl
为一个较大值,例如 1 小时,以保障在请求副本时你将不会受到IOException。
目前,异步WAL复制作不了META表的WAL复制。META表的二级副本仍然把持久化的存储文件中的内容刷新到本身。所以咱们能够设置hbase.regionserver.meta.storefile.refresh.period属性为非0值,用以刷新元数据存储文件。注意这个配置和hbase.regionserver.storefile.refresh.period配置不一样。
二级Region副本参考主Region副本的数据文件,可是它们有它们本身的MemStores(在 HBase-1.1+),而且还使用块缓存。可是,其中一个差异是,当二级Region的MemStore产生内存压力时,二级Region副本不能刷新MemStore中的数据到磁盘上。只有主Region作刷新操做而且这个刷新被复制到二级Region时,二级Region才能释放memstore的内存。由于在一个RegionServer中有一些Regions的主Regions,也有一些其余Regions的二级Regions,因此这些二级Region可能会引起同一台Server上的主Region的额外刷新(为何??)。在极端状况下,可能没有内存用来添加新的写入内容,这个写入内容是经过WAL复制过来的主Region的内容。为了避免受这种状况(而且因为二级不能自行刷新)的影响,二级Region能够经过执行文件系统的list操做以便从主Region中获取新文件,来执行“存储文件刷新”,并可能删除它的memstore。这个刷新 只有在最大的二级Region副本的MemStore的大小至少是一个主Region副本的最大MemStore大小的 hbase.region.replica.storefile.refresh.memstore.multiplier
(默认为 4) 倍时 才会执行。一个警告是,若是这个刷新被执行,二级Region能够观察到部分行在多个列族的更新(由于列族的刷新是独立的)。默认状况下,不要频繁地执行这个操做。若是须要,你能够将这个值设置为一个大的数字以禁用该功能,可是要警告你的是它可能致使复制永久地阻塞。
当一个二级region副本第一次上线或者失败时,它可能服务过一些来自它的memstore的编辑。由于对于二级副本,恢复的处理方式不一样,必须确认它在分配以后开启服务以前没法上线。为了作到这一点,这个二级Region会等待,直到它观察到一个完整的刷新周期(启动刷新、提交刷新)或从主Region复制了一个“Region打开事件”。在这种状况发生以前,这个二级Region副本将经过抛出一个带有消息“The region’s reads are disabled”的IOException以拒绝全部读取请求。可是,其余副本可能仍然可用来读取,所以不会对具备时间轴一致性(TIMELINE)的rpc形成任何影响。为了便于更快的恢复,当这个二级Region打开时将会触发一个来自主Region的刷新请求。配置参数 hbase.region.replica.wait.for.primary.flush
(默认启用) 能够被用来禁用这个功能,若是须要的话。
要使用高可用读取,你应该在 hbase-site.xml
文件中设置以下参数。没有特定的配置来启用或禁用Region副本,替代的方法是,你能够在建立表或者修改表结构时,改变(增或减)每一个表的Region副本数量。下列配置是用来配置使用异步WAL复制和配置元数据副本为3。
<property> <name>hbase.regionserver.storefile.refresh.period</name> <value>0</value> <description> 为二级Regions进行存储文件Refresh的周期(单位为毫秒)。0意味着该功能被禁用。
一旦二级Region看到了主Region中经过刷新到磁盘和紧缩生成的新的存储文件,这个二级Region会刷新(Refresh)在这个Region中的文件(没有通知机制)。
可是过于频繁的Refresh会给NameNode形成额外的压力。若是文件没有被refresh的时间比HFile的TTL(hbase.master.hfilecleaner.ttl)的值还长的话,这个refresh请求会被拒绝。
给HFile TTL配置一个大的值以配合这个参数的配置是推荐的。
The period (in milliseconds) for refreshing the store files for the secondary regions. 0 means this feature is disabled. Secondary regions sees new files (from flushes and compactions) from primary once the secondary region refreshes the list of files in the region (there is no notification mechanism). But too frequent refreshes might cause extra Namenode pressure. If the files cannot be refreshed for longer than HFile TTL (hbase.master.hfilecleaner.ttl) the requests are rejected. Configuring HFile TTL to a larger value is also recommended with this setting. </description> </property> <property> <name>hbase.regionserver.meta.storefile.refresh.period</name> <value>300000</value> <description> The period (in milliseconds) for refreshing the store files for the hbase:meta tables secondary regions. 0 means this feature is disabled. Secondary regions sees new files (from flushes and compactions) from primary once the secondary region refreshes the list of files in the region (there is no notification mechanism). But too frequent refreshes might cause extra Namenode pressure. If the files cannot be refreshed for longer than HFile TTL (hbase.master.hfilecleaner.ttl) the requests are rejected. Configuring HFile TTL to a larger value is also recommended with this setting. This should be a non-zero number if meta replicas are enabled (via hbase.meta.replica.count set to greater than 1). </description> </property> <property> <name>hbase.region.replica.replication.enabled</name> <value>true</value> <description> 是否启用异步WAL复制 到二级Region副本。
若是这个功能被启用,一个名为"region_replica_replication"的复制点会被建立,对于Region副本>1的表,这个复制点会tail日志,并把更改复制到Region副本中。
一旦这个功能被启用,禁用这个复制也须要用shell或者ReplicationAdmin java类来禁用它的复制点。到二级Region副本的复制在标准的跨集群复制上工做。
因此复制,若是显示地被禁用,也不得不经过设置"hbase.replication"为true被启用,以使异步WAL复制的功能能够工做。
Whether asynchronous WAL replication to the secondary region replicas is enabled or not. If this is enabled, a replication peer named "region_replica_replication" will be created which will tail the logs and replicate the mutations to region replicas for tables that have region replication > 1. If this is enabled once, disabling this replication also requires disabling the replication peer using shell or ReplicationAdmin java class. Replication to secondary region replicas works over standard inter-cluster replication. So replication, if disabled explicitly, also has to be enabled by setting "hbase.replication"· to true for this feature to work. </description> </property> <property> <name>hbase.region.replica.replication.memstore.enabled</name> <value>true</value> <description> 若是你设置这个值为`false`,副本不会接收主Region所在的RegionServer的MemStore的更新。
若是你设置这个值为`true`,你仍然能够经过设置表的"REGION_MEMSTORE_REPLICATION"属性为`false`以在这个表上禁用MemStore复制。
若是MemStore复制被禁用,二级Region只会接收例如刷新到磁盘和批量加载这类事件的更新,将不会访问主Region尚未刷新到磁盘到的数据。
这个功能保持了行级一致性的保证,即便读取请求是`Consistency.TIMELINE`。
If you set this to `false`, replicas do not receive memstore updates from the primary RegionServer. If you set this to `true`, you can still disable memstore replication on a per-table basis, by setting the table's `REGION_MEMSTORE_REPLICATION` configuration property to `false`. If memstore replication is disabled, the secondaries will only receive updates for events like flushes and bulkloads, and will not have access to data which the primary has not yet flushed. This preserves the guarantee of row-level consistency, even when the read requests `Consistency.TIMELINE`. </description> </property> <property> <name>hbase.master.hfilecleaner.ttl</name> <value>3600000</value> <description> The period (in milliseconds) to keep store files in the archive folder before deleting them from the file system.</description> </property> <property> <name>hbase.meta.replica.count</name> <value>3</value> <description> Region replication count for the meta regions. Defaults to 1. </description> </property> <property> <name>hbase.region.replica.storefile.refresh.memstore.multiplier</name> <value>4</value> <description> 二级Region副本的"存储文件刷新"操做的 乘数。
若是一个RegionServer存在内存压力,当最大的二级副本的MemStore的大小比最大的主副本的MemStore大小大这个参数值的倍数时,二级Region将会刷新它的存储文件。
把这个值设置的很大会禁用这个功能,这是不推荐的。
The multiplier for a “store file refresh” operation for the secondary region replica. If a region server has memory pressure, the secondary region will refresh it’s store files if the memstore size of the biggest secondary replica is bigger this many times than the memstore size of the biggest primary replica. Set this to a very big value to disable this feature (not recommended). </description> </property> <property> <name>hbase.region.replica.wait.for.primary.flush</name> <value>true</value> <description> 在一个二级Region开始服务数据以前,是否等待观察主Region的一个完整的flush周期。禁用这个功能可能会形成二级Region副本在Region移动间读取时后退。(不太明白设置为false的意为着什么)
Whether to wait for observing a full flush cycle from the primary before start serving data in a secondary. Disabling this might cause the secondary region replicas to go back in time for reads between region movements. </description> </property>
须要记住的一点是,Region副本放置策略只由 StochasticLoadBalancer
执行,它是默认的均衡器。若是你在hbase-site.xml(hbase.master.loadbalancer.class
)使用一个自定义的加载均衡器,Region副本可能会被放置在同一个Server中。
确保在全部要使用Region副本的客户端(和服务器)设置如下的参数属性。
<property> <name>hbase.ipc.client.specificThreadForWriting</name> <value>true</value> <description> Whether to enable interruption of RPC threads at the client side. This is required for region replicas with fallback RPC’s to secondary regions. </description> </property> <property> <name>hbase.client.primaryCallTimeout.get</name> <value>10000</value> <description> The timeout (in microseconds), before secondary fallback RPC’s are submitted for get requests with Consistency.TIMELINE to the secondary replicas of the regions.
Defaults to 10ms.
Setting this lower will increase the number of RPC’s, but will lower the p99 latencies. </description> </property> <property> <name>hbase.client.primaryCallTimeout.multiget</name> <value>10000</value> <description> 不明白这个参数
The timeout (in microseconds), before secondary fallback RPC’s are submitted for multi-get requests (Table.get(List<Get>)) with Consistency.TIMELINE to the secondary replicas of the regions.
Defaults to 10ms. Setting this lower will increase the number of RPC’s, but will lower the p99 latencies. </description> </property> <property> <name>hbase.client.replicaCallTimeout.scan</name> <value>1000000</value> <description> The timeout (in microseconds), before secondary fallback RPC’s are submitted for scan requests with Consistency.TIMELINE to the secondary replicas of the regions.
Defaults to 1 sec. Setting this lower will increase the number of RPC’s, but will lower the p99 latencies. </description> </property> <property> <name>hbase.meta.replicas.use</name> <value>true</value> <description> Whether to use meta table replicas or not. Default is false. </description> </property>
注意:HBase-1.0.x 用户应该使用 hbase.ipc.client.allowsInterrupt 而不是 hbase.ipc.client.specificThreadForWriting.
72.11. User Interface
在Master UI中,一个表的Region副本和主Region一块儿展现。你能够注意到,一个Region的副本将共享相同的开始和结束keys和相同的Region名前缀。惟一不一样的是,追加的replica_id(编码成16进制),而且Region编码后的名字将会不一样。你能够在UI看显示出来的副本ID。
72.12. Creating a table with region replication
Region复制是针对表的一个属性。全部的表都默认有 REGION_REPLICATION = 1 ,意味着对于每一个Region只有一个副本。你能够设置和改变一个表的每一个Region的副本数,经过在Table Descriptor里设置 REGION_REPLICATION 属性。
72.12.1. Shell
create 't1', 'f1', {REGION_REPLICATION => 2} describe 't1' for i in 1..100 put 't1', "r#{i}", 'f1:c1', i end flush 't1'
72.12.2. Java
HTableDescriptor htd = new HTableDescriptor(TableName.valueOf(“test_table”)); htd.setRegionReplication(2); ... admin.createTable(htd);
你还能够使用 setRegionReplication() 和 alter table 来增长、减小表的Region副本数。
72.13. Read API and Usage
72.13.1. Shell
你能够以下在Shell中使用 Consistency.TIMELINE 语义 读取数据
hbase(main):001:0> get 't1','r6', {CONSISTENCY => "TIMELINE"}
你能够模拟一个RegionServer 暂停或成不可用状态,而后从一个二级副本中读取:
$ kill -STOP <pid or primary region server> hbase(main):001:0> get 't1','r6', {CONSISTENCY => "TIMELINE"}
使用Scan也同样
hbase> scan 't1', {CONSISTENCY => 'TIMELINE'}
72.13.2. Java
你能够设置Gets和Scans的一致性:
Get get = new Get(row); get.setConsistency(Consistency.TIMELINE); ... Result result = table.get(get);
也能够传递给多个Gets:
Get get1 = new Get(row); get1.setConsistency(Consistency.TIMELINE); ... ArrayList<Get> gets = new ArrayList<Get>(); gets.add(get1); ... Result[] results = table.get(gets);
And Scans:
Scan scan = new Scan(); scan.setConsistency(Consistency.TIMELINE); ... ResultScanner scanner = table.getScanner(scan);
你能够检查结果是否从主Region返回,经过调用 Result.isStale():
Result result = table.get(get); if (result.isStale()) { ... }
72.14. Resources
1. 关于设计和执行更多的信息,能够参考: HBASE-10070
2. HBaseCon 2014 talk also contains some details and slides.
73. Storing Medium-sized Objects (MOB)
保存到HBase中的全部数据大小各异,包括二进制数据像图片或者文档是比较合适的(MOB技术存储和检索非结构化的数据)。Hbase从技术上能够处理大于100KB的cells二进制对象,HBase正常读写路径小于100KB是最优的。当HBase处理超过这个阀值的大对象,这儿称之为中等大小对象或者MOBs,因为拆分和紧缩形成的写入放大致使性能降低。使用MOBs时,对象大小最好在100KB-10M之间。HBase FIX_VERSION_NUMBER能够更好的管理大量的MOBs以保持性能,一致性和低成本的运营。对MOB的支持由HBase-11339中的实现提供。要发挥MOB的优点须要使用HFile的版本3。可选的配置是,为每个RegionServer配置MOB文件的读缓存(参考 Configuring the MOB Cache),而后配置特定的列保存MOB数据。客户端代码不须要改变就能够使用HBase中的 MOB。这个功能对客户端是透明的。
73.1. MOB方式配置列 Configuring Columns for MOB
在表的建立和修改时,你能够配置列以支持MOB,能够经过HBase Shell,也能够经过Java API。这两个相关的属性是boolean类型的IS_MOB 和 MOB_THRESHOLD(能够把一个对象看作是MOB的字节数)。只有 IS_MOB 是要求设置的。若是没有指定MOB_THRESHOLD, 默认值为 100 KB。
Example 38. Configure a Column for MOB Using HBase Shell
hbase> create 't1', {NAME => 'f1', IS_MOB => true, MOB_THRESHOLD => 102400} hbase> alter 't1', {NAME => 'f1', IS_MOB => true, MOB_THRESHOLD => 102400}
Example 39. Configure a Column for MOB Using the Java API
HColumnDescriptor hcd = new HColumnDescriptor(“f”); hcd.setMobEnabled(true); ... hcd.setMobThreshold(102400L); ...
73.2. Testing MOB
工具 org.apache.hadoop.hbase.IntegrationTestIngestMOB 被用来辅助测试MOB功能。运行以下:
$ sudo -u hbase hbase org.apache.hadoop.hbase.IntegrationTestIngestMOB \ -threshold 102400 \ -minMobDataSize 512 \ -maxMobDataSize 5120
threshold 肯定cell是否能够看做为MOB的阈值。默认为1KB,值为字节数。
minMobDataSize MOB数据大小的最小值。默认为512B,值为字节数。
maxMobDataSize MOB数据大小的最大值。默认为5KB,值为字节数。
73.3. Configuring the MOB Cache
由于和HFiles的数量相比,在任什么时候候均可能有大量的MOB文件,因此MOB文件不是一直保持打开状态。MOB文件读缓存是一个LRU缓存,它保持了最近使用的MOB文件为打开状态。要在每个RegionServer上配置MOB文件的读缓存,添加下列属性到RegionServer的 hbase-site.xml, 配置符合你环境的参数值,重启或依次启动RegionServer。
Example 40. Example MOB Cache Configuration
<property> <name>hbase.mob.file.cache.size</name> <value>1000</value> <description> Number of opened file handlers to cache. A larger value will benefit reads by providing more file handlers per mob file cache and would reduce frequent file opening and closing. However, if this is set too high, this could lead to a "too many opened file handers" The default value is 1000. </description> </property> <property> <name>hbase.mob.cache.evict.period</name> <value>3600</value> <description> The amount of time in seconds after which an unused file is evicted from the MOB cache. The default value is 3600 seconds. </description> </property> <property> <name>hbase.mob.cache.evict.remain.ratio</name> <value>0.5f</value> <description> A multiplier (between 0.0 and 1.0), which determines how many files remain cached after the threshold of files that remains cached after a cache eviction occurs which is triggered by reaching the `hbase.mob.file.cache.size` threshold. The default value is 0.5f, which means that half the files (the least-recently-used ones) are evicted. </description> </property>
73.4. MOB Optimization Tasks
73.4.1. Manually Compacting MOB Files
要手动紧缩MOB文件,而不是等到达到配置的条件触发紧缩,使用 compact_mob 或 major_compact_mob HBase shell 命令。这些命令须要的第一个参数是代表,第二个参数是可选的,传入列族名。若是不指定列族名,全部启用了MOB的列族都将被紧缩。
hbase> compact_mob 't1', 'c1' hbase> compact_mob 't1' hbase> major_compact_mob 't1', 'c1' hbase> major_compact_mob 't1'
这些命令也能够经过 Admin.compactMob 和 Admin.majorCompactMob 来实现。
73.4.2. MOB Sweeper MOB清扫器
HBase MOB 有一个叫作Sweeper工具的MapReduce做业作优化工做。这个Sweeper工具合并小MOB文件或者有不少删除和更新的MOB文件。若是你使用不依赖MapReduce的MOB紧缩,这个 Sweeper tool 就不是必须的。
要配置Sweeper工具,配置以下:
<property> <name>hbase.mob.sweep.tool.compaction.ratio</name> <value>0.5f</value> <description> If there are too many cells deleted in a mob file, it's regarded as an invalid file and needs to be merged. If existingCellsSize/mobFileSize is less than ratio, it's regarded as an invalid file. The default value is 0.5f. </description> </property> <property> <name>hbase.mob.sweep.tool.compaction.mergeable.size</name> <value>134217728</value> <description> If the size of a mob file is less than this value, it's regarded as a small file and needs to be merged. The default value is 128MB. </description> </property> <property> <name>hbase.mob.sweep.tool.compaction.memstore.flush.size</name> <value>134217728</value> <description> The flush size for the memstore used by sweep job. Each sweep reducer owns such a memstore. The default value is 128MB. </description> </property> <property> <name>hbase.master.mob.ttl.cleaner.period</name> <value>86400</value> <description> The period that ExpiredMobFileCleanerChore runs. The unit is second. The default value is one day. </description> </property>
接着,添加HBase安装目录 `$HBASE_HOME`/*, 和 HBase library 目录到 yarn-site.xml。调整这个例子里的配置以适用于你的环境。
<property> <description>Classpath for typical applications.</description> <name>yarn.application.classpath</name> <value> $HADOOP_CONF_DIR, $HADOOP_COMMON_HOME/*,$HADOOP_COMMON_HOME/lib/*, $HADOOP_HDFS_HOME/*,$HADOOP_HDFS_HOME/lib/*, $HADOOP_MAPRED_HOME/*,$HADOOP_MAPRED_HOME/lib/*, $HADOOP_YARN_HOME/*,$HADOOP_YARN_HOME/lib/*, $HBASE_HOME/*, $HBASE_HOME/lib/* </value> </property>
最后,在每个配置为MOB的列族上运行sweeper工具。
$ org.apache.hadoop.hbase.mob.compactions.Sweeper _tableName_ _familyName_