HBase1.2官方文档——Architecture
架构 Architecture
63. 概述 Overview
63.1. NoSQL?
HBase是一种 "NoSQL" 数据库。"NoSQL"是一个通用词表示数据库不是RDBMS ,后者支持 SQL 作为主要访问语言。有许多种 NoSQL 数据库: BerkeleyDB 是本地 NoSQL 数据库例子, 而 HBase 是大型分布式数据库。技术上来说, HBase 更像是"数据存储(Data Store)" 多于 "数据库(Data Base)",因为缺少很多RDBMS特性, 如列类型,第二索引,触发器,高级查询语言等。
然而, HBase 有许多特征同时支持线性化和模块化扩充。HBase 集群通过增加RegionServers进行扩充,RegionServer可以运行在普通的服务器中。例如,如果集群从10个扩充到20个RegionServer,存储空间和处理容量都同时翻倍。RDBMS 也能很好扩充,但仅对一个点——特别是对一个单独数据库服务器的大小 - 同时,为了更好的性能,需要特殊的硬件和存储设备。 HBase的特性有:
-
强一致性读写: HBase 不是 "最终一致性(eventually consistent)" 数据存储. 这让它很适合高速计数聚合类任务。
-
自动分片(Automatic sharding): HBase 表通过region分布在集群中。数据增长时,region会自动分割并重新分布。
-
RegionServer 自动故障恢复
-
Hadoop/HDFS 集成: HBase 支持本机外HDFS 作为它的分布式文件系统。
-
MapReduce: HBase 通过MapReduce支持大并发处理, HBase 可以同时做源和目标。
-
Java 客户端 API: HBase 支持易于使用的 Java API 进行编程访问。
-
Thrift/REST API: HBase 也支持Thrift 和 REST 作为非Java 前端.
-
Block Cache 和 Bloom Filters: 对于大容量查询优化, HBase支持 Block Cache 和 Bloom Filters。
-
运维管理: HBase提供内置网页用于运维视角和JMX 度量.
63.2. 什么时候用 HBase?
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- 表结构如下:
Key
-
.META. region key (
.META.,,1
)
Values
-
info:regioninfo
(序列化hbase:meta的 HRegionInfo 实例) -
info:server
(保存hbase:meta的RegionServer的server:port) -
info:serverstartcode
(保存hbase:meta的RegionServer进程启动时间)
64.2. hbase:meta
hbase:meta表(之前名为 .META.)保存系统中所有region列表。hbase:meta的位置之前是在 -ROOT-表中跟踪的,但是现在存储在ZooKeeper里了。
hbase:meta表结构如下:
Key
-
Region key的格式 ([table],[region start key],[region id])
Values
-
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 工具。
64.3. 启动时序 Startup Sequencing
首先,hbase:meta 地址在ZooKeeper中查询。其次,hbase:meta会更新 server 和 startcode 的值。
需要 region-RegionServer 分配信息, 参考Region-RegionServer Assignment。
65. 客户端
HBase客户端找到服务于特定行范围的RegionServer。它通过查询hbase:meta表来做这件事。参见hbase:meta获取细节。在定位到需要的Region后,客户端会联系服务这个Region的的RegionServer,而不经过master,并发起读写请求。这些信息会缓存在客户端,这样就不用每发起一个请求就去查一下。因为master load balance或者RegionServer已死,一个Region会被重新分配,客户端就会重新查询目录表,以决定要去访问的这个用户Region的新地址。
参考Runtime Impact 以获取更多关于Master对HBase客户端通信的影响的信息
管理集群操作是经由 Admin 实例发起的。
65.1. 集群连接 Cluster Connections
相关的API在HBase 1.0中有改变。关于连接配置的信息,参考 Client configuration and dependencies connecting to an HBase cluster。
65.1.1. HBase 1.0.0的API
它已经被清理干净,返回给用户的是用来工作的接口,而不是特定的类型。
在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客户端是如何处理的信息。
连接池Connection Pooling
对需要高端多线程访问的应用 (如网页服务器或应用服务器需要在一个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.94、0.95和0.96中弃用的 |
65.2. 写缓冲和批量操作方法 WriteBuffer and Batch Methods
在HBase1.0及以后,HTable被弃用了以支持使用Table。Table不使用自动flush。要缓冲写,使用BufferedMutator类。
在Table或HTable实例被废弃前,调用close()
或 flushCommits(),使'Put'不会丢失。
65.3. 外部客户端 External Clients
关于非Java客户端和自定义协议信息,在Apache HBase External APIs。
66. 客户端请求过滤器 Client Request Filters
Get 和 Scan 实例可以用 filters 配置,以应用于 RegionServer.
过滤器可能会搞混,因为有很多类型的过滤器, 最好通过理解过滤器功能组来了解他们。
66.1. 结构过滤器 Structural
结构过滤器包含其他过滤器。
66.1.1. FilterList
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);
66.2. 列值 Column Value
66.2.1. SingleColumnValueFilter
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);
66.3. 列值比较器 Column Value Comparators
过滤器包内有好几种比较器类需要特别提及。这些比较器和其他过滤器一起使用, 如SingleColumnValueFilter。
66.3.1. RegexStringComparator
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.
66.3.2. SubstringComparator
SubstringComparator 用于检测一个子串是否存在于值中。大小写不敏感。
SubstringComparator comp = new SubstringComparator("y val"); // looking for 'my value' SingleColumnValueFilter filter = new SingleColumnValueFilter( cf, column, CompareOp.EQUAL, comp ); scan.setFilter(filter);
66.3.3. BinaryPrefixComparator
66.3.4. BinaryComparator
See BinaryComparator.
66.4. 键值元数据 KeyValue Metadata
由于HBase 内部采用键值对保存数据,键值元数据过滤器评估一行的键是否存在(如 ColumnFamily:Column qualifiers) , 对应前节所述值的情况。
66.4.1. FamilyFilter
FamilyFilter 用于过滤列族。 通常,在Scan中选择ColumnFamilie优于在过滤器中做。
66.4.2. QualifierFilter
QualifierFilter 用于基于列名(即 Qualifier)过滤。
66.4.3. ColumnPrefixFilter
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();
66.4.4. MultipleColumnPrefixFilter
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();
66.4.5. ColumnRangeFilter
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中引入
66.5. 行键 RowKey
66.5.1. RowFilter
通常认为行选择时Scan采用 startRow/stopRow 方法比较好。然而RowFilter 也可以用。
66.6. Utility
66.6.1. FirstKeyOnlyFilter
这主要用于rowcount作业。参考 FirstKeyOnlyFilter.
67. 主服务器 Master
HMaster
是Master Server的实现。主服务器(Master Server)负责监控集群中的所有RegionServer实例,并且是所有元数据更改的接口。在一个分布式集群,Master通常会运行在NameNode节点上。
J Mohamed Zahoor 在发表的blog HBase HMaster Architecture中探究了关于Master结构更多的细节。
67.1. 启动行为 Startup Behavior
如果在一个一主多备的环境中运行,所有的备(standby Master)都争着运行集群。如果激活状态的主(Master)失去了它在ZooKeeper中的租约(或者这个主关闭了),那么剩下的备争夺成为主。
67.2. 运行时的影响 Runtime Impact
一个常见的问题是到当主服务器Master宕机时,HBase集群会发生什么情况。因为HBase客户端直接与RegionServer通信,集群仍然可以在“稳定状态”中运行。
另外,每个Catalog Tables,hbase:meta
作为HBase表存在,并不常驻Master。但是,Master控制关键的功能,比如RegionServer故障恢复和完成Region分裂。
因此,虽然集群在没有主服务器的情况下仍然可以运行很短的时间,但还是应该尽快重新启动主服务器。
67.3. 接口 Interface
由HMasterInterface公开的方法主要是面向元数据的方法
:
-
表 (createTable, modifyTable, removeTable, enable, disable)
-
列族 (addColumn, modifyColumn, removeColumn)
-
Region (move, assign, unassign)
例如, 当 Admin
的方法 disableTable
被调用时, 它由主服务器(Master server)提供服务。
67.4. 进程 Processes
Master 后台运行几种线程:
67.4.1. 负载均衡器 LoadBalancer
周期性的以及在过渡期间,当没有任何Region时,负载平衡器将运行并移动各个Region以平衡集群的负载。参见Balancer设置这个属性。
参考 Region-RegionServer Assignment 以获取更多关于Region分配的信息。
67.4.2. CatalogJanitor
周期性地检查和清理hbase:meta
表。参见 <arch.catalog.meta> 获得更多关于元数据表的信息。
68. RegionServer
HRegionServer
是RegionServer的实现,负责Region的服务和管理。在分布式集群中,一个RegionServer运行在一个DataNode上。
68.1. 接口 Interface
由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(紧缩)操作。
68.2. 进程 Processes
RegionServer 后台运行几种线程:
68.2.1. CompactSplitThread
检查分割并处理minor compactions。
68.2.2. MajorCompactionChecker
检查major compactions。
68.2.3. MemStoreFlusher
周期性地将写到MemStore的内容刷到存储文件(StoreFiles)。
68.2.4. LogRoller
周期性地检查RegionServer的WAL。
68.3. 协处理器 Coprocessors
协处理器在0.92版添加。有一个详细帖子 Blog Overview of CoProcessors 供参考。文档最终会放到本参考手册,但该blog是当前能获取的大部分信息。
68.4.1. 缓存的选择Cache Choices
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获取关于堆外内存的更多信息。
68.4.2. 一般的缓存配置 General Cache Configurations
除了缓存本身的实现之外,您还可以设置一些通用配置选项来控制缓存的执行情况。
参考http://hbase.apache.org/1.2/devapidocs/org/apache/hadoop/hbase/io/hfile/CacheConfig.html。在设置了这些选项之后,重新启动或依次重新启动你的集群以使配置生效。出现错误或异常行为就检查日志。
也可以参考Prefetch Option for Blockcache,它讨论了一个由HBASE-9857引入的新的配置选项。
68.4.3. LruBlockCache Design 设计
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
68.4.4. LruBlockCache Usage 使用
对于用户表来说,块缓存是默认启用的,这意味着任意的读操作都会被加载的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的个数)。 - HFiles索引 HFiles Indexes
-
HFile 是HBase用来把数据存储到HDFS上的文件格式。它包含一个多层索引,它允许HBase在不用读取整个文件的情况下查找数据。这些索引的大小是块大小的一个因子(默认为64KB),键的大小和存储的数据量。对于大数据集来说,每个RegionServer,HFile索引的大小有1GB左右也并不少见,然而并不是所有的HFile索引都将在缓存中,因为LRU会回收那些不使用的索引的缓存。
- 键 Keys
-
每个值都与它的键一起存储(行键、列族、列标识符和时间戳) 。参考 Try to minimize row and column sizes.
- 布隆过滤器 Bloom Filters
-
就像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)禁用缓存。如果您需要快速随机读取访问,您仍然可以在该表上保留启用块缓存的配置。例如,一个示例将计算一个服务于实时通信的表中的行数,缓存这个表的每个块都会造成大量的混乱,并且肯定会将当前正在使用的数据的缓存回收。
只缓存元数据块(数据块放在fscache中) Caching META blocks only (DATA blocks in fscache)
一个有趣的设置是,我们只缓存META块,并在每个访问中读取DATA块。如果数据块放到fscache,那么当访问在一个非常大的数据集上完全随机时,这种选择可能是有意义的。要启用这样的设置,修改你的表为每个列族设置 BLOCKCACHE ⇒ 'false'
。您正在禁用这个列家族的块缓存。你不能禁用META块的缓存。因为 HBASE-4683 Always cache index and bloom blocks, 我们会缓存META块即使BlockCache被禁用。
68.4.5. 堆外内存块缓存 Off-heap Block Cache
如何启用BucketCache How to Enable BucketCache
通常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 类获取配置项和描述信息。
BucketCache Example Configuration
这个例子配置了 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:
直接内存(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 与 |
68.4.6. 压缩的块缓存 Compressed BlockCache
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
。
68.5. RegionServer分割的实现 RegionServer Splitting Implementation
由于写请求是由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。
68.6. 预写日志 Write Ahead Log (WAL)
68.6.1. 目的 Purpose
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 的文章.
68.6.2. MultiWAL 多个WAL
每个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即可。
68.6.3. WAL Flushing
TODO (describe).
68.6.4. WAL Splitting WAL分割
一个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),编辑文件被删除。
对日志分割期间出现的错误的处理 Handling of Errors During Log Splitting
如果你把 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。 如果设置了这个标志,我们需要做的不仅仅是使分割操作失败。
当分割一个崩溃的RegionServer的WAL时,如何处理EOFException How EOFExceptions are treated when splitting a crashed RegionServer’s WALs
如果一个EOFException发生在分割日志时,分割操作继续进行,即使 hbase.hlog.split.skip.errors
被设置成 false。在读取要分割的日志文件集中的最后的日志时,可能会遇到
EOFException 因为RegionServer可能在崩溃时正在写入一条记录。关于这个背景,参考 HBASE-2643 Figure how to deal with eof splitting logs
日志分割时的性能改善 Performance Improvements during Log Splitting
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会扫描另一个未声明的任务。
分割日志worker如何处理一个任务 How the Split Log Worker Approaches a Task-
它查询任务状态,只有在任务处于`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里未完成的任务。如果没有找到,它会抛出一个异常,这样就可以立即重试日志分割,而不是挂在那里等待发生不会发生的事情。(博主注:这句话不理解,既然前面说失去对已完成任务的跟踪,为什么后面要去找未完成的任务,而且既然没有找到未完成任务,为什么要重试呢)
分布式日志重做 Distributed Log Replay
在一个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)。 分布式日志重做对于滚动升级是不安全的。
68.6.5. 禁用WAL Disabling the WAL
为了在某些特殊情况下改善性能,禁用WAL是可能的。然而,禁用WAL会把你的数据置于风险之中。推荐的禁用WAL的情况,只有在做批量加载(Bulk Load)的期间。这是因为,在出现问题时,可以重新运行批量加载,不存在数据丢失的风险。
通过调用HBase客户端中 field 的Mutation.writeToWAL(false)可以禁用WAL。使用
Mutation.setDurability(Durability.SKIP_WAL)
和 Mutation.getDurability() 方法来设置和获取域(field)的值。只是一个指定的表的话,无法禁用 WAL。
如果你在批量加载以外的情况下禁用WAL,你的数据会处于风险之中。 |
69. Regions
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.
69.1. Region数量的考虑 Considerations for Number of Regions
一般来说,HBase的设计目的是在每个服务器上运行一组少量的(20-200)Region,且Region相对较大(5-20gb)。对此的考虑如下:
69.1.1. 为什么我要保持低数量的Region? Why should I keep my Region count low?
通常,您想在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 获取配置指导。
69.2. Region-RegionServer 的分配
本节描述了Region是怎样被分配到RegionServer上的。
69.2.1. Startup 启动
当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.
69.2.2. 故障切换(失效转义) Failover
当一个 RegionServer 失效:
-
RegionServer中的 regions 立即成为不可用状态,因为这个 RegionServer 宕了.
-
Master 将检测到这个RegionServer已经失效了。
-
Region原有的分配失效,并采用跟启动时同样顺序的步骤重新分配Region
-
正在进行的查询操作会重新执行,不会丢失。
-
Region切换到新RegionServer动作要在以下时间内完成:
ZooKeeper session timeout + split time + assignment/replay time
69.2.3. Region负载均衡 Region Load Balancing
Region可以被 LoadBalancer 定期移动。
69.2.4. Region状态转换 Region State Transition
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会再次尝试关闭这些节点。
69.3. Region-RegionServer 的位置选择
随着时间的推移,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.
69.4. Region分裂(分割)Region Splits
当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分割 (以及你为什么要这么做)。
69.4.1. 自定义分割策略 Custom Split Policies
你可以使用一个自定义的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。 |
69.5. 手动分割Region Manual Region Splitting
手动分割你的表是可以的,可以在创建表的时候(预分割),也可以在以后的管理操作中。你选择手动分割你的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 |
69.5.1. 决定分割点 Determining Split Points
手动分割你的表的目的是为了在只通过良好的行键设计并不能达到这个平衡负载的情况下,提高在集群中平衡负载的机会。记住这一点,你分割Region的方式非常依赖于数据的特征。也许你已经知道分割你的表的最好的方法了,如果没有,分割你的表的方法要依赖于你的行键。
- 字母数字组成的 Rowkeys
-
如果你的行键以字母或数字开头,你可以在字母或数字边界上分割你的表。例如,下面的命令创建了一个有多个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
。
69.6. 在线Region合并 Online Region Merges
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
参数复写了这个行为,仅为专家使用。
69.7. 存储 Store
一个存储包含了一个内存存储(MemStore)和0或多个文件存储(StoreFile--HFile)。一个存储可以定位到一个Region的表中的一个列族。
69.7.1. MemStore
MemStores是Store中的内存Store,可以进行修改操作。修改的内容是Cell/KeyValues。当flush操作被请求时,现有的memstore会生成快照,然后被清空。HBase继续为从新的MemStore的编辑信息提供服务,并且直到flusher报告刷新成功之前,保存快照。flusher报告刷新成功之后,快照才被删除。注意,当刷新发生时,属于同一个Region的MemStores都会被刷新。
69.7.2. MemStore Flush
可以在下面列出的任何条件下触发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。
69.7.3. 扫描 Scans
-
当客户端发出在一个表上的扫描时,HBase产生
RegionScanner
对象, 每个Region一个,用来服务于扫描请求。 -
RegionScanner
对象包含一个StoreScanner
对象的列表,每个列族一个。 -
每个
StoreScanner
对象进一步包含了一个StoreFileScanner
对象的列表,对应每个列族的 StoreFile 和 HFile,还包含了一个MemStore中KeyValueScanner
对象的列表。 -
这两个列表合并为一个,它以升序排序,顺序是以列表结尾的MemStore的扫描对象进行排列的。
-
当一个
StoreFileScanner
对象被构造,它就和一个MultiVersionConcurrencyControl
的读取位置(read point)有关, 这个位置就是当前的memstoreTS,过滤掉在这个读取位置以后任意新的更新。
69.7.4. StoreFile (HFile)
StoreFiles 是数据存在的地方。
HFile Format
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 Tool
要想看到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
的文档。
StoreFile 在HDFS上的目录结构
关于StoreFiles在HDFS上是什么样子、目录结构如何的信息参考 Browsing HDFS for HBase Objects。
69.7.5. Blocks 块
StoreFiles 由块组成。块大小是基于每个列族配置的。
压缩发生在StoreFiles的块级别上。更多关于压缩的信息,参考 Compression and Data Block Encoding In HBase。
更多关于块的信息,参考 HFileBlock source code.
69.7.6. KeyValue
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。
Example
为了强调上面的观点,我们来看看在同一行中,两个不同的列都发生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就越大。
69.7.7. 紧缩 Compaction
-
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合并的信息。
紧缩策略 Compaction Policy - HBase 0.96.x and newer
紧缩大的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集合。
不管使用的紧缩策略是什么,文件选择都是由几个可配置参数控制的,并且在多步骤方法中进行。这些参数将在文中进行解释,然后将在一个表中给出它们的描述、默认值以及更改它们的含义。
被卡住 Being Stuck
当MemStore变得过大时,它需要把自己的内容刷新到一个StoreFile中。然而,一个Store只能拥有 hbase.hstore.blockingStoreFiles
个文件,所以MemStore需要等待这个Store的StoreFiles的数量经一次或多次紧缩而变少。然而,如果MemStore 变得比 hbase.hregion.memstore.flush.size 还大的话,它将不能把自己的内容刷新到一个StoreFile中。
如果MemStore过大,而且StoreFiles的数量过多,这种算法就被称为“被卡住”了。紧缩算法检查这种“卡住”的情况,并提供机制缓解这个问题。
ExploringCompactionPolicy 算法
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 算法
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紧缩。
紧缩算法用到的参数 Parameters Used by Compaction Algorithm
该表包含了用于紧缩的主要配置参数。这个列表并不详尽。要调整这些参数的默认值,编辑 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 to128
) 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
紧缩文件的选择 Compaction File Selection
遗留信息 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时,这个文件被选择进行紧缩操作
。
Minor Compaction File Selection - Example #1 (Basic Example)
这个例子反映了单元测试 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紧缩的文件是23、12、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的限制
Minor Compaction File Selection - Example #2 (文件数量不够做紧缩 Not Enough Files ToCompact)
这个例子反映了单元测试 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一样,但是没有足够数量的文件做紧缩操作。
Minor Compaction File Selection - Example #3 (因文件数量超过最大限制数而限制文件做紧缩 Limiting Files To Compact)
这个例子反映了单元测试 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 的配置参数表里 |
日期分层紧缩 Date Tiered Compaction
日期分层紧缩是一个基于时间的存储文件紧缩的策略,这个策略对时间序列的数据做基于时间范围的扫描有好处。
什么时候使用日期分层紧缩 When To Use Date Tiered Compactions
考虑对限定了时间范围的数据进行的读取操作使用日期分层紧缩,特别是扫描近期的数据。
不要在以下情景使用
-
没有时间范围限定的随机读取 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
性能测试展示了基于时间范围扫描的性能在限定的时间范围内有很大改善,特别是扫描近期的数据。
启用时间分层紧缩 Enabling Date Tiered Compaction
你可以对一个表或列族启用时间分层紧缩,通过设置它的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中的设计规格 |
Experimental: Stripe Compactions
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 Compactions
如果遇到以下情况,考虑使用Stripe Compaction:
-
较大的Regions。在没有附加的MemStore开销和Region管理开销的情况下,你可以得到较小Regions的积极影响。
-
非统一的键(分布不均的键?),例如键中的时间维度。只有收到新键的Stripes才会被紧缩。旧数据将不会经常紧缩。
性能测试显示出,读的性能稍微改善了,读和写性能的可变性大大降低了。在大型的非均匀的行键的Region上,例如一个hash前缀的时间戳键,总体的长期的性能得到了改善。这些性能收益在一个已经很大的表中是最显著的。性能改进可能会扩展到Region分割。
启用 Stripe Compaction
你可以为一个表或一个列族启用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 ororg.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上执行。这在新表上不是必须的。
Configuring Stripe Compaction
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。
70. Bulk Loading 批量加载
70.1. Overview
HBase 有几种把数据加载到表中的方法。最直接的方法是在MapReduce作业中使用 TableOutputFormat
类,或者使用普通的客户端APIs;但是这些通常不是最有效率的方法。
批量加载功能使用一个MapReduce作业,以HBase内部数据的格式输出表数据,然后直接把生成的StoreFiles加载到运行中的集群里去。和简单地使用HBase API相比,使用bulk load将会占用更少的CPU和网络资源。
70.2. Bulk Load Limitations限制
由于批量加载绕过了写路径,所以WAL并没有作为过程中的一部分被写下来。副本通过读取WAL进行工作,所以它不能看到批量加载进来的数据 - 对编辑使用 Put.setDurability(SKIP_WAL)也会有相同的效果。处理的一种方式是把原始的文件或HFile传到其他集群,并且以其他的方式处理
。
70.3. Bulk Load Architecture 结构
HBase bulk load 过程有两个主要步骤组成。
70.3.1. 通过MapReduce作业准备数据 Preparing data via a MapReduce job
批量加载的第一步是通过一个使用了 HFileOutputFormat2的MapReduce作业生成HBase数据文件(StoreFiles)。这个输出格式以HBase内部的存储格式写出数据,以便于它们过后被有效地加载到集群中
。
为了有效率地运行,HFileOutputFormat2
必须配置成每个输出的HFile都要放在一个单独的Region内。为了做到这一点,输出将要被加载到HBase里的作业使用Hadoop的 TotalOrderPartitioner
类来把map的输出划分到为分开的键空间中不相交的范围里去,对应于表中Regions的键的范围。
HFileOutputFormat2
有一个方便的函数, configureIncrementalLoad()
, 可以基于一个表里当前的Region边界自动地建立一个 TotalOrderPartitioner
。
70.3.2. 完成数据加载 Completing the data load
通过结合“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中还不存在,这个工具将会自动生成这个表。 |
70.4. See Also
要获取更多关于所引用工具的信息,参考 ImportTsv 和 CompleteBulkLoad.
参考 How-to: Use HBase Bulk Loading, and Why 获取最近关于批量加载的最新博客。
70.5. Advanced Usage
虽然 importtsv
工具在很多情况下都有用,高级用户可能想以编程的方式生成数据,或者从其他格式导入数据。要开始这么做,先研究 ImportTsv.java
,查看关于HFileOutputFormat2的JavaDoc。
批量加载的Import步骤可以编写程序完成。参考 LoadIncrementalHFiles
类以获取信息。
71. HDFS
由于 HBase 在 HDFS 上运行(每个存储文件也被写为HDFS的文件),必须理解 HDFS 结构,特别是它如何存储文件,处理故障转移,备份块。
参考 Hadoop 文档 HDFS Architecture 获取更多信息。
72. 基于时间轴一致性的高可用读取 Timeline-consistent High Available Reads
72.1. Introduction
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不可用时会有一段时间阻碍客户端的写操作)。
72.2. 时间轴一致性 Timeline Consistency
有了这个功能,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,等等。
72.3. 权衡取舍 Tradeoffs
拥有二级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?
72.4. Where is the code
该特性由两个阶段交付,第一阶段和第二阶段。第一阶段在HBase-1.0.0发布时完成,意味着使用HBase-1.0.0的话,你可以使用第一阶段的所有特性。第二阶段在HBase-1.1.0被提交,意味着所有在1.1.0之后的HBase版本都包含第二阶段的特性。
72.5. 将写入操作传播到Region副本中 Propagating writes to region replicas
正如上面所讨论的,写入操作只到主Region副本中进行。要把写入操作从主Region副本传播到二级Region副本中,有两个不同的机制。对于只读表,你不需要使用下列任何方法。禁用和启用该表应该使所有Region的副本里的数据都是可用的。对于变化的表,你必须只能下列机制中的一种:StoreFile Refresher, 或 Async WAL replication。 推荐使用后者。
72.5.1. StoreFile Refresher
第一种机制是 store file refresher,在 HBase-1.0+ 引入。Store file refresher 是在每一个RegionServer中的线程,周期性地运行,并且为二级Region副本做主Region存储文件的刷新操作。如果它被启用,这个refresher将保证二级Region副本及时地看到主Region中新刷新的、紧缩的或批量加载的文件。但是,这意味着只有刷新的数据可以从二级Region副本中读取到,并且在这个refresher运行之后,正在较长的一段时间内,二级Region副本中的数据将会落后主Region副本。
要调整这个功能特性,你应该配置 hbase.regionserver.storefile.refresh.period
为一个非零的值。参考下面的配置小节。
72.5.2. Asnyc WAL replication
把写入传播到二级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'
72.6. 存储文件的TTL Store File TTL
在上面提到的两种写传播方法中,主服务器的存储文件将在独立于主Region的二级Regions中打开。因此,对于那些被主Region紧缩的文件,二级Region可能仍然是引用这些文件供读取,这就导致文件已经被compact掉而读取不到。这两种功能都使用HFlieLinks引用文件,但是对于文件不会过早地删除的保证(目前)没有任何保障。因此,你应该设置属性参数 hbase.master.hfilecleaner.ttl
为一个较大值,例如 1 小时,以保障在请求副本时你将不会受到IOException。
72.7. 元数据表的Region的副本复制 Region replication for META table’s region
目前,异步WAL复制做不了META表的WAL复制。META表的二级副本仍然把持久化的存储文件中的内容刷新到自己。因此我们可以设置hbase.regionserver.meta.storefile.refresh.period属性为非0值,用以刷新元数据存储文件。注意这个配置和hbase.regionserver.storefile.refresh.period配置不同。
72.8. 内存处理 Memory accounting
二级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可以观察到部分行在多个列族的更新(因为列族的刷新是独立的)。默认情况下,不要频繁地执行这个操作。如果需要,你可以将这个值设置为一个大的数字以禁用该功能,但是要警告你的是它可能导致复制永久地阻塞。
72.9. 二级副本故障转移 Secondary replica failover
当一个二级region副本第一次上线或者失败时,它可能服务过一些来自它的memstore的编辑。因为对于二级副本,恢复的处理方式不同,必须确认它在分配之后开启服务之前无法上线。为了做到这一点,这个二级Region会等待,直到它观察到一个完整的刷新周期(启动刷新、提交刷新)或从主Region复制了一个“Region打开事件”。在这种情况发生之前,这个二级Region副本将通过抛出一个带有消息“The region’s reads are disabled”的IOException以拒绝所有读取请求。但是,其他副本可能仍然可用来读取,因此不会对具有时间轴一致性(TIMELINE)的rpc造成任何影响。为了便于更快的恢复,当这个二级Region打开时将会触发一个来自主Region的刷新请求。配置参数 hbase.region.replica.wait.for.primary.flush
(默认启用) 可以被用来禁用这个功能,如果需要的话。
72.10. 配置参数 Configuration properties
要使用高可用读取,你应该在 hbase-site.xml
文件中设置如下参数。没有特定的配置来启用或禁用Region副本,替代的方法是,你可以在创建表或者修改表结构时,改变(增或减)每个表的Region副本数量。下列配置是用来配置使用异步WAL复制和配置元数据副本为3。
72.10.1. 服务端属性 Server side properties
<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中。
72.10.2. 客户端属性 Client side properties
确保在所有要使用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_