HBase1.2官方文档——Apache HBase Performance Tuning

原文档:http://hbase.apache.org/1.2/book.html#performance

Apache HBase Performance Tuning

92. Operating System

92.1. Memory

RAM, RAM, RAM. Don’t starve HBase.

92.2. 64-bit

Use a 64-bit platform (and 64-bit JVM).

92.3. Swapping

Watch out for swapping. Set swappiness to 0.

92.4. CPU

确保您已经设置了您的Hadoop以使用本地的、硬件的校验和。 See link:[hadoop.native.lib].


93. Network

也许,避免网络问题降低Hadoop和HBase性能的最重要因素就是所使用的交换机硬件。在项目范围内,集群大小翻倍或三倍甚至更多时,早期的决定可能导致主要的问题。

要考虑的重要事项:

  • 设备交换机容量

  • 系统连接数量

  • 上行容量

93.1. 单交换机 Single Switch

单交换机配置最重要的因素,是硬件交换容量,所有系统连接到交换机产生的流量的处理能力。一些低价硬件商品,相对全交换机,具有较低交换能力。

93.2. 多交换机 Multiple Switches

多交换机在系统结构中是潜在陷阱。低价硬件的最常用配置是1Gbps上行连接到另一个交换机。 该常被忽略的窄点很容易成为集群通讯的瓶颈。特别是MapReduce任务通过该上行连接同时读写大量数据时,会导致饱和。

缓解该问题很简单,可以通过多种途径完成:

  • 针对要创建的集群容量,采用合适的硬件

  • 采用更大单交换机配置,如单48端口相较 2x 24 端口为优

  • 配置上行端口聚合(port trunking)来利用多网络接口增加交换机带宽。
    (译者注:port trunk:将交换机上的多个端口在物理上连接起来,在逻辑上捆绑在一起,形成一个拥有较大带宽的端口,组成一个干路,以达到平衡负载和提供备份线路,扩充带宽的目的。)

93.3. 多机架 Multiple Racks

多机架配置带来多交换机同样的潜在问题。导致性能降低的原因主要来自两个方面:

  • 较低的交换机容量性能

  • 到其他机架的上行链路不足

如果机架上的交换机有合适交换容量,可以处理所有主机全速通信,那么下一个问题就是如何自动导航更多的交错在机架中的集群。最简单的避免横跨多机架问题的办法,是采用端口聚合来创建到其他机架的捆绑的上行的连接。然而该方法下行侧,是潜在被使用的端口开销。举例:从机架A到机架B创建 8Gbps 端口通道,采用24端口中的8个来和其他机架互通,ROI(投资回报率)很低。采用太少端口意味着不能从集群中传出最多的东西。

机架间采用10Gbe 链接将极大增加性能,确保交换机都支持10Gbe 上行连接或支持扩展卡,后者相对上行连接,允许你节省机器端口。

93.4. 网络接口 Network Interfaces

所有网络接口功能正常吗?你确定?参考故障诊断用例: Case Study #1 (Performance Issue On A Single Node).

93.5. Network Consistency and Partition Tolerance

The CAP Theorem states that a distributed system can maintain two out of the following three characteristics: - *C*onsistency — 所有节点都能看到相同的数据。 - *A*vailability — 每个请求都能收到成功或失败的响应。 - *P*artition tolerance — 即使一些组件变成不可用,系统也能继续运行。

HBase favors consistency and partition tolerance, where a decision has to be made. Coda Hale explains why partition tolerance is so important, in http://codahale.com/you-cant-sacrifice-partition-tolerance/.

Robert Yokota used an automated testing framework called Jepson to test HBase’s partition tolerance in the face of network partitions, using techniques modeled after Aphyr’s Call Me Maybe series. The results, available as a blog post and an addendum, show that HBase performs correctly.


94. Java

94.1. 垃圾收集器和Apache HBase The Garbage Collector and Apache HBase

94.1.1. 长时间GC停顿 Long GC pauses

在这个PPT Avoiding Full GCs with MemStore-Local Allocation Buffers, Todd Lipcon 描述了在HBase中常见的两种“世界停止”式的GC操作,尤其在加载的时候。一种是CMS失败的模式(译者注:CMS是一种GC的算法),另一种是老一代的堆碎片导致的。

要想定位第一种,只要将CMS执行的时间比默认提前就可以了,加入-XX:CMSInitiatingOccupancyFraction参数,把值调得比默认值低。可以先从60%和70%开始(这个值调的越低,触发的GC次数就越多,消耗的CPU时间就越长)。要想定位第二种错误,Todd加入了一个实验性的功能(MSLAB),在HBase 0.90.x中这个是要明确指定的(在0.92.x中,这个是默认项),将你的Configuration中的hbase.hregion.memstore.mslab.enabled设置为true。详细信息和背景资料,可以看这个PPT。最新的JVMs可以更好地处理碎片,所以要确保您正在运行最近的发行版。在这个信息中阅读,Identifying concurrent mode failures caused by fragmentation。请注意,当启用时,每个MemStore实例将至少占用内存中的一个MSLAB实例。如果你有数千个Regions或每个Region都有很多列族的大量Region,这种MSLAB的分配可能负责您的堆分配的很大一部分,并且在一个极端的情况下导致您的OOME。在此情况下禁用MSLAB,或降低它使用的内存或者在每个服务器上浮动较少的Region。

如果你有大量的写的工作量,查看 HBASE-8163 MemStoreChunkPool: An improvement for JAVA GC when using MSLAB。它描述了在大量写加载时降低年轻代GC的量的配置。如果你没有安装HBASE-8163,且你试图改善你的年轻代GC的次数,一个技巧可以考虑,在hbase-env.sh中设置GC配置参数 -XX:PretenureSizeThreshold 的值小于 hbase.hregion.memstore.mslab.chunksize,以使MSLAB分配是直接发生在终身代的空间而不是在年轻代中。 You’d do this because these MSLAB allocations are going to likely make it to the old gen anyways and rather than pay the price of a copies between s0 and s1 in eden space followed by the copy up from young to old gen after the MSLABs have achieved sufficient tenure, save a bit of YGC churn and allocate in the old gen directly.

长时间GC的其他来源可能是JVM本身的日志记录。 参考 Eliminating Large JVM GC Pauses Caused by Background IO Traffic

GC日志的更多信息,参考 JVM Garbage Collection Logs.

考虑启用 off-heap Block Cache,这可以减少GC暂停的时间。参考 Block Cache


95. HBase Configurations

95.1. Managing Compactions

对于大型的系统,你需要考虑管理紧缩和分割 link:[compactions and splits] 

95.2. hbase.regionserver.handler.count

95.3. hfile.block.cache.size

See [hfile.block.cache.size]. A memory setting for the RegionServer process.

95.4. Prefetch Option for Blockcache

HBASE-9857 添加了一个新的选项用来在打开BlockCache时预获取HFile的内容,这个选项在列族或RegionServer属性中设置。这个选项在HBase 0.98.3及以后的版本中可用。 The purpose is to warm the BlockCache as rapidly as possible after the cache is opened, using in-memory table data, and not counting the prefetching as cache misses. 这对快速读取很有用,但是如果要被预加载的数据不适合BlockCache的话,不是一个好主意。It is useful for tuning the IO impact of prefetching versus the time before all data blocks are in cache.

To enable prefetching on a given column family, you can use HBase Shell or use the API.

Example 54. Enable Prefetch Using HBase Shell
hbase> create 'MyTable', { NAME => 'myCF', PREFETCH_BLOCKS_ON_OPEN => 'true' }
Example 55. Enable Prefetch Using the API
// ...
HTableDescriptor tableDesc = new HTableDescriptor("myTable");
HColumnDescriptor cfDesc = new HColumnDescriptor("myCF");
cfDesc.setPrefetchBlocksOnOpen(true);
tableDesc.addFamily(cfDesc);
// ...

See the API documentation for CacheConfig.

To see prefetch in operation, enable TRACE level logging on org.apache.hadoop.hbase.io.hfile.HFileReaderImpl in hbase-2.0+ or on org.apache.hadoop.hbase.io.hfile.HFileReaderV2 in earlier versions, hbase-1.x, of HBase.

95.5. hbase.regionserver.global.memstore.size

See [hbase.regionserver.global.memstore.size]. This memory setting is often adjusted for the RegionServer process depending on needs.

95.6. hbase.regionserver.global.memstore.size.lower.limit

See [hbase.regionserver.global.memstore.size.lower.limit]. This memory setting is often adjusted for the RegionServer process depending on needs.

95.7. hbase.hstore.blockingStoreFiles

See [hbase.hstore.blockingStoreFiles]. If there is blocking in the RegionServer logs, increasing this can help.

95.8. hbase.hregion.memstore.block.multiplier

See [hbase.hregion.memstore.block.multiplier]. If there is enough RAM, increasing this can help.

95.9. hbase.regionserver.checksum.verify

Have HBase write the checksum into the datablock and save having to do the checksum seek whenever you read.

95.10. Tuning callQueue Options

HBASE-11355 introduces several callQueue tuning mechanisms which can increase performance. See the JIRA for some benchmarking information.

To increase the number of callqueues, set hbase.ipc.server.num.callqueue to a value greater than 1. To split the callqueue into separate read and write queues, set hbase.ipc.server.callqueue.read.ratio to a value between 0 and 1. This factor weights the queues toward writes (if below .5) or reads (if above .5). Another way to say this is that the factor determines what percentage of the split queues are used for reads. The following examples illustrate some of the possibilities. Note that you always have at least one write queue, no matter what setting you use.

  • The default value of 0 does not split the queue.

  • A value of .3 uses 30% of the queues for reading and 60% for writing. Given a value of 10 for hbase.ipc.server.num.callqueue, 3 queues would be used for reads and 7 for writes.

  • A value of .5 uses the same number of read queues and write queues. Given a value of 10 for hbase.ipc.server.num.callqueue, 5 queues would be used for reads and 5 for writes.

  • A value of .6 uses 60% of the queues for reading and 30% for reading. Given a value of 10 for hbase.ipc.server.num.callqueue, 7 queues would be used for reads and 3 for writes.

  • A value of 1.0 uses one queue to process write requests, and all other queues process read requests. A value higher than 1.0 has the same effect as a value of 1.0. Given a value of 10 for hbase.ipc.server.num.callqueue, 9 queues would be used for reads and 1 for writes.

You can also split the read queues so that separate queues are used for short reads (from Get operations) and long reads (from Scan operations), by setting the hbase.ipc.server.callqueue.scan.ratio option. This option is a factor between 0 and 1, which determine the ratio of read queues used for Gets and Scans. More queues are used for Gets if the value is below .5 and more are used for scans if the value is above .5. No matter what setting you use, at least one read queue is used for Get operations.

  • A value of 0 does not split the read queue.

  • A value of .3 uses 60% of the read queues for Gets and 30% for Scans. Given a value of 20 for hbase.ipc.server.num.callqueue and a value of .5 for hbase.ipc.server.callqueue.read.ratio, 10 queues would be used for reads, out of those 10, 7 would be used for Gets and 3 for Scans.

  • A value of .5 uses half the read queues for Gets and half for Scans. Given a value of 20 for hbase.ipc.server.num.callqueueand a value of .5 for hbase.ipc.server.callqueue.read.ratio, 10 queues would be used for reads, out of those 10, 5 would be used for Gets and 5 for Scans.

  • A value of .6 uses 30% of the read queues for Gets and 60% for Scans. Given a value of 20 for hbase.ipc.server.num.callqueue and a value of .5 for hbase.ipc.server.callqueue.read.ratio, 10 queues would be used for reads, out of those 10, 3 would be used for Gets and 7 for Scans.

  • A value of 1.0 uses all but one of the read queues for Scans. Given a value of 20 for hbase.ipc.server.num.callqueue and a value of`.5` for hbase.ipc.server.callqueue.read.ratio, 10 queues would be used for reads, out of those 10, 1 would be used for Gets and 9 for Scans.

You can use the new option hbase.ipc.server.callqueue.handler.factor to programmatically tune the number of queues:

  • A value of 0 uses a single shared queue between all the handlers.

  • A value of 1 uses a separate queue for each handler.

  • A value between 0 and 1 tunes the number of queues against the number of handlers. For instance, a value of .5 shares one queue between each two handlers.

    Having more queues, such as in a situation where you have one queue per handler, reduces contention when adding a task to a queue or selecting it from a queue. The trade-off is that if you have some queues with long-running tasks, a handler may end up waiting to execute from that queue rather than processing another queue which has waiting tasks.

For these values to take effect on a given RegionServer, the RegionServer must be restarted. These parameters are intended for testing purposes and should be used carefully.


96. ZooKeeper

See ZooKeeper for information on configuring ZooKeeper, and see the part about having a dedicated disk.


97. Schema Design

97.1. Number of Column Families

97.2. Key and Attribute Lengths

See Try to minimize row and column sizes. See also However…​ for compression caveats.

97.3. Table RegionSize

The regionsize can be set on a per-table basis via setFileSize on HTableDescriptor in the event where certain tables require different regionsizes than the configured default regionsize.

See Determining region count and size for more information.

97.4. 布隆过滤器 Bloom Filters

布隆过滤器以它的创造者命名Burton Howard Bloom,它是一个数据结构用来判断一个给定元素是否是一个数据集中的成员。布隆过滤器返回的阳性结果不一定准确,但是阴性结果肯定是准确的。对于量大到传统的hash机制无法判断的数据集,布隆过滤器被设计成“足够准确”的。更多关于布隆过滤器的信息,参考 http://en.wikipedia.org/wiki/Bloom_filter.

在HBase中,布隆过滤器对一个在包含了想要得到的Row的StoreFile的Get操作(布隆过滤器对于Scan操作无效),提供一个轻量级的内存中结构以减少磁盘读取次数。随着并行读的数量的增加,潜在的性能也会增加。

布隆过滤器本身被存储在每个HFile的metadata中,从不需要被更新。当一个HFile因为Region被部署到RegionServer上而被打开时,布隆过滤器会被加载到内存中。

HBase包含一些用于折叠布隆过滤器的调优机制,以减小它的大小,并将误报率保持在理想的范围内。

布隆过滤器在 HBASE-1200中引入。在 HBase 0.96之后,基于行的布隆过滤器默认被启用。(HBASE-8450)

获取关于HBase中布隆过滤器的更多信息,参考 Bloom Filters ,或Quora 讨论: How are bloom filters used in HBase?.

97.4.1. 何时使用布隆过滤器 When To Use Bloom Filters

在HBase 0.96之后,基于行的布隆过滤器被默认启用。你可以选择禁用它们,或者选择改变一些表使用行+列的布隆过滤器,这取决于你数据的特征和它如何被加载到HBase。

要确定布隆过滤器是否会产生积极的影响,查看RegionServer metrics中(可能是UI上查看)blockCacheHitRatio 的值。如果布隆过滤器被启用,blockCacheHitRatio 的值应该增加,因为布隆过滤器过滤掉了那些绝对不需要的块。

你可以选择为一行或为一个行列组合启用布隆过滤器。如果你通常Scan多个整行,那么行+列的组合不能提供任何的好处。一个基于行的布隆过滤器可以在一个行+列的Get上操作,但是反过来不行。但是,如果你有大量的column-level Puts操作,像一行数据可能出现在每一个StoreFile中,一个row-based过滤器将会一直返回一个阳性结果而不会提供任何益处。除非你每行只有一列,否则row+column的布隆过滤器会要求更多的空间,以致于存储更多的keys。当每个数据entry的大小至少是几KB时,布隆过滤器的工作效果最好。

当你的数据存储在几个大的StoreFiles时,布隆过滤器的开销会减少,以避免当low-level scans寻找一个特定行时额外的磁盘IO。

布隆过滤器在删除时需要重构,所以对于有大量删除的环境并不适用。

97.4.2. Enabling Bloom Filters

Bloom filters are enabled on a Column Family. You can do this by using the setBloomFilterType method of HColumnDescriptor or using the HBase API. Valid values are NONE, ROW (default), or ROWCOL. See When To Use Bloom Filters for more information on ROW versus ROWCOL. See also the API documentation for HColumnDescriptor.

The following example creates a table and enables a ROWCOL Bloom filter on the colfam1 column family.

hbase> create 'mytable',{NAME => 'colfam1', BLOOMFILTER => 'ROWCOL'}

97.4.3. Configuring Server-Wide Behavior of Bloom Filters

You can configure the following settings in the hbase-site.xml.

ParameterDefaultDescription

io.storefile.bloom.enabled

yes

Set to no to kill bloom filters server-wide if something goes wrong

io.storefile.bloom.error.rate

.01

The average false positive rate for bloom filters. Folding is used to maintain the false positive rate. Expressed as a decimal representation of a percentage.

io.storefile.bloom.max.fold

7

The guaranteed maximum fold rate. Changing this setting should not be necessary and is not recommended.

io.storefile.bloom.max.keys

128000000

For default (single-block) Bloom filters, this specifies the maximum number of keys.

io.storefile.delete.family.bloom.enabled

true

Master switch to enable Delete Family Bloom filters and store them in the StoreFile.

io.storefile.bloom.block.size

131072

Target Bloom block size. Bloom filter blocks of approximately this size are interleaved with data blocks.

hfile.block.bloom.cacheonwrite

false

Enables cache-on-write for inline blocks of a compound Bloom filter.

97.5. ColumnFamily BlockSize

The blocksize can be configured for each ColumnFamily in a table, and defaults to 64k. 更大的cell需要更大的 blocksizes。BlockSize和StoreFile索引存在相反的关系(如果区块大小增加了一倍,那么得到的索引应该减少一半)。

参考 HColumnDescriptor and Store 以获取更多信息。

97.6. In-Memory ColumnFamilies

列族可以被定义为in-memory,数据和其他列族的数据一样,仍然被持久化在磁盘上。In-memory块在Block Cache有最高的优先级,但是不能保证整个表都能在内存中。

See HColumnDescriptor for more information.

97.7. Compression

生产系统中应该在列族定义中使用压缩。 See Compression and Data Block Encoding In HBase for more information.

97.7.1. However…​

压缩能缩小在磁盘中的数据。当数据在in-memory时(如,在MemStore中)或者在网络中(如,在RegionServer和Client间传输中),它是膨胀的。因此,虽然使用列族压缩是一种最佳实践,但它不会完全消除over-sized Keys、over-sized列族或over-sized列名 所带来的影响。

 

参考 Try to minimize row and column sizes 关于Schema设计的提示 和 KeyValue 以获取更多关于HBase内部存储数据的信息。


98. HBase通用模式 HBase General Patterns

98.1. 常量 Constants

人们刚开始使用HBase时,趋向于写如下的代码:

Get get = new Get(rowkey);
Result r = table.get(get);
byte[] b = r.getValue(Bytes.toBytes("cf"), Bytes.toBytes("attr"));  // returns current version of value

然而,特别是在循环内(和 MapReduce 工作内), 重复地将列族和列名转为字节数组代价昂贵。最好使用字节数组常量,如下:

public static final byte[] CF = "cf".getBytes();
public static final byte[] ATTR = "attr".getBytes();
...
Get get = new Get(rowkey);
Result r = table.get(get);
byte[] b = r.getValue(CF, ATTR);  // returns current version of value

99. 写到HBase Writing to HBase

99.1. 批量加载 Batch Loading

如果可以的话,尽量使用批量加载工具。参考 Bulk Loading. 否则注意以下内容。

99.2. 表创建:预创建Regions  Table Creation: Pre-Creating Regions

默认情况下HBase创建表会新建一个区域。执行批量导入,意味着所有的client会写入这个区域,直到这个区域足够大,以至于分裂,在集群中分布。一个有效的提高批量导入的性能的方式,是预创建空的Regions。最好稍保守一点,因为过多的区域会实实在在的降低性能。

使用HBase API,有两种不同的方式预创建区域。第一种是依赖 默认的 Admin 策略 (由 Bytes.split 实现)…​

byte[] startKey = ...;      // your lowest key
byte[] endKey = ...;        // your highest key
int numberOfRegions = ...;  // # of regions to create
admin.createTable(table, startKey, endKey, numberOfRegions);

其他使用HBase API的方式是自己定义分割…​

byte[][] splits = ...;   // create your own splits
admin.createTable(table, splits);

你可以使用HBase Shell在创建表时指定分割选项,达到相似的效果。

# create table with specific split points
hbase>create 't1','f1',SPLITS => ['\x10\x00', '\x20\x00', '\x30\x00', '\x40\x00']

# create table with four regions based on random bytes keys
hbase>create 't2','f1', { NUMREGIONS => 4 , SPLITALGO => 'UniformSplit' }

# create table with five regions based on hex keys
create 't3','f1', { NUMREGIONS => 5, SPLITALGO => 'HexStringSplit' }

参考 Relationship Between RowKeys and Region Splits ,获取和你的键空间和预创建Region相关的问题。参考 manual region splitting decisions 获取关于手动预分割Region的讨论。参考 Pre-splitting tables with the HBase Shell 获取使用HBase Shell预分割表的更多细节。

99.3. 表创建:延迟log刷写 Table Creation: Deferred Log Flush

Puts的缺省行为使用 Write Ahead Log (WAL),会导致 HLog 编辑立即写盘。如果采用延迟刷写,WAL编辑会保留在内存中,直到刷写周期来临。好处是集中和异步写HLog,潜在问题是如果RegionServer退出,没有刷写的日志将丢失。但这也比Puts时不使用WAL安全多了。

延迟log刷写可以通过 HTableDescriptor 在表上配置。hbase.regionserver.optionallogflushinterval 的默认值是 1000ms。

99.4. HBase客户端:自动刷写  HBase Client: AutoFlush

当你进行大量的Put的时候,要确认你的Table实例的setAutoFlush是关闭着的。否则的话,每执行一个Put就要向RegionServer发一个请求。通过 table.add(Put) 和 table.add( <List> Put)来将Put添加到写缓冲中。如果 autoFlush = false,要等到写缓冲(write-buffer)都填满的时候才会发起请求。要想显式的发起请求,可以调用flushCommits。在Table实例上进行的close操作也会发起flushCommits。

99.5. HBase客户端:在Puts上关闭WAL HBase Client: Turn off WAL on Puts

一个频繁的请求是要禁用WAL以提高Puts的性能。这只适用于批量加载(bulk loads),因为这种方式失去了在RegionServer crash时WAL的保护从而把你的数据至于风险之中。批量加载Bulk Load可以在RegionServer crash时重新运行,几乎没有数据丢失的风险。

一个经常讨论的在Puts上增加吞吐量的选项是调用 writeToWAL(false)。关闭它意味着 RegionServer 不再将 Put 写到 Write Ahead Log, 仅写到内存。然而后果是如果出现 RegionServer 失败,将导致数据丢失。如果调用 writeToWAL(false) ,需保持高度警惕。你会发现实际上基本没有不同,如果你的负载很好的分布到集群中。

如果你在批量加载以外的情景下禁用WAL,你的数据会处于风险之中。

 

 

通常而言,最好对Puts使用WAL,而增加负载吞吐量与使用批量加载技术有关。对于普通的Puts,你不太可能看到比风险更有价值的性能上的改善。要禁用 WAL, 参考 Disabling the WAL.

99.6. HBase客户端:由RegionServer成组写入 HBase Client: Group Puts by RegionServer

除了使用writeBuffer以外,由RegionServer将Put分组可以减少客户端RPC对每一个writeBuffer刷写调用的次数。目前在MASTER中有一个实用的HTableUtil可以这样做,但是你可以为那些仍然在0.90.x或更早HBase版本上的系统复制HTableUtil或者实现你自己的版本。

99.7. MapReduce: 跳过Reducer  Skip The Reducer

当通过一个MR作业(例如,使用TableOutputFormat),把大量数据写入到一个HBase表中时,特别地是从Mapper中发送出Puts时,跳过Reducer步骤。当使用了Reducer步骤时,所有的从Mapper的输出(Puts)将被放到磁盘上,然后数据被排序/混洗到很可能是非本地节点的其他Reducer中。相比于此,直接写入到HBase会更加有效率。

对于HBase被用作数据源(source)或数据汇总点(sink)的汇总作业,数据写入将来自Reducer步骤(例如,汇总值然后写出结果)。这是一个与上述情况不同的处理问题。

99.8. 反模式:一个热点Region Anti-Pattern: One Hot Region

如果你所有的数据正在被一次性写入到一个Region中,那么重新读取处理时间序列(timeseries)数据的部分。

还有,如果你做了预分割Regions,并且你所有的数据仍然写入一个Region中,即使你的key不是单调递增的,那么要确认你的键空间实际上与分割策略是有效的。有各种各样的原因使得Regions看起来“很分裂”,但是却不能与你的数据一起工作。当HBase客户端与RegionServers直接通信时,Region的信息可以通过 Table.getRegionLocation (在HBase1.2中,要使用Connection.getRegionLocatorRegionLocator.getRegionLocation)被获取到.


100. 从HBase读取 Reading from HBase

如果你有性能问题的话,这个邮件列表可以提供帮助。例如,这有一个关于读取时间问题的好的讨论: HBase Random Read latency > 100ms

100.1. Scan缓存 Scan Caching

如果HBase作为一个MapReduce Job的输入源,要确保Scan实例到这个MapReduce作业的输入的setCaching值要比默认值(默认值是1)要大。使用默认值就意味着map-task每一行都会去请求一下region-server。可以把这个值设为500,这样就可以一次传输500行给客户端去处理。当然这也是需要权衡的,过大的值会同时消耗客户端和服务端很大的内存,不是越大越好。

100.1.1. MapReduce作业中的Scan缓存  Scan Caching in MapReduce Jobs

在MapReduce作业里的Scan设置要特别注意。在客户端返回到RegionServer获取下一批数据之前,如果Map任务花费很长的时间去处理当前一批记录,会导致超时(例如UnknownScannerException)。这个问题会出现,是因为每行都有不简单的处理过程。如果你能快速处理行,则可以把缓存设置得大一些。如果你处理行很慢(例如,每一行有大量的转换、写操作),则设置缓存要小一些。

超时也会出现在非MapReduce用例中(例如,单线程的HBase客户端在做一个Scan操作时),但是在MapReduce作业中经常执行的处理往往会加剧这个问题。

100.2. Scan属性(列)的选择 Scan Attribute Selection

当Scan用来处理大量的行的时候(尤其是作为MapReduce的输入源时),要注意的是选择了什么字段(列)。如果调用了 scan.addFamily,这个列族的所有属性(列)都会返回给客户端。如果只是想处理其中的一小部分,就应该在输入的Scan中指定那几个属性(列),因为属性(列)的过度选择对于大型数据集来说是一个不小的性能损失。

100.3. 避免Scan seeks Avoid scan seeks

当列通过scan.addColumn被显示地选择时, HBase将在选定的列之间安排寻找操作。当行有很少的列且每一列有很少的版本时,这可能是低效的。如果不寻找至少5-10个列或版本或512-1024个字节,一个Seek(寻找)操作通常比较慢。

为了适时地查看几个列/版本以发现下一个列/版本在一个Seek操作被安排前是否能被找到,一个新的属性 Scan.HINT_LOOKAHEAD 可以在这个Scan对象上设置。下列代码介绍了RegionServer在一个Seek被安排之前尝试两个迭代:

Scan scan = new Scan();
scan.addColumn(...);
scan.setAttribute(Scan.HINT_LOOKAHEAD, Bytes.toBytes(2));
table.getScanner(scan);

100.4. MapReduce - Input Splits

对于使用HBase表做数据源的MapReduce作业来说,如果几个慢的Map任务似乎有相同的输入分片(Input Split)(例如,数据都存放在一个RegionServer上),查看Troubleshooting Case Study in Case Study #1 (Performance Issue On A Single Node).

100.5. 关闭ResultScanners Close ResultScanners

这与其说是提高性能,倒不如说是避免发生性能问题。如果你忘记了关闭ResultScanners,会导致RegionServer出现问题。所以一定要把ResultScanner包含在try/catch 块中。

Scan scan = new Scan();
// set attrs...
ResultScanner rs = table.getScanner(scan);
try {
  for (Result r = rs.next(); r != null; r = rs.next()) {
  // process result...
} finally {
  rs.close();  // always close the ResultScanner!
}
table.close();

100.6. Block Cache

Scan 实例可以通过setCacheBlocks 方法,被配置使用RegionServer中的块缓存(block cache)。对于作为MapReduce作业输入的Scans操作来说,这个值应该是false。对于频繁被访问的行,推荐使用块缓存。

通过移动你的堆外块缓存以缓存更多的数据。参考 Off-heap Block Cache

100.7. 行键的负载优化  Optimal Loading of Row Keys

scan一个表的时候,如果仅仅需要行键(不需要列族, 列标识符, 值 和 时间戳),就使用setFilter方法把一个带有MUST_PASS_ALL操作惨数的FilterList添加到这个Scanner中(译者注:MUST_PASS_ALL操作参数相当于And操作符)。一个过滤器列表要包含一个 FirstKeyOnlyFilter 和一个 KeyOnlyFilter。使用这样的filter组合,将导致在一个RegionServer从磁盘读取一个value的最坏情况下,最小化一个单行对客户端的网络通信量。

100.8. 并发:监视数据传输 Concurrency: Monitor Data Spread

当执行一个高并发的读取操作时,监视目标表的数据传输。如果目标表局限在几个少量的region上,那么可能服务于读取操作的节点就会很少。

100.9. Bloom Filters

启用布隆过滤可以节省必须读磁盘的过程,可以有助于改进读取延迟。

Bloom filters were developed over in HBase-1200 Add bloomfilters. For description of the development process — why static blooms rather than dynamic — and for an overview of the unique properties that pertain to blooms in HBase, as well as possible future directions, see the Development Process section of the document BloomFilters in HBase attached to HBASE-1200. The bloom filters described here are actually version two of blooms in HBase. In versions up to 0.19.x, HBase had a dynamic bloom option based on work done by the European Commission One-Lab Project 034819. The core of the HBase bloom work was later pulled up into Hadoop to implement org.apache.hadoop.io.BloomMapFile. Version 1 of HBase blooms never worked that well. Version 2 is a rewrite from scratch though again it starts with the one-lab work.

参考 Bloom Filters.

100.9.1. Bloom StoreFile footprint

Bloom filters add an entry to the StoreFile general FileInfo data structure and then two extra entries to the StoreFile metadata section.

BloomFilter in the StoreFile``FileInfo data structure

FileInfo has a BLOOM_FILTER_TYPE entry which is set to NONE, ROW or ROWCOL.

BloomFilter entries in StoreFile metadata

BLOOM_FILTER_META holds Bloom Size, Hash Function used, etc. It’s small in size and is cached on StoreFile.Reader load

BLOOM_FILTER_DATA is the actual bloomfilter data. Obtained on-demand. Stored in the LRU cache, if it is enabled (It’s enabled by default).

100.9.2. Bloom Filter Configuration

io.storefile.bloom.enabled global kill switch

io.storefile.bloom.enabled in Configuration serves as the kill switch in case something goes wrong. Default = true.

io.storefile.bloom.error.rate

io.storefile.bloom.error.rate = average false positive rate. Default = 1%. Decrease rate by ½ (e.g. to .5%) == +1 bit per bloom entry.

io.storefile.bloom.max.fold

io.storefile.bloom.max.fold = guaranteed minimum fold rate. Most people should leave this alone. Default = 7, or can collapse to at least 1/128th of original size. See the Development Process section of the document BloomFilters in HBase for more on what this option means.

100.10. Hedged Reads

Hedged reads是HDFS的一个功能,在 HDFS-5776 引入。通常,为每个读请求生成一个线程。然而,如果Hedged Reads被启用,那么客户端等待了一些可配置的时间后,如果读取没有返回,则客户端会生成第二个读取请求,去读取相同数据的不同块备份。首先返回的读取结果会被使用,另一个读取请求将被丢弃。Hedged reads 对于一个由错误的磁盘或脆弱的网络连接造成的罕见的慢读,很有帮助。

因为一个HBase RegionServer是一个HDFS客户端,你可以在HBase中启用Hedged Reads,在RegionServer的hbase-site.xml中添加下列属性,并调整它们的值以适应你的环境。

Configuration for Hedged Reads
  • dfs.client.hedged.read.threadpool.size - 专用于服务Hedged Reads的线程数。如果被设置成 0 (默认值), hedged reads被禁用.

  • dfs.client.hedged.read.threshold.millis - 在生成第二个读取线程前等待的毫秒数。

Example 56. Hedged Reads Configuration Example
<property>
  <name>dfs.client.hedged.read.threadpool.size</name>
  <value>20</value>  <!-- 20 threads -->
</property>
<property>
  <name>dfs.client.hedged.read.threshold.millis</name>
  <value>10</value>  <!-- 10 milliseconds -->
</property>

使用以下度量方式调整集群上Hedged Reads的设置。参考 HBase Metrics 获取更多信息。

Metrics for Hedged Reads
  • hedgedReadOps - Hedged Read线程被触发的次数。这可能表明读取请求通常很慢,或者被触发的读取速度太快。

  • hedgeReadOpsWin - Hedged Read 线程快于原有读取线程的次数。这可能表明给定的RegionServer(原有读取线程要读取的数据所在的RegionServer)在请求的服务上有问题。


101. 从HBase中删除数据 Deleting from HBase

101.1. 将HBase表当做队列 Using HBase Tables as Queues

HBase表有时被用作队列使用。在这个场景下,必须采取特殊的措施,定期对以这种方式使用的表执行Major Compaction操作。如 Data Model 描述的那样, 标记行为被删除会创建额外的StoreFiles,这些文件在读取过程中需要被处理。墓碑只有在Major Compactions时才会被清除。

101.2. 删除RPC的行为 Delete RPC Behavior

要注意的是 Table.delete(Delete) 不使用 writeBuffer。它在每次调用时将执行一个RegionServer RPC。对于大量数据的删除,考虑使用 Table.delete(List).

参考 hbase.client.Delete.


102. HDFS

因为 HBase 运行在 HDFS 上,理解HDFS如何工作和如何影响HBase是重要的。

102.1. 目前在低延迟读取时的问题  Current Issues With Low-Latency Reads

原有的HDFS的用例是批量处理。因此,低延迟读取在历史上并不是优先做的事情。随着Apache HBase的使用越来越多,这种情况正在发生变化,并且已经有了一些改进。参考 Umbrella Jira Ticket for HDFS Improvements for HBase.

102.2. 利用本地数据 Leveraging local data

自从 Hadoop 1.0.0 (also 0.22.1, 0.23.1, CDH3u3 and HDP 1.0) via HDFS-2246, 当数据是本地数据时,DFSClient可能会采用“短路(short circuit)”并直接从磁盘读取数据,而不是通过DataNode。对于HBase这意味着,RegionServers可以直接读取它们机器上的磁盘,而不必打开一个socket去和DataNode沟通,前者一般来说会快很多。参考 JD’s Performance Talk. 参考 HBase, mail # dev - read short circuit 这条mail线以获取关于短路读取(short circuit reads)更多的讨论。

要启用“短路”读取,这要取决于你的Hadoop版本。原有的短路读取补丁在Hadoop 2 HDFS-347上有很大改进。参考 http://blog.cloudera.com/blog/2013/08/how-improved-short-circuit-local-reads-bring-better-performance-and-security-to-hadoop/ 以获取关于旧实现和新实现不同之处的细节。参考 Hadoop shortcircuit reads configuration page 以获取如何启用短路读取的新的实现。例如,这有一个最小化的配置,在hbase-site.xml中添加启用短路读取的配置:

<property>
  <name>dfs.client.read.shortcircuit</name>
  <value>true</value>
  <description>
    This configuration parameter turns on short-circuit local reads.
  </description>
</property>
<property>
  <name>dfs.domain.socket.path</name>
  <value>/home/stack/sockets/short_circuit_read_socket_PORT</value>
  <description>
    Optional.  This is a path to a UNIX domain socket that will be used for
    communication between the DataNode and local HDFS clients.
    If the string "_PORT" is present in this path, it will be replaced by the
    TCP port of the DataNode.
  </description>
</property>

对于托管共享domain socket的目录的权限要小心;如果向其他用户开放,那么dfsclient将会complain。

如果你的HBase运行在旧版的Hadoop上,而这版的Hadoop没有实现 HDFS-347 而实现了HDFS-2246, 你必须设置两个配置。首先,需要修改 hdfs-site.xml。设置属性dfs.block.local-path-access.user 仅为可以使用短路的user。这个user不得不是启动HBase的user。然后,在 hbase-site.xml 中, 设置 dfs.client.read.shortcircuit 为 true。

多个服务 — 至少是 HBase RegionServers — 需要重新启动以实现新的配置。

dfs.client.read.shortcircuit.buffer.size

当在高流量的HBase上运行时,这个值的默认值太高了。在HBase中,如果这个值没被设置,我们把它的值从1M的默认值降到128k (从 HBase 0.98.0 和 0.96.1开始). 参考 HBASE-8143 HBase on Hadoop 2 with local short circuit reads (ssr) causes OOM). HBase中的 Hadoop DFSClient 将将为它所打开的每个块分配一个该大小的直接字节缓冲区; 由于HBase一直保持其HDFS文件的开放,因此可以快速添加。

102.3. HBase和HDFS的性能比较 Performance Comparisons of HBase vs. HDFS

一个相当常见的问题是为什么HBase在批处理环境中不像HDFS文件那样具有性能(例如,作为MapReduce的数据源或数据汇集点)。简而言之,HBase比HDFS做的更多 (例如, 读取 KeyValues, 返回当前的行或指定的时间戳等等), 在这个处理环境中,HBase的速度比HDFS慢4-5倍。有改进的余地,随着时间的推移,这个差距将会缩小,但是在这个用例中,HDFS总是更快。


103. Amazon EC2

Performance questions are common on Amazon EC2 environments because it is a shared environment. You will not see the same throughput as a dedicated server. In terms of running tests on EC2, run them several times for the same reason (i.e., it’s a shared environment and you don’t know what else is happening on the server).

If you are running on EC2 and post performance questions on the dist-list, please state this fact up-front that because EC2 issues are practically a separate class of performance issues.


104. HBase和MapReduce的搭配  Collocating HBase and MapReduce

通常建议对HBase和MapReduce有不同的集群。一个更好的限定是:不要分配一个HBase去服务一个沉重的MR工作负载的实时请求。OLTP和优化的OLAP系统有需求上的冲突,其中一个会输给另一个,通常是前者会输。例如,短延迟敏感的磁盘读取将不得不排队等待更长时间的读取,以便尽可能地挤出更多的吞吐量。写入到HBase的MR作业会产生刷写和紧缩compactions,这将使Block Cache中的块失效。

如果你需要在MR中处理从HBase集群中获取的数据,你可以把数据通过CopyTable传输数据或使用复制去获取OLAP集群的实时数据。在最坏的情况下,如果你真的需要搭配HBase和MapReduce的话,设置MR使用比正常配置要少的Map和Reduce槽位,可能只有1个。

当HBase被用来做OLAP操作时,最好将其设置为更硬的方式,比如将ZooKeeper会话超时设置为更高,并将更多的内存用于MemStores(参数设置成那样是由于工作负载通常是长时间扫描,所以块缓存不被使用得太多)。


105. Case Studies

For Performance and Troubleshooting Case Studies, see Apache HBase Case Studies.

 

 

 

posted @ 2017-10-12 13:42  子秦  阅读(694)  评论(0编辑  收藏  举报